fff
This commit is contained in:
@@ -1003,6 +1003,81 @@ func (s *Service) setupNode() error {
|
||||
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) {
|
||||
nodeID := s.options.ConfigNodeID
|
||||
if nodeID == 0 {
|
||||
@@ -1012,18 +1087,13 @@ func (s *Service) fetchConfig() (*XNodeConfig, error) {
|
||||
if baseURL == "" {
|
||||
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)
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("User-Agent", "sing-box/xboard")
|
||||
|
||||
resp, err := s.httpClient.Do(req)
|
||||
headers, body, statusCode, err := s.panelRequest("GET", baseURL, "/api/v1/server/UniProxy/config", nodeID, nil, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check time drift
|
||||
if dateStr := resp.Header.Get("Date"); dateStr != "" {
|
||||
if dateStr := headers.Get("Date"); dateStr != "" {
|
||||
if panelTime, err := http.ParseTime(dateStr); err == nil {
|
||||
localTime := time.Now()
|
||||
drift := localTime.Sub(panelTime)
|
||||
@@ -1035,13 +1105,10 @@ func (s *Service) fetchConfig() (*XNodeConfig, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
return nil, E.New("failed to fetch config, status: ", resp.Status, ", body: ", string(respBody))
|
||||
if statusCode != 200 {
|
||||
return nil, E.New("failed to fetch config, status: ", statusCode, ", body: ", string(body))
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
var result struct {
|
||||
Data XNodeConfig `json:"data"`
|
||||
}
|
||||
@@ -1249,22 +1316,15 @@ func (s *Service) pushTraffic(data any) error {
|
||||
if baseURL == "" {
|
||||
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)
|
||||
|
||||
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", "sing-box/xboard")
|
||||
|
||||
resp, err := s.httpClient.Do(req)
|
||||
|
||||
_, responseBody, statusCode, err := s.panelRequest("POST", baseURL, "/api/v1/server/UniProxy/push", nodeID, body, "application/json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
return E.New("failed to push traffic, status: ", resp.Status, ", body: ", string(respBody))
|
||||
|
||||
if statusCode != 200 {
|
||||
return E.New("failed to push traffic, status: ", statusCode, ", body: ", string(responseBody))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1278,21 +1338,15 @@ func (s *Service) sendAlive() {
|
||||
if baseURL == "" {
|
||||
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)
|
||||
req.Header.Set("User-Agent", "sing-box/xboard")
|
||||
|
||||
resp, err := s.httpClient.Do(req)
|
||||
|
||||
_, responseBody, statusCode, err := s.panelRequest("POST", baseURL, "/api/v1/server/UniProxy/alive", nodeID, nil, "")
|
||||
if err != nil {
|
||||
s.logger.Error("Xboard heartbeat error: ", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
s.logger.Warn("Xboard heartbeat failed, status: ", resp.Status, ", body: ", string(respBody))
|
||||
|
||||
if statusCode != 200 {
|
||||
s.logger.Warn("Xboard heartbeat failed, status: ", statusCode, ", body: ", string(responseBody))
|
||||
} else {
|
||||
s.logger.Trace("Xboard heartbeat sent")
|
||||
}
|
||||
@@ -1369,23 +1423,15 @@ func (s *Service) fetchUsers() ([]XUser, error) {
|
||||
if baseURL == "" {
|
||||
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)
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("User-Agent", "sing-box/xboard")
|
||||
|
||||
resp, err := s.httpClient.Do(req)
|
||||
_, body, statusCode, err := s.panelRequest("GET", baseURL, "/api/v1/server/UniProxy/user", nodeID, nil, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
return nil, E.New("failed to fetch users, status: ", resp.Status, ", body: ", string(respBody))
|
||||
|
||||
if statusCode != 200 {
|
||||
return nil, E.New("failed to fetch users, status: ", statusCode, ", body: ", string(body))
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
var result struct {
|
||||
Data []XUser `json:"data"`
|
||||
Users []XUser `json:"users"`
|
||||
|
||||
@@ -116,3 +116,18 @@ func TestBuildInboundMultiplex(t *testing.T) {
|
||||
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