diff --git a/cli/main.go b/cli/main.go new file mode 100644 index 0000000..648ed51 --- /dev/null +++ b/cli/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "context" + "fmt" + "time" + + "google.golang.org/grpc" + + v2scar "github.com/Ehco1996/v2scar" +) + +func main() { + + up := v2scar.NewUserPool() + + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + conn, _ := grpc.DialContext(ctx, "127.0.0.1:8080", grpc.WithInsecure(), grpc.WithBlock()) + defer conn.Close() + + v2scar.GetAndResetUserTraffic(ctx, conn, up) + + for _, user := range up.GetAllUsers() { + fmt.Println(user) + + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f7860e8 --- /dev/null +++ b/go.mod @@ -0,0 +1,6 @@ +module github.com/Ehco1996/v2scar + +require ( + google.golang.org/grpc v1.23.0 + v2ray.com/core v4.20.0+incompatible +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..37f6926 --- /dev/null +++ b/go.sum @@ -0,0 +1,49 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.1-0.20190205222052-c823c79ea157 h1:SdQMHsZ18/XZCHuwt3IF+dvHgYTO2XMWZjv3XBKQqAI= +github.com/golang/protobuf v1.2.1-0.20190205222052-c823c79ea157/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +go.starlark.net v0.0.0-20190225160109-1174b2613e82/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= +golang.org/x/crypto v0.0.0-20190208162236-193df9c0f06f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +h12.io/socks v1.0.0/go.mod h1:MdYbo5/eB9ka7u5dzW2Qh0iSyJENwB3KI5H5ngenFGA= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +v2ray.com/core v4.15.0+incompatible h1:my1X3WD45iZIXPTz1nse9YBkZxuJzhO9F8bkPPBhO74= +v2ray.com/core v4.15.0+incompatible/go.mod h1:kCjxLuEer3COnk8YSZBx9xHnqadF1zMlaaZaKIgdEB0= +v2ray.com/core v4.20.0+incompatible h1:CMwF0n0z9Ta/nDNYuOq2LuL/WqvOapDpZzMgrFg9MSI= +v2ray.com/core v4.20.0+incompatible/go.mod h1:0yC/gG6Jd6qgp9PuaXuFE9uOi1xVYoDPexD1nlFms/w= diff --git a/services.go b/services.go new file mode 100644 index 0000000..c0b04c8 --- /dev/null +++ b/services.go @@ -0,0 +1,37 @@ +package v2scar + +import ( + "context" + "strings" + + "google.golang.org/grpc" + v2stats "v2ray.com/core/app/stats/command" +) + +// GetAndResetUserTraffic 统计所有user的上行下行流量 +// V2ray的stats的统计模块设计的非常奇怪,具体规则如下 +// 上传流量:"user>>>" + user.Email + ">>>traffic>>>uplink" +// 下载流量:"user>>>" + user.Email + ">>>traffic>>>downlink" +func GetAndResetUserTraffic(ctx context.Context, conn *grpc.ClientConn, up *UserPool) { + client := v2stats.NewStatsServiceClient(conn) + req := &v2stats.QueryStatsRequest{ + Pattern: "user>>>", + Reset_: true, + } + resp, _ := client.QueryStats(ctx, req) + for _, stat := range resp.Stat { + email, trafficType := getEmailAndTrafficType(stat.Name) + user := up.GetOrCreateUser(email) + switch trafficType { + case "uplink": + user.setUploadTraffic(stat.Value) + case "downlink": + user.setDownloadTraffic(stat.Value) + } + } +} + +func getEmailAndTrafficType(input string) (string, string) { + s := strings.Split(input, ">>>") + return s[1], s[len(s)-1] +} diff --git a/user.go b/user.go new file mode 100644 index 0000000..2167d23 --- /dev/null +++ b/user.go @@ -0,0 +1,62 @@ +package v2scar + +import ( + "sync" + "sync/atomic" +) + +// User V2ray User +type User struct { + Email string `json:"_"` + UploadTraffic int64 `json:"upload_traffic"` + DownloadTraffic int64 `json:"download_traffic"` +} + +func (u *User) setUploadTraffic(ut int64) { + atomic.StoreInt64(&u.UploadTraffic, ut) +} + +func (u *User) setDownloadTraffic(dt int64) { + atomic.StoreInt64(&u.DownloadTraffic, dt) +} + +// UserPool user pool +type UserPool struct { + access sync.RWMutex + users map[string]*User +} + +// NewUserPool New UserPool +func NewUserPool() *UserPool { + // map key : email + return &UserPool{ + users: make(map[string]*User), + } +} + +// GetOrCreateUser GetOrCreateUser +func (up *UserPool) GetOrCreateUser(email string) *User { + up.access.Lock() + defer up.access.Unlock() + + if user, found := up.users[email]; found { + return user + } + user := &User{ + Email: email, + } + up.users[user.Email] = user + return user +} + +// GetAllUsers GetAllUsers +func (up *UserPool) GetAllUsers() []*User { + up.access.Lock() + defer up.access.Unlock() + + users := make([]*User, 0, len(up.users)) + for _, user := range up.users { + users = append(users, user) + } + return users +}