diff --git a/api/gov2panel/gov2panel.go b/api/gov2panel/gov2panel.go index 7c9d6d3c..152d9ffe 100644 --- a/api/gov2panel/gov2panel.go +++ b/api/gov2panel/gov2panel.go @@ -2,92 +2,71 @@ package gov2panel import ( "bufio" - "encoding/json" + "context" "errors" "fmt" + "log" "os" "regexp" - "strconv" - "strings" - "sync/atomic" "time" - log "github.com/sirupsen/logrus" - - "github.com/bitly/go-simplejson" - "github.com/go-resty/resty/v2" - "github.com/gogf/gf/v2/util/gconv" + "github.com/XrayR-project/XrayR/api" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gclient" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/infra/conf" - - "github.com/XrayR-project/XrayR/api" ) -// APIClient create an api client to the panel. +// APIClient API config type APIClient struct { - client *resty.Client - APIHost string - NodeID int - Key string - NodeType string - EnableVless bool - VlessFlow string - SpeedLimit float64 - DeviceLimit int + ctx context.Context + APIHost string + NodeID int + Key string + NodeType string + EnableVless bool + VlessFlow string + Timeout int + SpeedLimit float64 + DeviceLimit int + RuleListPath string + DisableCustomConfig bool + LocalRuleList []api.DetectRule - resp atomic.Value - eTags map[string]string } // New create an api instance func New(apiConfig *api.Config) *APIClient { - client := resty.New() - client.SetRetryCount(3) - if apiConfig.Timeout > 0 { - client.SetTimeout(time.Duration(apiConfig.Timeout) * time.Second) - } else { - client.SetTimeout(5 * time.Second) - } - client.OnError(func(req *resty.Request, err error) { - if v, ok := err.(*resty.ResponseError); ok { - // v.Response contains the last response from the server - // v.Err contains the original error - log.Print(v.Err) - } - }) - client.SetBaseURL(apiConfig.APIHost) - // Create Key for each requests - client.SetQueryParams(map[string]string{ - "node_id": strconv.Itoa(apiConfig.NodeID), - "node_type": strings.ToLower(apiConfig.NodeType), - "token": apiConfig.Key, - }) - // Read local rule list - localRuleList := readLocalRuleList(apiConfig.RuleListPath) + + //https://goframe.org/pages/viewpage.action?pageId=1114381 + apiClient := &APIClient{ - client: client, - NodeID: apiConfig.NodeID, - Key: apiConfig.Key, - APIHost: apiConfig.APIHost, - NodeType: apiConfig.NodeType, - EnableVless: apiConfig.EnableVless, - VlessFlow: apiConfig.VlessFlow, - SpeedLimit: apiConfig.SpeedLimit, - DeviceLimit: apiConfig.DeviceLimit, - LocalRuleList: localRuleList, - eTags: make(map[string]string), + ctx: context.Background(), + APIHost: apiConfig.APIHost, + NodeID: apiConfig.NodeID, + Key: apiConfig.Key, + NodeType: apiConfig.NodeType, + EnableVless: apiConfig.EnableVless, + VlessFlow: apiConfig.VlessFlow, + Timeout: apiConfig.Timeout, + DeviceLimit: apiConfig.DeviceLimit, + RuleListPath: apiConfig.RuleListPath, + DisableCustomConfig: apiConfig.DisableCustomConfig, + + LocalRuleList: readLocalRuleList(apiConfig.RuleListPath), //加载本地路由规则 } return apiClient } // readLocalRuleList reads the local rule list file func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) { - LocalRuleList = make([]api.DetectRule, 0) + LocalRuleList = make([]api.DetectRule, 0) if path != "" { // open the file file, err := os.Open(path) - defer file.Close() + // handle errors while opening if err != nil { log.Printf("Error when opening file: %s", err) @@ -108,96 +87,77 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) { log.Fatalf("Error while reading file: %s", err) return } + + file.Close() } return LocalRuleList } -// Describe return a description of the client -func (c *APIClient) Describe() api.ClientInfo { - return api.ClientInfo{APIHost: c.APIHost, NodeID: c.NodeID, Key: c.Key, NodeType: c.NodeType} -} - -// Debug set the client debug for client -func (c *APIClient) Debug() { - c.client.SetDebug(true) -} - -func (c *APIClient) assembleURL(path string) string { - return c.APIHost + path -} +func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) { -func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (*simplejson.Json, error) { + apiPath := "/api/server/config" + reslutJson, err := c.sendRequest( + nil, + "POST", + apiPath, + g.Map{}) if err != nil { - return nil, fmt.Errorf("request %s failed: %v", c.assembleURL(path), err) + return nil, err } - if res.StatusCode() > 399 { - return nil, fmt.Errorf("request %s failed: %s, %v", c.assembleURL(path), res.String(), err) + if reslutJson.Get("data").String() == "" { + return nil, errors.New("gov2panel node config data is null") } - rtn, err := simplejson.NewJson(res.Body()) - if err != nil { - return nil, fmt.Errorf("ret %s invalid", res.String()) + if reslutJson.Get("data.port").Int() == 0 { + return nil, errors.New("server port must > 0") } - return rtn, nil -} - -// GetNodeInfo will pull NodeInfo Config from panel -func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) { - server := new(serverConfig) - path := "/api/server/config" - - res, err := c.client.R(). - SetHeader("If-None-Match", c.eTags["node"]). - ForceContentType("application/json"). - Get(path) - - // Etag identifier for a specific version of a resource. StatusCode = 304 means no changed - if res.StatusCode() == 304 { - return nil, errors.New(api.NodeNotModified) - } - // update etag - if res.Header().Get("Etag") != "" && res.Header().Get("Etag") != c.eTags["node"] { - c.eTags["node"] = res.Header().Get("Etag") + nodeInfo = new(api.NodeInfo) + err = reslutJson.Get("data").Scan(nodeInfo) + if err != nil { + return nil, fmt.Errorf("parse node info failed: \nError: %v", err) } - nodeInfoResp, err := c.parseResponse(res, path, err) + routes := make([]route, 0) + err = reslutJson.Get("data.routes").Scan(&routes) if err != nil { - return nil, err + return nil, fmt.Errorf("parse node routes failed: \nError: %v", err) } - b, _ := nodeInfoResp.Encode() - json.Unmarshal(b, server) - if gconv.Uint32(server.Port) == 0 { - return nil, errors.New("server port must > 0") - } + nodeInfo.NodeType = c.NodeType + nodeInfo.NodeID = c.NodeID + nodeInfo.EnableVless = c.EnableVless + nodeInfo.VlessFlow = c.VlessFlow - c.resp.Store(server) + nodeInfo.AlterID = 0 - switch c.NodeType { - case "V2ray", "Vmess", "Vless": - nodeInfo, err = c.parseV2rayNodeResponse(server) - case "Trojan": - nodeInfo, err = c.parseTrojanNodeResponse(server) - case "Shadowsocks": - nodeInfo, err = c.parseSSNodeResponse(server) - default: - return nil, fmt.Errorf("unsupported node type: %s", c.NodeType) - } + nodeInfo.NameServerConfig = parseDNSConfig(routes) - if err != nil { - return nil, fmt.Errorf("parse node info failed: %s, \nError: %v", res.String(), err) + return nodeInfo, nil + +} + +func parseDNSConfig(routes []route) (nameServerList []*conf.NameServerConfig) { + + nameServerList = make([]*conf.NameServerConfig, 0) + for i := range routes { + if routes[i].Action == "dns" { + nameServerList = append(nameServerList, &conf.NameServerConfig{ + Address: &conf.Address{Address: net.ParseAddress(routes[i].ActionValue)}, + Domains: routes[i].Match, + }) + } } - return nodeInfo, nil + return } // GetUserList will pull user form panel func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) { - var users []*user - path := "/api/server/user" + + apiPath := "/api/server/user" switch c.NodeType { case "V2ray", "Trojan", "Shadowsocks", "Vmess", "Vless": @@ -206,29 +166,17 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) { return nil, fmt.Errorf("unsupported node type: %s", c.NodeType) } - res, err := c.client.R(). - SetHeader("If-None-Match", c.eTags["users"]). - ForceContentType("application/json"). - Get(path) - - // Etag identifier for a specific version of a resource. StatusCode = 304 means no changed - if res.StatusCode() == 304 { - return nil, errors.New(api.UserNotModified) - } - // update etag - if res.Header().Get("Etag") != "" && res.Header().Get("Etag") != c.eTags["users"] { - c.eTags["users"] = res.Header().Get("Etag") - } - - usersResp, err := c.parseResponse(res, path, err) + reslutJson, err := c.sendRequest( + nil, + "GET", + apiPath, + g.Map{}) if err != nil { return nil, err } - b, _ := usersResp.Get("users").Encode() - json.Unmarshal(b, &users) - if len(users) == 0 { - return nil, errors.New("users is null") - } + + var users []*user + reslutJson.Get("data.users").Scan(&users) userList := make([]api.UserInfo, len(users)) for i := 0; i < len(users); i++ { @@ -255,148 +203,126 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) { return &userList, nil } -// ReportUserTraffic reports the user traffic -func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error { - path := "/api/server/push" +func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) { + return +} + +func (c *APIClient) ReportNodeOnlineUsers(onlineUser *[]api.OnlineUser) (err error) { + return +} - res, err := c.client.R().SetBody(userTraffic).ForceContentType("application/json").Post(path) - _, err = c.parseResponse(res, path, err) +// ReportUserTraffic reports the user traffic +func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) (err error) { + apiPath := "/api/server/push" + reslutJson, err := c.sendRequest( + nil, + "POST", + apiPath, + g.Map{ + "data": userTraffic, + }) if err != nil { return err } - return nil + if reslutJson.Get("code").Int() != 0 { + return errors.New(reslutJson.Get("message").String()) + } + + return +} + +func (c *APIClient) Describe() api.ClientInfo { + return api.ClientInfo{APIHost: c.APIHost, NodeID: c.NodeID, Key: c.Key, NodeType: c.NodeType} } // GetNodeRule implements the API interface func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) { - routes := c.resp.Load().(*serverConfig).Routes - ruleList := c.LocalRuleList + apiPath := "/api/server/config" + reslutJson, err := c.sendRequest( + nil, + "POST", + apiPath, + g.Map{}) + if err != nil { + return nil, err + } + + routes := make([]route, 0) + err = reslutJson.Get("data.routes").Scan(&routes) + if err != nil { + return nil, fmt.Errorf("parse node routes failed: \nError: %v", err) + } + for i := range routes { if routes[i].Action == "block" { + for _, v := range routes[i].Match { + ruleList = append(ruleList, api.DetectRule{ + ID: i, + Pattern: regexp.MustCompile(v), + }) + } - ruleList = append(ruleList, api.DetectRule{ - ID: i, - Pattern: regexp.MustCompile(strings.Join(routes[i].Match, "|")), - }) } } return &ruleList, nil -} -// ReportNodeStatus implements the API interface -func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) { - return nil } -// ReportNodeOnlineUsers implements the API interface -func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error { - return nil +func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) (err error) { + return } -// ReportIllegal implements the API interface -func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error { - return nil -} +func (c *APIClient) Debug() { -// parseTrojanNodeResponse parse the response for the given nodeInfo format -func (c *APIClient) parseTrojanNodeResponse(s *serverConfig) (*api.NodeInfo, error) { - // Create GeneralNodeInfo - nodeInfo := &api.NodeInfo{ - NodeType: c.NodeType, - NodeID: c.NodeID, - Port: gconv.Uint32(s.Port), - TransportProtocol: "tcp", - EnableTLS: true, - Host: s.Host, - ServiceName: s.Sni, - NameServerConfig: s.parseDNSConfig(), - } - return nodeInfo, nil } -// parseSSNodeResponse parse the response for the given nodeInfo format -func (c *APIClient) parseSSNodeResponse(s *serverConfig) (*api.NodeInfo, error) { - var header json.RawMessage - - if s.Obfs == "http" { - path := "/" - if p := s.ObfsSettings.Path; p != "" { - if strings.HasPrefix(p, "/") { - path = p - } else { - path += p - } - } - h := simplejson.New() - h.Set("type", "http") - h.SetPath([]string{"request", "path"}, path) - header, _ = h.Encode() - } - // Create GeneralNodeInfo - return &api.NodeInfo{ - NodeType: c.NodeType, - NodeID: c.NodeID, - Port: gconv.Uint32(s.Port), - TransportProtocol: "tcp", - CypherMethod: s.Encryption, - ServerKey: s.ServerKey, // shadowsocks2022 share key - NameServerConfig: s.parseDNSConfig(), - Header: header, - }, nil -} +// request 统一请求接口 +func (c *APIClient) sendRequest(headerM map[string]string, method string, url string, data g.Map) (reslutJson *gjson.Json, err error) { + url = c.APIHost + url -// parseV2rayNodeResponse parse the response for the given nodeInfo format -func (c *APIClient) parseV2rayNodeResponse(s *serverConfig) (*api.NodeInfo, error) { - var ( - header json.RawMessage - enableTLS bool - ) - - switch s.Net { - case "tcp": - if s.Header != nil { - if httpHeader, err := s.Header.MarshalJSON(); err != nil { - return nil, err - } else { - header = httpHeader - } - } + client := gclient.New() + + var gResponse *gclient.Response + + if c.Timeout > 0 { + client.SetTimeout(time.Duration(c.Timeout) * time.Second) //方法用于设置当前请求超时时间 + } else { + client.SetTimeout(5 * time.Second) } + client.Retry(3, 10*time.Second) //方法用于设置请求失败时重连次数和重连间隔。 - if s.TLS == "tls" { - enableTLS = true + client.SetHeaderMap(headerM) + client.SetHeader("Content-Type", "application/json") + + data["token"] = c.Key + data["node_id"] = c.NodeID + + switch method { + case "GET": + gResponse, err = client.Get(c.ctx, url, data) + case "POST": + gResponse, err = client.Post(c.ctx, url, data) + default: + err = fmt.Errorf("unsupported method: %s", method) + return } - // Create GeneralNodeInfo - return &api.NodeInfo{ - NodeType: c.NodeType, - NodeID: c.NodeID, - Port: gconv.Uint32(s.Port), - AlterID: 0, - TransportProtocol: s.Net, - EnableTLS: enableTLS, - Path: s.Path, - Host: s.Host, - EnableVless: c.EnableVless, - VlessFlow: c.VlessFlow, - ServiceName: s.Sni, - Header: header, - NameServerConfig: s.parseDNSConfig(), - }, nil -} + if err != nil { + return + } + defer gResponse.Close() -func (s *serverConfig) parseDNSConfig() (nameServerList []*conf.NameServerConfig) { - for i := range s.Routes { - if s.Routes[i].Action == "dns" { - nameServerList = append(nameServerList, &conf.NameServerConfig{ - Address: &conf.Address{Address: net.ParseAddress(s.Routes[i].ActionValue)}, - Domains: s.Routes[i].Match, - }) - } + reslutJson = gjson.New(gResponse.ReadAllString()) + if reslutJson == nil { + err = fmt.Errorf("http reslut to json, err : %s", gResponse.ReadAllString()) + } + if reslutJson.Get("code").Int() != 0 { + err = errors.New(reslutJson.Get("message").String()) + return } return diff --git a/api/gov2panel/gov2panel_test.go b/api/gov2panel/gov2panel_test.go index 9508e530..ceb3c135 100644 --- a/api/gov2panel/gov2panel_test.go +++ b/api/gov2panel/gov2panel_test.go @@ -5,56 +5,31 @@ import ( "github.com/XrayR-project/XrayR/api" "github.com/XrayR-project/XrayR/api/gov2panel" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/util/gconv" ) func CreateClient() api.API { apiConfig := &api.Config{ APIHost: "http://localhost:8080", Key: "123456", - NodeID: 1, + NodeID: 90, NodeType: "V2ray", } client := gov2panel.New(apiConfig) return client } -func TestGetV2rayNodeInfo(t *testing.T) { +func TestGetNodeInfo(t *testing.T) { client := CreateClient() nodeInfo, err := client.GetNodeInfo() if err != nil { t.Error(err) } - t.Log(nodeInfo) -} -func TestGetSSNodeInfo(t *testing.T) { - apiConfig := &api.Config{ - APIHost: "http://127.0.0.1:668", - Key: "qwertyuiopasdfghjkl", - NodeID: 1, - NodeType: "Shadowsocks", - } - client := gov2panel.New(apiConfig) - nodeInfo, err := client.GetNodeInfo() - if err != nil { - t.Error(err) - } - t.Log(nodeInfo) -} - -func TestGetTrojanNodeInfo(t *testing.T) { - apiConfig := &api.Config{ - APIHost: "http://127.0.0.1:668", - Key: "qwertyuiopasdfghjkl", - NodeID: 1, - NodeType: "Trojan", - } - client := gov2panel.New(apiConfig) - nodeInfo, err := client.GetNodeInfo() - if err != nil { - t.Error(err) - } - t.Log(nodeInfo) + nodeInfoJson := gjson.New(nodeInfo) + t.Log(nodeInfoJson.String()) + t.Log(nodeInfoJson.String()) } func TestGetUserList(t *testing.T) { @@ -65,6 +40,7 @@ func TestGetUserList(t *testing.T) { t.Error(err) } + t.Log(len(*userList)) t.Log(userList) } @@ -74,24 +50,30 @@ func TestReportReportUserTraffic(t *testing.T) { if err != nil { t.Error(err) } + t.Log(userList) generalUserTraffic := make([]api.UserTraffic, len(*userList)) for i, userInfo := range *userList { generalUserTraffic[i] = api.UserTraffic{ UID: userInfo.UID, - Upload: 1111, - Download: 2222, + Upload: 1073741824, + Download: 1073741824, } } - // client.Debug() + + t.Log(gconv.String(generalUserTraffic)) + client = CreateClient() err = client.ReportUserTraffic(&generalUserTraffic) if err != nil { t.Error(err) } + t.Error(err) } func TestGetNodeRule(t *testing.T) { + client := CreateClient() client.Debug() + ruleList, err := client.GetNodeRule() if err != nil { t.Error(err) diff --git a/api/gov2panel/model.go b/api/gov2panel/model.go index a20b33c6..7149825a 100644 --- a/api/gov2panel/model.go +++ b/api/gov2panel/model.go @@ -1,35 +1,9 @@ package gov2panel -import "encoding/json" - -type serverConfig struct { - v2ray - shadowsocks - //--- - Routes []route `json:"routes"` - Header *json.RawMessage `json:"header"` -} - -type v2ray struct { - Port string `json:"port"` - Scy string `json:"scy"` - Net string `json:"net"` - Type string `json:"type"` - Host string `json:"host"` - Path string `json:"path"` - TLS string `json:"tls"` - Sni string `json:"sni"` - Alpn string `json:"alpn"` -} - -type shadowsocks struct { - Encryption string `json:"encryption"` - Obfs string `json:"obfs"` - ObfsSettings struct { - Path string `json:"path"` - Host string `json:"host"` - } `json:"obfs_settings"` - ServerKey string `json:"server_key"` +type user struct { + Id int `json:"id"` + Uuid string `json:"uuid"` + SpeedLimit int `json:"speed_limit"` } type route struct { @@ -38,9 +12,3 @@ type route struct { Action string `json:"action"` ActionValue string `json:"action_value"` } - -type user struct { - Id int `json:"id"` - Uuid string `json:"uuid"` - SpeedLimit int `json:"speed_limit"` -} diff --git a/go.mod b/go.mod index 817ec737..c6484b72 100644 --- a/go.mod +++ b/go.mod @@ -85,6 +85,7 @@ require ( github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/civo/civogo v0.3.63 // indirect + github.com/clbanning/mxj/v2 v2.7.0 // indirect github.com/cloudflare/circl v1.3.9 // indirect github.com/cloudflare/cloudflare-go v0.90.0 // indirect github.com/cpu/goacmedns v0.1.1 // indirect @@ -95,6 +96,7 @@ require ( github.com/dimchansky/utfbom v1.1.1 // indirect github.com/dnsimple/dnsimple-go v1.7.0 // indirect github.com/exoscale/egoscale v1.19.0 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/flosch/pongo2/v4 v4.0.2 // indirect @@ -135,6 +137,7 @@ require ( github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/grokify/html-strip-tags-go v0.0.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -173,6 +176,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/microcosm-cc/bluemonday v1.0.26 // indirect github.com/miekg/dns v1.1.61 // indirect github.com/mimuret/golang-iij-dpf v0.9.1 // indirect @@ -192,6 +196,7 @@ require ( github.com/nrdcg/nodion v0.1.0 // indirect github.com/nrdcg/porkbun v0.3.0 // indirect github.com/nzdjb/go-metaname v1.0.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onsi/ginkgo/v2 v2.19.0 // indirect github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect @@ -212,6 +217,7 @@ require ( github.com/quic-go/quic-go v0.45.1 // indirect github.com/refraction-networking/utls v1.6.7 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sacloud/api-client-go v0.2.10 // indirect github.com/sacloud/go-http v0.1.8 // indirect