Skip to content

Commit 2cecc87

Browse files
authored
Fix/xray api (#31)
* fix: enhance API source handling and connection management * feat: add support for normalizing GeoIP private rules in API configuration
1 parent ff83f71 commit 2cecc87

3 files changed

Lines changed: 172 additions & 4 deletions

File tree

backend/xray/api/base.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package api
22

33
import (
4+
"context"
45
"fmt"
6+
"net"
7+
"time"
8+
59
"github.com/xtls/xray-core/app/proxyman/command"
610
statsService "github.com/xtls/xray-core/app/stats/command"
711
"google.golang.org/grpc"
@@ -16,9 +20,26 @@ type XrayHandler struct {
1620

1721
func NewXrayAPI(apiPort int) (*XrayHandler, error) {
1822
x := &XrayHandler{}
23+
target := fmt.Sprintf("127.0.0.1:%v", apiPort)
24+
dialer := &net.Dialer{
25+
Timeout: 5 * time.Second,
26+
LocalAddr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1")},
27+
}
1928

2029
var err error
21-
x.GrpcClient, err = grpc.NewClient(fmt.Sprintf("127.0.0.1:%v", apiPort), grpc.WithTransportCredentials(insecure.NewCredentials()))
30+
x.GrpcClient, err = grpc.NewClient(
31+
target,
32+
grpc.WithTransportCredentials(insecure.NewCredentials()),
33+
grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
34+
conn, dialErr := dialer.DialContext(ctx, "tcp4", addr)
35+
if dialErr == nil {
36+
return conn, nil
37+
}
38+
39+
var fallback net.Dialer
40+
return fallback.DialContext(ctx, "tcp", addr)
41+
}),
42+
)
2243

2344
if err != nil {
2445
return nil, err

backend/xray/config.go

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"encoding/json"
55
"fmt"
66
"log"
7+
"net"
78
"slices"
9+
"sort"
810
"strings"
911
"sync"
1012

@@ -405,6 +407,144 @@ func filterRules(rules []json.RawMessage, apiTag string) ([]json.RawMessage, err
405407
return filtered, nil
406408
}
407409

410+
var privateCIDRs = []string{
411+
"0.0.0.0/8",
412+
"10.0.0.0/8",
413+
"100.64.0.0/10",
414+
"127.0.0.0/8",
415+
"169.254.0.0/16",
416+
"172.16.0.0/12",
417+
"192.0.0.0/24",
418+
"192.168.0.0/16",
419+
"198.18.0.0/15",
420+
"224.0.0.0/4",
421+
"240.0.0.0/4",
422+
"::/128",
423+
"::1/128",
424+
"fc00::/7",
425+
"fe80::/10",
426+
}
427+
428+
func replaceGeoIPPrivate(values any) (any, bool) {
429+
list, ok := values.([]any)
430+
if !ok {
431+
return values, false
432+
}
433+
434+
updated := make([]any, 0, len(list)+len(privateCIDRs))
435+
changed := false
436+
for _, entry := range list {
437+
s, strOK := entry.(string)
438+
if strOK && strings.EqualFold(s, "geoip:private") {
439+
for _, cidr := range privateCIDRs {
440+
updated = append(updated, cidr)
441+
}
442+
changed = true
443+
continue
444+
}
445+
updated = append(updated, entry)
446+
}
447+
448+
if !changed {
449+
return values, false
450+
}
451+
452+
return updated, true
453+
}
454+
455+
func normalizeGeoIPPrivateRules(rules []json.RawMessage) ([]json.RawMessage, error) {
456+
if rules == nil {
457+
return []json.RawMessage{}, nil
458+
}
459+
460+
normalized := make([]json.RawMessage, 0, len(rules))
461+
for _, raw := range rules {
462+
var obj map[string]any
463+
if err := json.Unmarshal(raw, &obj); err != nil {
464+
return nil, fmt.Errorf("invalid JSON in rule: %w", err)
465+
}
466+
467+
ruleChanged := false
468+
if ip, ok := obj["ip"]; ok {
469+
newIP, changed := replaceGeoIPPrivate(ip)
470+
if changed {
471+
obj["ip"] = newIP
472+
ruleChanged = true
473+
}
474+
}
475+
476+
if source, ok := obj["source"]; ok {
477+
newSource, changed := replaceGeoIPPrivate(source)
478+
if changed {
479+
obj["source"] = newSource
480+
ruleChanged = true
481+
}
482+
}
483+
484+
if !ruleChanged {
485+
normalized = append(normalized, raw)
486+
continue
487+
}
488+
489+
rawBytes, err := json.Marshal(obj)
490+
if err != nil {
491+
return nil, fmt.Errorf("failed to marshal normalized rule: %w", err)
492+
}
493+
normalized = append(normalized, json.RawMessage(rawBytes))
494+
}
495+
496+
return normalized, nil
497+
}
498+
499+
func apiRuleSources() []string {
500+
seen := map[string]struct{}{
501+
"127.0.0.1": {},
502+
"::1": {},
503+
}
504+
505+
ifaces, err := net.Interfaces()
506+
if err != nil {
507+
return []string{"127.0.0.1", "::1"}
508+
}
509+
510+
for _, iface := range ifaces {
511+
if iface.Flags&net.FlagUp == 0 {
512+
continue
513+
}
514+
515+
addrs, err := iface.Addrs()
516+
if err != nil {
517+
continue
518+
}
519+
520+
for _, addr := range addrs {
521+
var ip net.IP
522+
switch v := addr.(type) {
523+
case *net.IPNet:
524+
ip = v.IP
525+
case *net.IPAddr:
526+
ip = v.IP
527+
default:
528+
continue
529+
}
530+
531+
if ip == nil || ip.IsUnspecified() {
532+
continue
533+
}
534+
535+
seen[ip.String()] = struct{}{}
536+
}
537+
}
538+
539+
sources := make([]string, 0, len(seen))
540+
for source := range seen {
541+
sources = append(sources, source)
542+
}
543+
sort.Strings(sources)
544+
545+
return sources
546+
}
547+
408548
func (c *Config) ApplyAPI(apiPort int) (err error) {
409549
// Remove the existing inbound with the API_INBOUND tag
410550
for i, inbound := range c.InboundConfigs {
@@ -425,7 +565,14 @@ func (c *Config) ApplyAPI(apiPort int) (err error) {
425565
}
426566

427567
rules := c.RouterConfig.RuleList
568+
rules, err = normalizeGeoIPPrivateRules(rules)
569+
if err != nil {
570+
return err
571+
}
428572
c.RouterConfig.RuleList, err = filterRules(rules, apiTag)
573+
if err != nil {
574+
return err
575+
}
429576

430577
c.checkPolicy()
431578

@@ -442,7 +589,7 @@ func (c *Config) ApplyAPI(apiPort int) (err error) {
442589

443590
rule := map[string]any{
444591
"inboundTag": []string{"API_INBOUND"},
445-
"source": []string{"127.0.0.1"},
592+
"source": apiRuleSources(),
446593
"outboundTag": "API",
447594
"type": "field",
448595
}

controller/rest/base.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ func (s *Service) Start(w http.ResponseWriter, r *http.Request) {
3434
s.Disconnect()
3535
}
3636

37-
s.Connect(ip, keepAlive)
38-
3937
if err = s.StartBackend(ctx, backendType); err != nil {
4038
http.Error(w, err.Error(), http.StatusServiceUnavailable)
4139
return
4240
}
4341

42+
s.Connect(ip, keepAlive)
43+
4444
common.SendProtoResponse(w, s.BaseInfoResponse())
4545
}
4646

0 commit comments

Comments
 (0)