refactor limiter
fix getLink bug
add connection limit
move limit config to ControllerConfig
del dynamic speed limit (next version will be re add)
del online ip sync (next version will be re add)
This commit is contained in:
yuzuki999 2023-05-16 09:15:29 +08:00
parent 2d7aaef066
commit 15c36a9580
35 changed files with 564 additions and 617 deletions

View File

@ -1,7 +1,9 @@
package iprecoder
import "github.com/Yuzuki616/V2bX/core/app/dispatcher"
import (
"github.com/Yuzuki616/V2bX/limiter"
)
type IpRecorder interface {
SyncOnlineIp(Ips []dispatcher.UserIpList) ([]dispatcher.UserIpList, error)
SyncOnlineIp(Ips []limiter.UserIpList) ([]limiter.UserIpList, error)
}

View File

@ -3,7 +3,7 @@ package iprecoder
import (
"errors"
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/core/app/dispatcher"
"github.com/Yuzuki616/V2bX/limiter"
"github.com/go-resty/resty/v2"
"github.com/goccy/go-json"
"time"
@ -21,7 +21,7 @@ func NewRecorder(c *conf.RecorderConfig) *Recorder {
}
}
func (r *Recorder) SyncOnlineIp(ips []dispatcher.UserIpList) ([]dispatcher.UserIpList, error) {
func (r *Recorder) SyncOnlineIp(ips []limiter.UserIpList) ([]limiter.UserIpList, error) {
rsp, err := r.client.R().
SetBody(ips).
Post(r.Url + "/api/v1/SyncOnlineIp?token=" + r.Token)
@ -31,7 +31,7 @@ func (r *Recorder) SyncOnlineIp(ips []dispatcher.UserIpList) ([]dispatcher.UserI
if rsp.StatusCode() != 200 {
return nil, errors.New(rsp.String())
}
ips = []dispatcher.UserIpList{}
ips = []limiter.UserIpList{}
err = json.Unmarshal(rsp.Body(), &ips)
if err != nil {
return nil, err

View File

@ -4,7 +4,7 @@ import (
"context"
"fmt"
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/core/app/dispatcher"
"github.com/Yuzuki616/V2bX/limiter"
"github.com/go-redis/redis/v8"
"strconv"
"time"
@ -26,7 +26,7 @@ func NewRedis(c *conf.RedisConfig) *Redis {
}
}
func (r *Redis) SyncOnlineIp(Ips []dispatcher.UserIpList) ([]dispatcher.UserIpList, error) {
func (r *Redis) SyncOnlineIp(Ips []limiter.UserIpList) ([]limiter.UserIpList, error) {
ctx := context.Background()
for i := range Ips {
err := r.client.SAdd(ctx, "UserList", Ips[i].Uid).Err()
@ -46,7 +46,7 @@ func (r *Redis) SyncOnlineIp(Ips []dispatcher.UserIpList) ([]dispatcher.UserIpLi
if c.Err() != nil {
return nil, fmt.Errorf("get user list failed: %s", c.Err())
}
Ips = make([]dispatcher.UserIpList, 0, len(c.Val()))
Ips = make([]limiter.UserIpList, 0, len(c.Val()))
for _, uid := range c.Val() {
uidInt, err := strconv.Atoi(uid)
if err != nil {
@ -56,7 +56,7 @@ func (r *Redis) SyncOnlineIp(Ips []dispatcher.UserIpList) ([]dispatcher.UserIpLi
if ips.Err() != nil {
return nil, fmt.Errorf("get ip list failed: %s", ips.Err())
}
Ips = append(Ips, dispatcher.UserIpList{
Ips = append(Ips, limiter.UserIpList{
Uid: uidInt,
IpList: ips.Val(),
})

View File

@ -2,7 +2,7 @@ package iprecoder
import (
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/core/app/dispatcher"
"github.com/Yuzuki616/V2bX/limiter"
"log"
"testing"
)
@ -13,7 +13,7 @@ func TestRedis_SyncOnlineIp(t *testing.T) {
Password: "",
Db: 0,
})
users, err := r.SyncOnlineIp([]dispatcher.UserIpList{
users, err := r.SyncOnlineIp([]limiter.UserIpList{
{1, []string{"5.5.5.5", "4.4.4.4"}},
})
if err != nil {

View File

@ -38,10 +38,6 @@ type DestinationRule struct {
type localNodeConfig struct {
NodeId int
NodeType string
EnableVless bool
EnableTls bool
SpeedLimit int
DeviceLimit int
}
func (c *Client) GetNodeInfo() (nodeInfo *NodeInfo, err error) {

View File

@ -28,8 +28,6 @@ type Client struct {
Key string
NodeType string
NodeId int
SpeedLimit int
DeviceLimit int
LocalRuleList []DestinationRule
etag string
}
@ -69,8 +67,6 @@ func New(c *conf.ApiConfig) (Panel, error) {
Key: c.Key,
APIHost: c.APIHost,
NodeType: c.NodeType,
SpeedLimit: c.SpeedLimit,
DeviceLimit: c.DeviceLimit,
NodeId: c.NodeID,
LocalRuleList: localRuleList,
}, nil

View File

@ -10,18 +10,9 @@ type OnlineUser struct {
IP string
}
type V2RayUserInfo struct {
Uuid string `json:"uuid"`
Email string `json:"email"`
AlterId int `json:"alter_id"`
}
type TrojanUserInfo struct {
Password string `json:"password"`
}
type UserInfo struct {
Id int `json:"id"`
Uuid string `json:"uuid"`
Email string `json:"-"`
SpeedLimit int `json:"speed_limit"`
Traffic int64 `json:"-"`
}

28
common/rate/rate.go Normal file
View File

@ -0,0 +1,28 @@
package rate
import (
"github.com/juju/ratelimit"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
)
type Writer struct {
writer buf.Writer
limiter *ratelimit.Bucket
}
func NewRateLimitWriter(writer buf.Writer, limiter *ratelimit.Bucket) buf.Writer {
return &Writer{
writer: writer,
limiter: limiter,
}
}
func (w *Writer) Close() error {
return common.Close(w.writer)
}
func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {
w.limiter.Wait(int64(mb.Len()))
return w.writer.WriteMultiBuffer(mb)
}

View File

@ -67,7 +67,7 @@ func (p *Conf) Watch(filePath string, reload func()) error {
}
case err := <-watcher.Errors:
if err != nil {
log.Panicf("watcher error: %s", err)
log.Printf("File watcher error: %s", err)
}
}
}

View File

@ -52,6 +52,11 @@ type ControllerConfig struct {
SendIP string `yaml:"SendIP"`
EnableDNS bool `yaml:"EnableDNS"`
DNSType string `yaml:"DNSType"`
EnableVless bool `yaml:"EnableVless"`
EnableTls bool `yaml:"EnableTls"`
SpeedLimit int `yaml:"SpeedLimit"`
IPLimit int `yaml:"DeviceLimit"`
ConnLimit int `yaml:"ConnLimit"`
DisableUploadTraffic bool `yaml:"DisableUploadTraffic"`
DisableGetRule bool `yaml:"DisableGetRule"`
EnableProxyProtocol bool `yaml:"EnableProxyProtocol"`
@ -71,12 +76,8 @@ type ApiConfig struct {
NodeID int `yaml:"NodeID"`
Key string `yaml:"ApiKey"`
NodeType string `yaml:"NodeType"`
EnableVless bool `yaml:"EnableVless"`
Timeout int `yaml:"Timeout"`
SpeedLimit int `yaml:"SpeedLimit"`
DeviceLimit int `yaml:"DeviceLimit"`
RuleListPath string `yaml:"RuleListPath"`
DisableCustomConfig bool `yaml:"DisableCustomConfig"`
}
type NodeConfig struct {

View File

@ -5,6 +5,8 @@ package dispatcher
import (
"context"
"fmt"
"github.com/Yuzuki616/V2bX/common/rate"
"github.com/Yuzuki616/V2bX/limiter"
routingSession "github.com/xtls/xray-core/features/routing/session"
"strings"
"sync"
@ -95,8 +97,6 @@ type DefaultDispatcher struct {
stats stats.Manager
dns dns.Client
fdns dns.FakeDNSEngine
Limiter *Limiter
RuleManager *Rule
}
func init() {
@ -121,8 +121,6 @@ func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router rou
d.policy = pm
d.stats = sm
d.dns = dns
d.Limiter = NewLimiter()
d.RuleManager = NewRule()
return nil
}
@ -139,7 +137,7 @@ func (*DefaultDispatcher) Start() error {
// Close implements common.Closable.
func (*DefaultDispatcher) Close() error { return nil }
func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sniffing session.SniffingRequest) (*transport.Link, *transport.Link) {
func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sniffing session.SniffingRequest) (*transport.Link, *transport.Link, *limiter.Limiter, error) {
downOpt := pipe.OptionsFromContext(ctx)
upOpt := downOpt
@ -226,21 +224,31 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sn
if sessionInbound != nil {
user = sessionInbound.User
}
var limit *limiter.Limiter
if user != nil && len(user.Email) > 0 {
// Speed Limit and Device Limit
bucket, ok, reject := d.Limiter.CheckSpeedAndDeviceLimit(sessionInbound.Tag, user.Email, sessionInbound.Source.Address.IP().String())
if reject {
newError("Devices reach the limit: ", user.Email).AtError().WriteToLog()
var err error
limit, err = limiter.GetLimiter(sessionInbound.Tag)
if err != nil {
newError("Get limit info error: ", err).AtError().WriteToLog()
common.Close(outboundLink.Writer)
common.Close(inboundLink.Writer)
common.Interrupt(outboundLink.Reader)
common.Interrupt(inboundLink.Reader)
return nil, nil
return nil, nil, nil, newError("Get limit info error: ", err)
}
if ok {
inboundLink.Writer = d.Limiter.RateWriter(inboundLink.Writer, bucket)
outboundLink.Writer = d.Limiter.RateWriter(outboundLink.Writer, bucket)
// Speed Limit and Device Limit
w, reject := limit.CheckLimit(user.Email, sessionInbound.Source.Address.IP().String())
if reject {
newError("Limited ", user.Email, " by conn or ip").AtWarning().WriteToLog()
common.Close(outboundLink.Writer)
common.Close(inboundLink.Writer)
common.Interrupt(outboundLink.Reader)
common.Interrupt(inboundLink.Reader)
return nil, nil, nil, newError("Limited ", user.Email, " by conn or ip")
}
if w != nil {
inboundLink.Writer = rate.NewRateLimitWriter(inboundLink.Writer, w)
outboundLink.Writer = rate.NewRateLimitWriter(outboundLink.Writer, w)
}
p := d.policy.ForLevel(user.Level)
if p.Stats.UserUplink {
@ -263,7 +271,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sn
}
}
return inboundLink, outboundLink
return inboundLink, outboundLink, limit, nil
}
func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool {
@ -313,11 +321,13 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
content = new(session.Content)
ctx = session.ContextWithContent(ctx, content)
}
sniffingRequest := content.SniffingRequest
inbound, outbound := d.getLink(ctx, destination.Network, sniffingRequest)
inbound, outbound, l, err := d.getLink(ctx, destination.Network, sniffingRequest)
if err != nil {
return nil, err
}
if !sniffingRequest.Enabled {
go d.routedDispatch(ctx, outbound, destination, "")
go d.routedDispatch(ctx, outbound, destination, l)
} else {
go func() {
cReader := &cachedReader{
@ -338,7 +348,7 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
ob.Target = destination
}
}
d.routedDispatch(ctx, outbound, destination, content.Protocol)
d.routedDispatch(ctx, outbound, destination, l)
}()
}
return inbound, nil
@ -360,7 +370,7 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
}
sniffingRequest := content.SniffingRequest
if !sniffingRequest.Enabled {
go d.routedDispatch(ctx, outbound, destination, content.Protocol)
go d.routedDispatch(ctx, outbound, destination, nil)
} else {
go func() {
cReader := &cachedReader{
@ -381,10 +391,9 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
ob.Target = destination
}
}
d.routedDispatch(ctx, outbound, destination, content.Protocol)
d.routedDispatch(ctx, outbound, destination, nil)
}()
}
return nil
}
@ -434,7 +443,7 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, netw
return contentResult, contentErr
}
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination, protocol string) {
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination, l *limiter.Limiter) {
ob := session.OutboundFromContext(ctx)
if hosts, ok := d.dns.(dns.HostsLookup); ok && destination.Address.Family().IsDomain() {
proxied := hosts.LookupHosts(ob.Target.String())
@ -455,9 +464,22 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
sessionInbound := session.InboundFromContext(ctx)
// Whether the inbound connection contains a user
if sessionInbound.User != nil {
if d.RuleManager.Detect(sessionInbound.Tag, destination.String(), protocol) {
if l == nil {
var err error
l, err = limiter.GetLimiter(sessionInbound.Tag)
if err != nil {
newError("Get limiter error: ", err).AtError().WriteToLog()
common.Close(link.Writer)
common.Interrupt(link.Reader)
return
}
} else {
defer func() {
l.ConnLimiter.DelConnCount(sessionInbound.User.Email, sessionInbound.Source.Address.IP().String())
}()
}
if l.CheckDomainRule(destination.String()) {
newError(fmt.Sprintf("User %s access %s reject by rule", sessionInbound.User.Email, destination.String())).AtError().WriteToLog()
newError("destination is reject by rule")
common.Close(link.Writer)
common.Interrupt(link.Reader)
return

View File

@ -1,305 +0,0 @@
package dispatcher
import (
"fmt"
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/juju/ratelimit"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"io"
"sync"
"time"
)
type UserLimitInfo struct {
UID int
SpeedLimit int
DynamicSpeedLimit int
ExpireTime int64
}
type InboundInfo struct {
Tag string
NodeSpeedLimit int
NodeDeviceLimit int
UserLimitInfo *sync.Map // Key: Uid value: UserLimitInfo
SpeedLimiter *sync.Map // key: Uid, value: *ratelimit.Bucket
UserOnlineIP *sync.Map // Key: Uid Value: *sync.Map: Key: IP, Value: bool
}
type Limiter struct {
InboundInfo *sync.Map // Key: Tag, Value: *InboundInfo
}
func NewLimiter() *Limiter {
return &Limiter{
InboundInfo: new(sync.Map),
}
}
func (l *Limiter) AddInboundLimiter(tag string, nodeInfo *panel.NodeInfo, users []panel.UserInfo) error {
inboundInfo := &InboundInfo{
Tag: tag,
NodeSpeedLimit: nodeInfo.SpeedLimit,
NodeDeviceLimit: nodeInfo.DeviceLimit,
UserLimitInfo: new(sync.Map),
SpeedLimiter: new(sync.Map),
UserOnlineIP: new(sync.Map),
}
for i := range users {
if users[i].SpeedLimit != 0 {
userLimit := &UserLimitInfo{
UID: users[i].Id,
SpeedLimit: users[i].SpeedLimit,
ExpireTime: 0,
}
inboundInfo.UserLimitInfo.Store(fmt.Sprintf("%s|%s|%d", tag, users[i].Uuid, users[i].Id), userLimit)
}
}
l.InboundInfo.Store(tag, inboundInfo) // Replace the old inbound info
return nil
}
func (l *Limiter) UpdateInboundLimiter(tag string, added []panel.UserInfo, deleted []panel.UserInfo) error {
if value, ok := l.InboundInfo.Load(tag); ok {
inboundInfo := value.(*InboundInfo)
for i := range deleted {
inboundInfo.UserLimitInfo.Delete(fmt.Sprintf("%s|%s|%d", tag,
(deleted)[i].Uuid, (deleted)[i].Id))
}
for i := range added {
if added[i].SpeedLimit != 0 {
userLimit := &UserLimitInfo{
UID: added[i].Id,
SpeedLimit: added[i].SpeedLimit,
ExpireTime: 0,
}
inboundInfo.UserLimitInfo.Store(fmt.Sprintf("%s|%s|%d", tag,
(added)[i].Uuid, (added)[i].Id), userLimit)
}
}
} else {
return fmt.Errorf("no such inbound in limiter: %s", tag)
}
return nil
}
func (l *Limiter) DeleteInboundLimiter(tag string) error {
l.InboundInfo.Delete(tag)
return nil
}
func (l *Limiter) AddDynamicSpeedLimit(tag string, userInfo *panel.UserInfo, limit int, expire int64) error {
if value, ok := l.InboundInfo.Load(tag); ok {
inboundInfo := value.(*InboundInfo)
userLimit := &UserLimitInfo{
DynamicSpeedLimit: limit,
ExpireTime: time.Now().Add(time.Duration(expire) * time.Second).Unix(),
}
inboundInfo.UserLimitInfo.Store(fmt.Sprintf("%s|%s|%d", tag, userInfo.Uuid, userInfo.Id), userLimit)
return nil
} else {
return fmt.Errorf("no such inbound in limiter: %s", tag)
}
}
type UserIpList struct {
Uid int `json:"Uid"`
IpList []string `json:"Ips"`
}
func (l *Limiter) ListOnlineUserIp(tag string) ([]UserIpList, error) {
if value, ok := l.InboundInfo.Load(tag); ok {
inboundInfo := value.(*InboundInfo)
onlineUser := make([]UserIpList, 0)
var ipMap *sync.Map
inboundInfo.UserOnlineIP.Range(func(key, value interface{}) bool {
ipMap = value.(*sync.Map)
var ip []string
ipMap.Range(func(key, v interface{}) bool {
if v.(bool) {
ip = append(ip, key.(string))
}
return true
})
if len(ip) > 0 {
if u, ok := inboundInfo.UserLimitInfo.Load(key.(string)); ok {
onlineUser = append(onlineUser, UserIpList{
Uid: u.(*UserLimitInfo).UID,
IpList: ip,
})
}
}
return true
})
if len(onlineUser) == 0 {
return nil, nil
}
return onlineUser, nil
} else {
return nil, fmt.Errorf("no such inbound in limiter: %s", tag)
}
}
func (l *Limiter) UpdateOnlineUserIP(tag string, userIpList []UserIpList) {
if v, ok := l.InboundInfo.Load(tag); ok {
inboundInfo := v.(*InboundInfo)
//Clear old IP
inboundInfo.UserOnlineIP.Range(func(key, value interface{}) bool {
inboundInfo.UserOnlineIP.Delete(key)
return true
})
// Update User Online IP
for i := range userIpList {
ipMap := new(sync.Map)
for _, userIp := range (userIpList)[i].IpList {
ipMap.Store(userIp, false)
}
inboundInfo.UserOnlineIP.Store((userIpList)[i].Uid, ipMap)
}
inboundInfo.SpeedLimiter.Range(func(key, value interface{}) bool {
if _, exists := inboundInfo.UserOnlineIP.Load(key.(string)); !exists {
inboundInfo.SpeedLimiter.Delete(key.(string))
}
return true
})
}
}
func (l *Limiter) ClearOnlineUserIpAndSpeedLimiter(tag string) {
if v, ok := l.InboundInfo.Load(tag); ok {
inboundInfo := v.(*InboundInfo)
inboundInfo.SpeedLimiter.Range(func(key, value interface{}) bool {
if _, exists := inboundInfo.UserOnlineIP.Load(key.(string)); !exists {
inboundInfo.SpeedLimiter.Delete(key.(string))
}
return true
})
inboundInfo.UserOnlineIP.Range(func(key, value interface{}) bool {
inboundInfo.UserOnlineIP.Delete(key)
return true
})
}
}
func (l *Limiter) CheckSpeedAndDeviceLimit(tag string, email string, ip string) (speedLimiter *ratelimit.Bucket, SpeedLimit bool, Reject bool) {
if value, ok := l.InboundInfo.Load(tag); ok {
inboundInfo := value.(*InboundInfo)
nodeLimit := inboundInfo.NodeSpeedLimit
userLimit := 0
if v, ok := inboundInfo.UserLimitInfo.Load(email); ok {
u := v.(*UserLimitInfo)
if u.ExpireTime < time.Now().Unix() && u.ExpireTime != 0 {
if u.SpeedLimit != 0 {
userLimit = u.SpeedLimit
u.DynamicSpeedLimit = 0
u.ExpireTime = 0
} else {
inboundInfo.UserLimitInfo.Delete(email)
}
} else {
userLimit = determineSpeedLimit(u.SpeedLimit, u.DynamicSpeedLimit)
}
}
ipMap := new(sync.Map)
ipMap.Store(ip, true)
// If any device is online
if v, ok := inboundInfo.UserOnlineIP.LoadOrStore(email, ipMap); ok {
ipMap := v.(*sync.Map)
// If this ip is a new device
if online, ok := ipMap.LoadOrStore(ip, true); !ok {
counter := 0
ipMap.Range(func(key, value interface{}) bool {
counter++
return true
})
if counter > inboundInfo.NodeDeviceLimit && inboundInfo.NodeDeviceLimit > 0 {
ipMap.Delete(ip)
return nil, false, true
}
} else {
if !online.(bool) {
ipMap.Store(ip, true)
}
}
}
limit := int64(determineSpeedLimit(nodeLimit, userLimit)) * 1000000 / 8 // If you need the Speed limit
if limit > 0 {
limiter := ratelimit.NewBucketWithQuantum(time.Second, limit, limit) // Byte/s
if v, ok := inboundInfo.SpeedLimiter.LoadOrStore(email, limiter); ok {
return v.(*ratelimit.Bucket), true, false
} else {
inboundInfo.SpeedLimiter.Store(email, limiter)
return limiter, true, false
}
} else {
return nil, false, false
}
} else {
newError("Get Inbound Limiter information failed").AtDebug().WriteToLog()
return nil, false, false
}
}
type Writer struct {
writer buf.Writer
limiter *ratelimit.Bucket
w io.Writer
}
func (l *Limiter) RateWriter(writer buf.Writer, limiter *ratelimit.Bucket) buf.Writer {
return &Writer{
writer: writer,
limiter: limiter,
}
}
func (w *Writer) Close() error {
return common.Close(w.writer)
}
func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {
w.limiter.Wait(int64(mb.Len()))
return w.writer.WriteMultiBuffer(mb)
}
// determineSpeedLimit returns the minimum non-zero rate
func determineSpeedLimit(limit1, limit2 int) (limit int) {
if limit1 == 0 || limit2 == 0 {
if limit1 > limit2 {
return limit1
} else if limit1 < limit2 {
return limit2
} else {
return 0
}
} else {
if limit1 > limit2 {
return limit2
} else if limit1 < limit2 {
return limit1
} else {
return limit1
}
}
}
func determineDeviceLimit(nodeLimit, userLimit int) (limit int) {
if nodeLimit == 0 || userLimit == 0 {
if nodeLimit > userLimit {
return nodeLimit
} else if nodeLimit < userLimit {
return userLimit
} else {
return 0
}
} else {
if nodeLimit > userLimit {
return userLimit
} else if nodeLimit < userLimit {
return nodeLimit
} else {
return nodeLimit
}
}
}

View File

@ -1,42 +0,0 @@
package dispatcher
import (
"github.com/Yuzuki616/V2bX/api/panel"
"reflect"
"sync"
)
type Rule struct {
Rule *sync.Map // Key: Tag, Value: *panel.DetectRule
}
func NewRule() *Rule {
return &Rule{
Rule: new(sync.Map),
}
}
func (r *Rule) UpdateRule(tag string, newRuleList []panel.DestinationRule) error {
if value, ok := r.Rule.LoadOrStore(tag, newRuleList); ok {
oldRuleList := value.([]panel.DestinationRule)
if !reflect.DeepEqual(oldRuleList, newRuleList) {
r.Rule.Store(tag, newRuleList)
}
}
return nil
}
func (r *Rule) Detect(tag string, destination string, protocol string) (reject bool) {
reject = false
// If we have some rule for this inbound
if value, ok := r.Rule.Load(tag); ok {
ruleList := value.([]panel.DestinationRule)
for i := range ruleList {
if ruleList[i].Pattern.Match([]byte(destination)) {
reject = true
break
}
}
}
return reject
}

View File

@ -30,9 +30,6 @@ import (
// Fix dependency cycle caused by core import in internet package
_ "github.com/xtls/xray-core/transport/internet/tagged/taggedimpl"
// Developer preview features
_ "github.com/xtls/xray-core/app/observatory"
// Inbound and outbound proxies.
_ "github.com/xtls/xray-core/proxy/blackhole"
_ "github.com/xtls/xray-core/proxy/dns"

View File

@ -3,8 +3,6 @@ package core
import (
"context"
"fmt"
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/core/app/dispatcher"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/inbound"
)
@ -27,27 +25,3 @@ func (p *Core) AddInbound(config *core.InboundHandlerConfig) error {
}
return nil
}
func (p *Core) AddInboundLimiter(tag string, nodeInfo *panel.NodeInfo, users []panel.UserInfo) error {
return p.dispatcher.Limiter.AddInboundLimiter(tag, nodeInfo, users)
}
func (p *Core) GetInboundLimiter(tag string) (*dispatcher.InboundInfo, error) {
limit, ok := p.dispatcher.Limiter.InboundInfo.Load(tag)
if ok {
return limit.(*dispatcher.InboundInfo), nil
}
return nil, fmt.Errorf("not found limiter")
}
func (p *Core) UpdateInboundLimiter(tag string, added []panel.UserInfo, deleted []panel.UserInfo) error {
return p.dispatcher.Limiter.UpdateInboundLimiter(tag, added, deleted)
}
func (p *Core) DeleteInboundLimiter(tag string) error {
return p.dispatcher.Limiter.DeleteInboundLimiter(tag)
}
func (p *Core) UpdateRule(tag string, newRuleList []panel.DestinationRule) error {
return p.dispatcher.RuleManager.UpdateRule(tag, newRuleList)
}

View File

@ -3,8 +3,6 @@ package core
import (
"context"
"fmt"
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/core/app/dispatcher"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/proxy"
)
@ -79,19 +77,3 @@ func (p *Core) GetUserTraffic(email string, reset bool) (up int64, down int64) {
}
return up, down
}
func (p *Core) AddUserSpeedLimit(tag string, user *panel.UserInfo, speedLimit int, expire int64) error {
return p.dispatcher.Limiter.AddDynamicSpeedLimit(tag, user, speedLimit, expire)
}
func (p *Core) ListOnlineIp(tag string) ([]dispatcher.UserIpList, error) {
return p.dispatcher.Limiter.ListOnlineUserIp(tag)
}
func (p *Core) UpdateOnlineIp(tag string, ips []dispatcher.UserIpList) {
p.dispatcher.Limiter.UpdateOnlineUserIP(tag, ips)
}
func (p *Core) ClearOnlineIp(tag string) {
p.dispatcher.Limiter.ClearOnlineUserIpAndSpeedLimiter(tag)
}

96
limiter/conn.go Normal file
View File

@ -0,0 +1,96 @@
package limiter
import (
"sync"
)
type ConnLimiter struct {
ipLimit int
connLimit int
count sync.Map //map[string]int
ip sync.Map //map[string]map[string]*sync.Map
}
func NewConnLimiter(conn int, ip int) *ConnLimiter {
return &ConnLimiter{
connLimit: conn,
ipLimit: ip,
count: sync.Map{},
ip: sync.Map{},
}
}
func (c *ConnLimiter) AddConnCount(user string, ip string) (limit bool) {
if c.connLimit != 0 {
if v, ok := c.count.Load(user); ok {
if v.(int) >= c.connLimit {
return true
} else {
c.count.Store(user, v.(int)+1)
}
} else {
c.count.Store(user, 1)
}
}
if c.ipLimit == 0 {
return false
}
ipMap := new(sync.Map)
ipMap.Store(ip, 1)
if v, ok := c.ip.LoadOrStore(user, ipMap); ok {
// have online ip
ips := v.(*sync.Map)
cn := 0
if online, ok := ips.Load(ip); !ok {
ips.Range(func(key, value interface{}) bool {
cn++
if cn >= c.ipLimit {
limit = true
return false
}
return true
})
if limit {
return
}
ips.Store(ip, 1)
} else {
// have this ip
ips.Store(ip, online.(int)+1)
}
}
return false
}
func (c *ConnLimiter) DelConnCount(user string, ip string) {
if c.connLimit != 0 {
if v, ok := c.count.Load(user); ok {
if v.(int) == 1 {
c.count.Delete(user)
} else {
c.count.Store(user, v.(int)-1)
}
}
}
if c.ipLimit == 0 {
return
}
if i, ok := c.ip.Load(user); ok {
is := i.(*sync.Map)
if i, ok := is.Load(ip); ok {
if i.(int) == 1 {
is.Delete(ip)
} else {
is.Store(user, i.(int)-1)
}
notDel := false
c.ip.Range(func(_, _ any) bool {
notDel = true
return true
})
if !notDel {
c.ip.Delete(user)
}
}
}
}

38
limiter/conn_test.go Normal file
View File

@ -0,0 +1,38 @@
package limiter
import (
"sync"
"testing"
)
var c *ConnLimiter
func init() {
c = NewConnLimiter(1, 1)
}
func TestConnLimiter_AddConnCount(t *testing.T) {
t.Log(c.AddConnCount("1", "1"))
t.Log(c.AddConnCount("1", "2"))
}
func TestConnLimiter_DelConnCount(t *testing.T) {
t.Log(c.AddConnCount("1", "1"))
t.Log(c.AddConnCount("1", "2"))
c.DelConnCount("1", "1")
t.Log(c.AddConnCount("1", "2"))
}
func BenchmarkConnLimiter(b *testing.B) {
wg := sync.WaitGroup{}
for i := 0; i < b.N; i++ {
wg.Add(1)
go func() {
c.AddConnCount("1", "2")
c.DelConnCount("1", "2")
wg.Done()
}()
}
wg.Wait()
}

37
limiter/dynamic.go Normal file
View File

@ -0,0 +1,37 @@
package limiter
import (
"fmt"
"github.com/Yuzuki616/V2bX/api/panel"
"time"
)
func (l *Limiter) AddDynamicSpeedLimit(tag string, userInfo *panel.UserInfo, limitNum int, expire int64) error {
userLimit := &UserLimitInfo{
DynamicSpeedLimit: limitNum,
ExpireTime: time.Now().Add(time.Duration(expire) * time.Second).Unix(),
}
l.UserLimitInfo.Store(fmt.Sprintf("%s|%s|%d", tag, userInfo.Uuid, userInfo.Id), userLimit)
return nil
}
// determineSpeedLimit returns the minimum non-zero rate
func determineSpeedLimit(limit1, limit2 int) (limit int) {
if limit1 == 0 || limit2 == 0 {
if limit1 > limit2 {
return limit1
} else if limit1 < limit2 {
return limit2
} else {
return 0
}
} else {
if limit1 > limit2 {
return limit2
} else if limit1 < limit2 {
return limit1
} else {
return limit1
}
}
}

166
limiter/limiter.go Normal file
View File

@ -0,0 +1,166 @@
package limiter
import (
"errors"
"fmt"
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/juju/ratelimit"
"sync"
"time"
)
var limitLock sync.RWMutex
var limiter map[string]*Limiter
func Init() {
limiter = map[string]*Limiter{}
}
type Limiter struct {
Rules []panel.DestinationRule
ProtocolRules []string
SpeedLimit int
UserLimitInfo *sync.Map // Key: Uid value: UserLimitInfo
ConnLimiter *ConnLimiter // Key: Uid value: ConnLimiter
SpeedLimiter *sync.Map // key: Uid, value: *ratelimit.Bucket
}
type UserLimitInfo struct {
UID int
SpeedLimit int
DynamicSpeedLimit int
ExpireTime int64
}
type LimitConfig struct {
SpeedLimit int
IpLimit int
ConnLimit int
}
func AddLimiter(tag string, l *LimitConfig, users []panel.UserInfo) *Limiter {
info := &Limiter{
SpeedLimit: l.SpeedLimit,
UserLimitInfo: new(sync.Map),
ConnLimiter: NewConnLimiter(l.ConnLimit, l.IpLimit),
SpeedLimiter: new(sync.Map),
}
for i := range users {
if users[i].SpeedLimit != 0 {
userLimit := &UserLimitInfo{
UID: users[i].Id,
SpeedLimit: users[i].SpeedLimit,
ExpireTime: 0,
}
info.UserLimitInfo.Store(fmt.Sprintf("%s|%s|%d", tag, users[i].Uuid, users[i].Id), userLimit)
}
}
limitLock.Lock()
limiter[tag] = info
limitLock.Unlock()
return info
}
func GetLimiter(tag string) (info *Limiter, err error) {
limitLock.RLock()
info, ok := limiter[tag]
limitLock.RUnlock()
if !ok {
return nil, errors.New("not found")
}
return
}
func UpdateLimiter(tag string, added []panel.UserInfo, deleted []panel.UserInfo) error {
l, err := GetLimiter(tag)
if err != nil {
return fmt.Errorf("get limit error: %s", err)
}
for i := range deleted {
l.UserLimitInfo.Delete(fmt.Sprintf("%s|%s|%d",
tag,
deleted[i].Uuid,
deleted[i].Id))
}
for i := range added {
if added[i].SpeedLimit != 0 {
userLimit := &UserLimitInfo{
UID: added[i].Id,
SpeedLimit: added[i].SpeedLimit,
ExpireTime: 0,
}
l.UserLimitInfo.Store(fmt.Sprintf("%s|%s|%d",
tag,
added[i].Uuid,
added[i].Id), userLimit)
}
}
return nil
}
func DeleteLimiter(tag string) {
limitLock.Lock()
delete(limiter, tag)
limitLock.Unlock()
}
func (l *Limiter) CheckLimit(email string, ip string) (Bucket *ratelimit.Bucket, Reject bool) {
// ip and conn limiter
if l.ConnLimiter.AddConnCount(email, ip) {
return nil, true
}
// check and gen speed limit Bucket
nodeLimit := l.SpeedLimit
userLimit := 0
if v, ok := l.UserLimitInfo.Load(email); ok {
u := v.(*UserLimitInfo)
if u.ExpireTime < time.Now().Unix() && u.ExpireTime != 0 {
if u.SpeedLimit != 0 {
userLimit = u.SpeedLimit
u.DynamicSpeedLimit = 0
u.ExpireTime = 0
} else {
l.UserLimitInfo.Delete(email)
}
} else {
userLimit = determineSpeedLimit(u.SpeedLimit, u.DynamicSpeedLimit)
}
}
limit := int64(determineSpeedLimit(nodeLimit, userLimit)) * 1000000 / 8 // If you need the Speed limit
if limit > 0 {
Bucket = ratelimit.NewBucketWithQuantum(time.Second, limit, limit) // Byte/s
if v, ok := l.SpeedLimiter.LoadOrStore(email, Bucket); ok {
return v.(*ratelimit.Bucket), false
} else {
l.SpeedLimiter.Store(email, Bucket)
return Bucket, false
}
} else {
return nil, false
}
}
type UserIpList struct {
Uid int `json:"Uid"`
IpList []string `json:"Ips"`
}
func determineDeviceLimit(nodeLimit, userLimit int) (limit int) {
if nodeLimit == 0 || userLimit == 0 {
if nodeLimit > userLimit {
return nodeLimit
} else if nodeLimit < userLimit {
return userLimit
} else {
return 0
}
} else {
if nodeLimit > userLimit {
return userLimit
} else if nodeLimit < userLimit {
return nodeLimit
} else {
return nodeLimit
}
}
}

34
limiter/rule.go Normal file
View File

@ -0,0 +1,34 @@
package limiter
import (
"github.com/Yuzuki616/V2bX/api/panel"
"reflect"
)
func (l *Limiter) CheckDomainRule(destination string) (reject bool) {
// have rule
for i := range l.Rules {
if l.Rules[i].Pattern.MatchString(destination) {
reject = true
break
}
}
return
}
func (l *Limiter) CheckProtocolRule(protocol string) (reject bool) {
for i := range l.ProtocolRules {
if l.ProtocolRules[i] == protocol {
reject = true
break
}
}
return
}
func (l *Limiter) UpdateRule(newRuleList []panel.DestinationRule) error {
if !reflect.DeepEqual(l.Rules, newRuleList) {
l.Rules = newRuleList
}
return nil
}

1
limiter/task.go Normal file
View File

@ -0,0 +1 @@
package limiter

View File

@ -5,6 +5,7 @@ import (
"fmt"
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/core"
"github.com/Yuzuki616/V2bX/limiter"
"github.com/Yuzuki616/V2bX/node"
"log"
"os"
@ -40,6 +41,7 @@ func main() {
if err != nil {
log.Panicf("can't unmarshal config file: %s \n", err)
}
limiter.Init()
log.Println("Start V2bX...")
x := core.New(config)
err = x.Start()
@ -69,9 +71,9 @@ func main() {
log.Panicf("watch config file error: %s", err)
}
}
//Explicitly triggering GC to remove garbage from config loading.
// clear memory
runtime.GC()
// Running backend
// wait exit signal
{
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, syscall.SIGINT, syscall.SIGKILL, syscall.SIGTERM)

View File

@ -1,4 +1,4 @@
package controller
package node
import (
"errors"
@ -7,11 +7,12 @@ import (
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/core"
"github.com/Yuzuki616/V2bX/limiter"
"github.com/xtls/xray-core/common/task"
"log"
)
type Node struct {
type Controller struct {
server *core.Core
clientInfo panel.ClientInfo
apiClient panel.Panel
@ -27,9 +28,9 @@ type Node struct {
*conf.ControllerConfig
}
// New return a Node service with default parameters.
func New(server *core.Core, api panel.Panel, config *conf.ControllerConfig) *Node {
controller := &Node{
// NewController return a Node controller with default parameters.
func NewController(server *core.Core, api panel.Panel, config *conf.ControllerConfig) *Controller {
controller := &Controller{
server: server,
ControllerConfig: config,
apiClient: api,
@ -38,7 +39,7 @@ func New(server *core.Core, api panel.Panel, config *conf.ControllerConfig) *Nod
}
// Start implement the Start() function of the service interface
func (c *Node) Start() error {
func (c *Controller) Start() error {
c.clientInfo = c.apiClient.Describe()
// First fetch Node Info
var err error
@ -46,12 +47,6 @@ func (c *Node) Start() error {
if err != nil {
return fmt.Errorf("get node info failed: %s", err)
}
c.Tag = c.buildNodeTag()
// Add new tag
err = c.addNewTag(c.nodeInfo)
if err != nil {
return fmt.Errorf("add new tag failed: %s", err)
}
// Update user
c.userList, err = c.apiClient.GetUserList()
if err != nil {
@ -60,25 +55,36 @@ func (c *Node) Start() error {
if len(c.userList) == 0 {
return errors.New("add users failed: not have any user")
}
c.Tag = c.buildNodeTag()
// add limiter
l := limiter.AddLimiter(c.Tag, &limiter.LimitConfig{
SpeedLimit: c.SpeedLimit,
IpLimit: c.IPLimit,
ConnLimit: c.ConnLimit,
}, c.userList)
// add rule limiter
if !c.DisableGetRule {
if err = l.UpdateRule(c.nodeInfo.Rules); err != nil {
log.Printf("Update rule filed: %s", err)
}
}
// Add new tag
err = c.addNewNode(c.nodeInfo)
if err != nil {
return fmt.Errorf("add new tag failed: %s", err)
}
err = c.addNewUser(c.userList, c.nodeInfo)
if err != nil {
return err
}
if err := c.server.AddInboundLimiter(c.Tag, c.nodeInfo, c.userList); err != nil {
return fmt.Errorf("add inbound limiter failed: %s", err)
}
// Add Rule Manager
if !c.DisableGetRule {
if err := c.server.UpdateRule(c.Tag, c.nodeInfo.Rules); err != nil {
log.Printf("Update rule filed: %s", err)
}
}
c.initTask()
return nil
}
// Close implement the Close() function of the service interface
func (c *Node) Close() error {
func (c *Controller) Close() error {
limiter.DeleteLimiter(c.Tag)
if c.nodeInfoMonitorPeriodic != nil {
err := c.nodeInfoMonitorPeriodic.Close()
if err != nil {
@ -112,6 +118,6 @@ func (c *Node) Close() error {
return nil
}
func (c *Node) buildNodeTag() string {
func (c *Controller) buildNodeTag() string {
return fmt.Sprintf("%s_%s_%d", c.nodeInfo.NodeType, c.ListenIP, c.nodeInfo.NodeId)
}

View File

@ -1,4 +1,4 @@
package controller_test
package node_test
import (
"fmt"
@ -6,7 +6,7 @@ import (
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/core"
_ "github.com/Yuzuki616/V2bX/core/distro/all"
. "github.com/Yuzuki616/V2bX/node/controller"
. "github.com/Yuzuki616/V2bX/node"
xCore "github.com/xtls/xray-core/core"
coreConf "github.com/xtls/xray-core/infra/conf"
"os"

View File

@ -1,4 +1,4 @@
package controller
package node
import (
"crypto/rand"
@ -8,7 +8,7 @@ import (
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/common/file"
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/node/controller/lego"
"github.com/Yuzuki616/V2bX/node/lego"
"github.com/goccy/go-json"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/core"
@ -65,7 +65,7 @@ func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag s
AcceptProxyProtocol: config.EnableProxyProtocol} //Enable proxy protocol
}
// Set TLS and XTLS settings
if nodeInfo.EnableTls && config.CertConfig.CertMode != "none" {
if config.EnableTls && config.CertConfig.CertMode != "none" {
inbound.StreamSetting.Security = "tls"
certFile, keyFile, err := getCertFile(config.CertConfig)
if err != nil {
@ -91,7 +91,7 @@ func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag s
}
func buildV2ray(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, inbound *coreConf.InboundDetourConfig) error {
if nodeInfo.EnableVless {
if config.EnableVless {
//Set vless
inbound.Protocol = "vless"
if config.EnableFallback {

View File

@ -1,8 +1,8 @@
package controller_test
package node_test
import (
"github.com/Yuzuki616/V2bX/api/panel"
. "github.com/Yuzuki616/V2bX/node/controller"
. "github.com/Yuzuki616/V2bX/node"
"testing"
)

View File

@ -4,11 +4,10 @@ import (
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/core"
"github.com/Yuzuki616/V2bX/node/controller"
)
type Node struct {
controllers []*controller.Node
controllers []*Controller
}
func New() *Node {
@ -16,14 +15,14 @@ func New() *Node {
}
func (n *Node) Start(nodes []*conf.NodeConfig, core *core.Core) error {
n.controllers = make([]*controller.Node, len(nodes))
n.controllers = make([]*Controller, len(nodes))
for i, c := range nodes {
p, err := panel.New(c.ApiConfig)
if err != nil {
return err
}
// Register controller service
n.controllers[i] = controller.New(core, p, c.ControllerConfig)
n.controllers[i] = NewController(core, p, c.ControllerConfig)
err = n.controllers[i].Start()
if err != nil {
return err

View File

@ -1,4 +1,4 @@
package controller
package node
import (
"fmt"

View File

@ -1,10 +1,10 @@
package controller
package node
import (
"fmt"
"github.com/Yuzuki616/V2bX/api/iprecoder"
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/node/controller/lego"
"github.com/Yuzuki616/V2bX/limiter"
"github.com/Yuzuki616/V2bX/node/lego"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/task"
"log"
@ -13,7 +13,7 @@ import (
"time"
)
func (c *Node) initTask() {
func (c *Controller) initTask() {
// fetch node info task
c.nodeInfoMonitorPeriodic = &task.Periodic{
Interval: time.Duration(c.nodeInfo.BaseConfig.PullInterval.(int)) * time.Second,
@ -36,7 +36,7 @@ func (c *Node) initTask() {
time.Sleep(time.Duration(c.nodeInfo.BaseConfig.PushInterval.(int)) * time.Second)
_ = c.userReportPeriodic.Start()
}()
if c.nodeInfo.EnableTls && c.CertConfig.CertMode != "none" &&
if c.EnableTls && c.CertConfig.CertMode != "none" &&
(c.CertConfig.CertMode == "dns" || c.CertConfig.CertMode == "http") {
c.renewCertPeriodic = &task.Periodic{
Interval: time.Hour * 24,
@ -48,42 +48,9 @@ func (c *Node) initTask() {
_ = c.renewCertPeriodic.Start()
}()
}
if c.EnableDynamicSpeedLimit {
// Check dynamic speed limit task
c.dynamicSpeedLimitPeriodic = &task.Periodic{
Interval: time.Duration(c.DynamicSpeedLimitConfig.Periodic) * time.Second,
Execute: c.dynamicSpeedLimit,
}
go func() {
time.Sleep(time.Duration(c.DynamicSpeedLimitConfig.Periodic) * time.Second)
_ = c.dynamicSpeedLimitPeriodic.Start()
}()
log.Printf("[%s: %d] Start dynamic speed limit", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
}
if c.EnableIpRecorder {
switch c.IpRecorderConfig.Type {
case "Recorder":
c.ipRecorder = iprecoder.NewRecorder(c.IpRecorderConfig.RecorderConfig)
case "Redis":
c.ipRecorder = iprecoder.NewRedis(c.IpRecorderConfig.RedisConfig)
default:
log.Printf("recorder type: %s is not vail, disable recorder", c.IpRecorderConfig.Type)
return
}
// report and fetch online ip list task
c.onlineIpReportPeriodic = &task.Periodic{
Interval: time.Duration(c.IpRecorderConfig.Periodic) * time.Second,
Execute: c.reportOnlineIp,
}
go func() {
time.Sleep(time.Duration(c.IpRecorderConfig.Periodic) * time.Second)
_ = c.onlineIpReportPeriodic.Start()
}()
log.Printf("[%s: %d] Start report online ip", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
}
}
func (c *Node) nodeInfoMonitor() (err error) {
func (c *Controller) nodeInfoMonitor() (err error) {
// First fetch Node Info
newNodeInfo, err := c.apiClient.GetNodeInfo()
if err != nil {
@ -95,28 +62,22 @@ func (c *Node) nodeInfoMonitor() (err error) {
if newNodeInfo != nil {
// Remove old tag
oldTag := c.Tag
err := c.removeOldTag(oldTag)
err := c.removeOldNode(oldTag)
if err != nil {
log.Print(err)
return nil
}
// Remove Old limiter
limiter.DeleteLimiter(oldTag)
// Add new tag
c.nodeInfo = newNodeInfo
c.Tag = c.buildNodeTag()
err = c.addNewTag(newNodeInfo)
err = c.addNewNode(newNodeInfo)
if err != nil {
log.Print(err)
return nil
}
nodeInfoChanged = true
// Remove Old limiter
if err = c.server.DeleteInboundLimiter(oldTag); err != nil {
log.Print(err)
return nil
}
if err := c.server.UpdateRule(c.Tag, newNodeInfo.Rules); err != nil {
log.Print(err)
}
}
// Update User
newUserInfo, err := c.apiClient.GetUserList()
@ -126,15 +87,20 @@ func (c *Node) nodeInfoMonitor() (err error) {
}
if nodeInfoChanged {
c.userList = newUserInfo
err = c.addNewUser(c.userList, newNodeInfo)
// Add new Limiter
l := limiter.AddLimiter(c.Tag, &limiter.LimitConfig{
SpeedLimit: c.SpeedLimit,
IpLimit: c.IPLimit,
ConnLimit: c.ConnLimit,
}, newUserInfo)
err = c.addNewUser(newUserInfo, newNodeInfo)
if err != nil {
log.Print(err)
return nil
}
// Add Limiter
if err := c.server.AddInboundLimiter(c.Tag, newNodeInfo, newUserInfo); err != nil {
log.Print(err)
return nil
err = l.UpdateRule(newNodeInfo.Rules)
if err != nil {
log.Printf("Update Rule error: %s", err)
}
// Check interval
if c.nodeInfoMonitorPeriodic.Interval != time.Duration(newNodeInfo.BaseConfig.PullInterval.(int))*time.Second {
@ -176,8 +142,9 @@ func (c *Node) nodeInfoMonitor() (err error) {
}
if len(added) > 0 || len(deleted) > 0 {
// Update Limiter
if err := c.server.UpdateInboundLimiter(c.Tag, added, deleted); err != nil {
log.Print(err)
err = limiter.UpdateLimiter(c.Tag, added, deleted)
if err != nil {
log.Print("update limiter:", err)
}
}
log.Printf("[%s: %d] %d user deleted, %d user added", c.nodeInfo.NodeType, c.nodeInfo.NodeId,
@ -187,7 +154,7 @@ func (c *Node) nodeInfoMonitor() (err error) {
return nil
}
func (c *Node) removeOldTag(oldTag string) (err error) {
func (c *Controller) removeOldNode(oldTag string) (err error) {
err = c.server.RemoveInbound(oldTag)
if err != nil {
return err
@ -199,7 +166,7 @@ func (c *Node) removeOldTag(oldTag string) (err error) {
return nil
}
func (c *Node) addNewTag(newNodeInfo *panel.NodeInfo) (err error) {
func (c *Controller) addNewNode(newNodeInfo *panel.NodeInfo) (err error) {
inboundConfig, err := buildInbound(c.ControllerConfig, newNodeInfo, c.Tag)
if err != nil {
return fmt.Errorf("build inbound error: %s", err)
@ -219,10 +186,10 @@ func (c *Node) addNewTag(newNodeInfo *panel.NodeInfo) (err error) {
return nil
}
func (c *Node) addNewUser(userInfo []panel.UserInfo, nodeInfo *panel.NodeInfo) (err error) {
func (c *Controller) addNewUser(userInfo []panel.UserInfo, nodeInfo *panel.NodeInfo) (err error) {
users := make([]*protocol.User, 0)
if nodeInfo.NodeType == "V2ray" {
if nodeInfo.EnableVless {
if c.EnableVless {
users = c.buildVlessUsers(userInfo)
} else {
users = c.buildVmessUsers(userInfo)
@ -270,7 +237,7 @@ func compareUserList(old, new []panel.UserInfo) (deleted, added []panel.UserInfo
return deleted, added
}
func (c *Node) reportUserTraffic() (err error) {
func (c *Controller) reportUserTraffic() (err error) {
// Get User traffic
userTraffic := make([]panel.UserTraffic, 0)
for i := range c.userList {
@ -294,52 +261,11 @@ func (c *Node) reportUserTraffic() (err error) {
}
}
userTraffic = nil
if !c.EnableIpRecorder {
c.server.ClearOnlineIp(c.Tag)
}
runtime.GC()
return nil
}
func (c *Node) reportOnlineIp() (err error) {
onlineIp, err := c.server.ListOnlineIp(c.Tag)
if err != nil {
log.Print(err)
return nil
}
onlineIp, err = c.ipRecorder.SyncOnlineIp(onlineIp)
if err != nil {
log.Print("Report online ip error: ", err)
c.server.ClearOnlineIp(c.Tag)
}
if c.IpRecorderConfig.EnableIpSync {
c.server.UpdateOnlineIp(c.Tag, onlineIp)
log.Printf("[Node: %d] Updated %d online ip", c.nodeInfo.NodeId, len(onlineIp))
}
log.Printf("[Node: %d] Report %d online ip", c.nodeInfo.NodeId, len(onlineIp))
return nil
}
func (c *Node) dynamicSpeedLimit() error {
if c.EnableDynamicSpeedLimit {
for i := range c.userList {
up, down := c.server.GetUserTraffic(c.buildUserTag(&(c.userList)[i]), false)
if c.userList[i].Traffic+down+up/1024/1024 > c.DynamicSpeedLimitConfig.Traffic {
err := c.server.AddUserSpeedLimit(c.Tag,
&c.userList[i],
c.DynamicSpeedLimitConfig.SpeedLimit,
time.Now().Add(time.Second*time.Duration(c.DynamicSpeedLimitConfig.ExpireTime)).Unix())
if err != nil {
log.Print(err)
}
}
c.userList[i].Traffic = 0
}
}
return nil
}
func (c *Node) RenewCert() {
func (c *Controller) RenewCert() {
l, err := lego.New(c.CertConfig)
if err != nil {
log.Print(err)

View File

@ -1,4 +1,4 @@
package controller
package node
import (
"encoding/base64"
@ -14,7 +14,7 @@ import (
"strings"
)
func (c *Node) buildVmessUsers(userInfo []panel.UserInfo) (users []*protocol.User) {
func (c *Controller) buildVmessUsers(userInfo []panel.UserInfo) (users []*protocol.User) {
users = make([]*protocol.User, len(userInfo))
for i, user := range userInfo {
users[i] = c.buildVmessUser(&user, 0)
@ -22,7 +22,7 @@ func (c *Node) buildVmessUsers(userInfo []panel.UserInfo) (users []*protocol.Use
return users
}
func (c *Node) buildVmessUser(userInfo *panel.UserInfo, serverAlterID uint16) (user *protocol.User) {
func (c *Controller) buildVmessUser(userInfo *panel.UserInfo, serverAlterID uint16) (user *protocol.User) {
vmessAccount := &conf.VMessAccount{
ID: userInfo.Uuid,
AlterIds: serverAlterID,
@ -35,7 +35,7 @@ func (c *Node) buildVmessUser(userInfo *panel.UserInfo, serverAlterID uint16) (u
}
}
func (c *Node) buildVlessUsers(userInfo []panel.UserInfo) (users []*protocol.User) {
func (c *Controller) buildVlessUsers(userInfo []panel.UserInfo) (users []*protocol.User) {
users = make([]*protocol.User, len(userInfo))
for i := range userInfo {
users[i] = c.buildVlessUser(&(userInfo)[i])
@ -43,7 +43,7 @@ func (c *Node) buildVlessUsers(userInfo []panel.UserInfo) (users []*protocol.Use
return users
}
func (c *Node) buildVlessUser(userInfo *panel.UserInfo) (user *protocol.User) {
func (c *Controller) buildVlessUser(userInfo *panel.UserInfo) (user *protocol.User) {
vlessAccount := &vless.Account{
Id: userInfo.Uuid,
Flow: "xtls-rprx-direct",
@ -55,7 +55,7 @@ func (c *Node) buildVlessUser(userInfo *panel.UserInfo) (user *protocol.User) {
}
}
func (c *Node) buildTrojanUsers(userInfo []panel.UserInfo) (users []*protocol.User) {
func (c *Controller) buildTrojanUsers(userInfo []panel.UserInfo) (users []*protocol.User) {
users = make([]*protocol.User, len(userInfo))
for i := range userInfo {
users[i] = c.buildTrojanUser(&(userInfo)[i])
@ -63,7 +63,7 @@ func (c *Node) buildTrojanUsers(userInfo []panel.UserInfo) (users []*protocol.Us
return users
}
func (c *Node) buildTrojanUser(userInfo *panel.UserInfo) (user *protocol.User) {
func (c *Controller) buildTrojanUser(userInfo *panel.UserInfo) (user *protocol.User) {
trojanAccount := &trojan.Account{
Password: userInfo.Uuid,
Flow: "xtls-rprx-direct",
@ -90,7 +90,7 @@ func getCipherFromString(c string) shadowsocks.CipherType {
}
}
func (c *Node) buildSSUsers(userInfo []panel.UserInfo, cypher shadowsocks.CipherType) (users []*protocol.User) {
func (c *Controller) buildSSUsers(userInfo []panel.UserInfo, cypher shadowsocks.CipherType) (users []*protocol.User) {
users = make([]*protocol.User, len(userInfo))
for i := range userInfo {
users[i] = c.buildSSUser(&(userInfo)[i], cypher)
@ -98,7 +98,7 @@ func (c *Node) buildSSUsers(userInfo []panel.UserInfo, cypher shadowsocks.Cipher
return users
}
func (c *Node) buildSSUser(userInfo *panel.UserInfo, cypher shadowsocks.CipherType) (user *protocol.User) {
func (c *Controller) buildSSUser(userInfo *panel.UserInfo, cypher shadowsocks.CipherType) (user *protocol.User) {
if c.nodeInfo.ServerKey == "" {
ssAccount := &shadowsocks.Account{
Password: userInfo.Uuid,
@ -121,6 +121,6 @@ func (c *Node) buildSSUser(userInfo *panel.UserInfo, cypher shadowsocks.CipherTy
}
}
func (c *Node) buildUserTag(user *panel.UserInfo) string {
func (c *Controller) buildUserTag(user *panel.UserInfo) string {
return fmt.Sprintf("%s|%s|%d", c.Tag, user.Uuid, user.Id)
}