fff
This commit is contained in:
@@ -1003,6 +1003,81 @@ func (s *Service) setupNode() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizePanelNodeType(nodeType string) string {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(nodeType)) {
|
||||||
|
case "v2ray":
|
||||||
|
return "vmess"
|
||||||
|
case "hysteria2":
|
||||||
|
return "hysteria"
|
||||||
|
default:
|
||||||
|
return strings.ToLower(strings.TrimSpace(nodeType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) panelRequest(method string, baseURL string, endpoint string, nodeID int, payload []byte, contentType string) (http.Header, []byte, int, error) {
|
||||||
|
nodeType := normalizePanelNodeType(s.options.NodeType)
|
||||||
|
nodeTypeCandidates := []string{nodeType}
|
||||||
|
if nodeType != "" {
|
||||||
|
nodeTypeCandidates = append(nodeTypeCandidates, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastHeader http.Header
|
||||||
|
var lastBody []byte
|
||||||
|
var lastStatus int
|
||||||
|
for index, candidate := range nodeTypeCandidates {
|
||||||
|
requestURL, err := url.Parse(strings.TrimRight(baseURL, "/") + endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, 0, err
|
||||||
|
}
|
||||||
|
query := requestURL.Query()
|
||||||
|
query.Set("node_id", strconv.Itoa(nodeID))
|
||||||
|
query.Set("token", s.options.Key)
|
||||||
|
if candidate != "" {
|
||||||
|
query.Set("node_type", candidate)
|
||||||
|
}
|
||||||
|
requestURL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
var bodyReader io.Reader
|
||||||
|
if payload != nil {
|
||||||
|
bodyReader = bytes.NewReader(payload)
|
||||||
|
}
|
||||||
|
req, _ := http.NewRequest(method, requestURL.String(), bodyReader)
|
||||||
|
req.Header.Set("User-Agent", "sing-box/xboard")
|
||||||
|
if contentType != "" {
|
||||||
|
req.Header.Set("Content-Type", contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
logNodeType := candidate
|
||||||
|
if logNodeType == "" {
|
||||||
|
logNodeType = "<empty>"
|
||||||
|
}
|
||||||
|
s.logger.Info("Xboard panel request. endpoint=", endpoint, ", node_id=", nodeID, ", node_type=", logNodeType)
|
||||||
|
|
||||||
|
resp, err := s.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, 0, err
|
||||||
|
}
|
||||||
|
responseBody, readErr := io.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
if readErr != nil {
|
||||||
|
return nil, nil, 0, readErr
|
||||||
|
}
|
||||||
|
|
||||||
|
lastHeader = resp.Header.Clone()
|
||||||
|
lastBody = responseBody
|
||||||
|
lastStatus = resp.StatusCode
|
||||||
|
|
||||||
|
if resp.StatusCode == 400 && candidate != "" && strings.Contains(string(responseBody), "Server does not exist") && index+1 < len(nodeTypeCandidates) {
|
||||||
|
s.logger.Warn("Xboard panel request failed with node_type=", candidate, ", retrying without node_type")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastHeader, lastBody, lastStatus, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastHeader, lastBody, lastStatus, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) fetchConfig() (*XNodeConfig, error) {
|
func (s *Service) fetchConfig() (*XNodeConfig, error) {
|
||||||
nodeID := s.options.ConfigNodeID
|
nodeID := s.options.ConfigNodeID
|
||||||
if nodeID == 0 {
|
if nodeID == 0 {
|
||||||
@@ -1012,18 +1087,13 @@ func (s *Service) fetchConfig() (*XNodeConfig, error) {
|
|||||||
if baseURL == "" {
|
if baseURL == "" {
|
||||||
baseURL = s.options.PanelURL
|
baseURL = s.options.PanelURL
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("%s/api/v1/server/UniProxy/config?node_id=%d&node_type=%s&token=%s", baseURL, nodeID, s.options.NodeType, s.options.Key)
|
headers, body, statusCode, err := s.panelRequest("GET", baseURL, "/api/v1/server/UniProxy/config", nodeID, nil, "")
|
||||||
req, _ := http.NewRequest("GET", url, nil)
|
|
||||||
req.Header.Set("User-Agent", "sing-box/xboard")
|
|
||||||
|
|
||||||
resp, err := s.httpClient.Do(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// Check time drift
|
// Check time drift
|
||||||
if dateStr := resp.Header.Get("Date"); dateStr != "" {
|
if dateStr := headers.Get("Date"); dateStr != "" {
|
||||||
if panelTime, err := http.ParseTime(dateStr); err == nil {
|
if panelTime, err := http.ParseTime(dateStr); err == nil {
|
||||||
localTime := time.Now()
|
localTime := time.Now()
|
||||||
drift := localTime.Sub(panelTime)
|
drift := localTime.Sub(panelTime)
|
||||||
@@ -1035,13 +1105,10 @@ func (s *Service) fetchConfig() (*XNodeConfig, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
if statusCode != 200 {
|
||||||
respBody, _ := io.ReadAll(resp.Body)
|
return nil, E.New("failed to fetch config, status: ", statusCode, ", body: ", string(body))
|
||||||
return nil, E.New("failed to fetch config, status: ", resp.Status, ", body: ", string(respBody))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
|
||||||
|
|
||||||
var result struct {
|
var result struct {
|
||||||
Data XNodeConfig `json:"data"`
|
Data XNodeConfig `json:"data"`
|
||||||
}
|
}
|
||||||
@@ -1249,22 +1316,15 @@ func (s *Service) pushTraffic(data any) error {
|
|||||||
if baseURL == "" {
|
if baseURL == "" {
|
||||||
baseURL = s.options.PanelURL
|
baseURL = s.options.PanelURL
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("%s/api/v1/server/UniProxy/push?node_id=%d&node_type=%s&token=%s", baseURL, nodeID, s.options.NodeType, s.options.Key)
|
|
||||||
body, _ := json.Marshal(data)
|
body, _ := json.Marshal(data)
|
||||||
|
|
||||||
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(body))
|
_, responseBody, statusCode, err := s.panelRequest("POST", baseURL, "/api/v1/server/UniProxy/push", nodeID, body, "application/json")
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
req.Header.Set("User-Agent", "sing-box/xboard")
|
|
||||||
|
|
||||||
resp, err := s.httpClient.Do(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
if statusCode != 200 {
|
||||||
respBody, _ := io.ReadAll(resp.Body)
|
return E.New("failed to push traffic, status: ", statusCode, ", body: ", string(responseBody))
|
||||||
return E.New("failed to push traffic, status: ", resp.Status, ", body: ", string(respBody))
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1278,21 +1338,15 @@ func (s *Service) sendAlive() {
|
|||||||
if baseURL == "" {
|
if baseURL == "" {
|
||||||
baseURL = s.options.PanelURL
|
baseURL = s.options.PanelURL
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("%s/api/v1/server/UniProxy/alive?node_id=%d&node_type=%s&token=%s", baseURL, nodeID, s.options.NodeType, s.options.Key)
|
|
||||||
|
|
||||||
req, _ := http.NewRequest("POST", url, nil)
|
_, responseBody, statusCode, err := s.panelRequest("POST", baseURL, "/api/v1/server/UniProxy/alive", nodeID, nil, "")
|
||||||
req.Header.Set("User-Agent", "sing-box/xboard")
|
|
||||||
|
|
||||||
resp, err := s.httpClient.Do(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Xboard heartbeat error: ", err)
|
s.logger.Error("Xboard heartbeat error: ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
if statusCode != 200 {
|
||||||
respBody, _ := io.ReadAll(resp.Body)
|
s.logger.Warn("Xboard heartbeat failed, status: ", statusCode, ", body: ", string(responseBody))
|
||||||
s.logger.Warn("Xboard heartbeat failed, status: ", resp.Status, ", body: ", string(respBody))
|
|
||||||
} else {
|
} else {
|
||||||
s.logger.Trace("Xboard heartbeat sent")
|
s.logger.Trace("Xboard heartbeat sent")
|
||||||
}
|
}
|
||||||
@@ -1369,23 +1423,15 @@ func (s *Service) fetchUsers() ([]XUser, error) {
|
|||||||
if baseURL == "" {
|
if baseURL == "" {
|
||||||
baseURL = s.options.PanelURL
|
baseURL = s.options.PanelURL
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("%s/api/v1/server/UniProxy/user?node_id=%d&node_type=%s&token=%s", baseURL, nodeID, s.options.NodeType, s.options.Key)
|
_, body, statusCode, err := s.panelRequest("GET", baseURL, "/api/v1/server/UniProxy/user", nodeID, nil, "")
|
||||||
req, _ := http.NewRequest("GET", url, nil)
|
|
||||||
req.Header.Set("User-Agent", "sing-box/xboard")
|
|
||||||
|
|
||||||
resp, err := s.httpClient.Do(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
if statusCode != 200 {
|
||||||
respBody, _ := io.ReadAll(resp.Body)
|
return nil, E.New("failed to fetch users, status: ", statusCode, ", body: ", string(body))
|
||||||
return nil, E.New("failed to fetch users, status: ", resp.Status, ", body: ", string(respBody))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
|
||||||
|
|
||||||
var result struct {
|
var result struct {
|
||||||
Data []XUser `json:"data"`
|
Data []XUser `json:"data"`
|
||||||
Users []XUser `json:"users"`
|
Users []XUser `json:"users"`
|
||||||
|
|||||||
@@ -116,3 +116,18 @@ func TestBuildInboundMultiplex(t *testing.T) {
|
|||||||
t.Fatalf("buildInboundMultiplex() brutal = %+v", got.Brutal)
|
t.Fatalf("buildInboundMultiplex() brutal = %+v", got.Brutal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNormalizePanelNodeType(t *testing.T) {
|
||||||
|
tests := map[string]string{
|
||||||
|
"v2ray": "vmess",
|
||||||
|
"hysteria2": "hysteria",
|
||||||
|
"vless": "vless",
|
||||||
|
"": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
for input, want := range tests {
|
||||||
|
if got := normalizePanelNodeType(input); got != want {
|
||||||
|
t.Fatalf("normalizePanelNodeType(%q) = %q, want %q", input, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user