chore: move ip utils
Kim committed
Mar 26, 2026 at 10:00 UTC
ed9f8a47e9c682f37d941258e99191932538305c
6 files changed
+138
-126
portal/api_server.go
+2
-13
@@ -432,17 +432,6 @@ func (s *Server) handleQUICTunnelConn(conn *quic.Conn) {
432
Msg("quic tunnel connected")
433
}
434
435
-func sanitizeReportedIP(raw string) string {
436
- candidate := strings.TrimSpace(raw)
437
- if candidate == "" {
438
- return ""
439
- }
440
- if net.ParseIP(candidate) == nil {
441
- return ""
442
- }
443
- return candidate
444
-}
445
-
435
func (s *Server) registerLease(req types.RegisterRequest, clientIP string) (types.RegisterResponse, error) {
436
name, err := utils.NormalizeDNSLabel(req.Name)
437
if err != nil {
@@ -503,7 +492,7 @@ func (s *Server) registerLease(req types.RegisterRequest, clientIP string) (type
492
FirstSeenAt: now,
493
LastSeenAt: now,
494
ClientIP: clientIP,
506
- ReportedIP: sanitizeReportedIP(req.ReportedIP),
495
+ ReportedIP: utils.SanitizeReportedIP(req.ReportedIP),
496
UDPEnabled: req.UDPEnabled,
497
},
498
ReverseToken: req.ReverseToken,
@@ -580,7 +569,7 @@ func (s *Server) renewLease(req types.RenewRequest, clientIP string) (types.Rene
569
if req.TTL > 0 {
570
ttl = time.Duration(req.TTL) * time.Second
571
}
583
- record, err := s.registry.Renew(req.LeaseID, req.ReverseToken, ttl, clientIP, sanitizeReportedIP(req.ReportedIP))
572
+ record, err := s.registry.Renew(req.LeaseID, req.ReverseToken, ttl, clientIP, utils.SanitizeReportedIP(req.ReportedIP))
573
if err != nil {
574
return types.RenewResponse{}, err
575
}
sdk/api_client.go
+1
-1
@@ -147,7 +147,7 @@ func (a *apiClient) ensureReady(ctx context.Context) error {
147
a.rawTLSConfig = rawTLSConfig
148
149
if a.resolvedPublicIP == "" {
150
- a.resolvedPublicIP = resolvePublicIP(ctx)
150
+ a.resolvedPublicIP = utils.ResolvePublicIP(ctx)
151
}
152
153
return nil
sdk/public_ip.go
deleted
-60
@@ -1,60 +0,0 @@
1
-package sdk
2
-
3
-import (
4
- "context"
5
- "io"
6
- "net"
7
- "net/http"
8
- "strings"
9
- "time"
10
-)
11
-
12
-// resolvePublicIP attempts to determine the caller's public IP address
13
-// using well-known external services. Returns empty string on failure.
14
-// Best-effort with a short timeout to avoid blocking registration.
15
-func resolvePublicIP(ctx context.Context) string {
16
- ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
17
- defer cancel()
18
-
19
- endpoints := []string{
20
- "https://api.ipify.org",
21
- "https://ifconfig.me/ip",
22
- }
23
-
24
- for _, endpoint := range endpoints {
25
- if ip := queryIPEndpoint(ctx, endpoint); ip != "" {
26
- return ip
27
- }
28
- }
29
- return ""
30
-}
31
-
32
-func queryIPEndpoint(ctx context.Context, url string) string {
33
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
34
- if err != nil {
35
- return ""
36
- }
37
- req.Header.Set("User-Agent", "portal-tunnel")
38
-
39
- client := &http.Client{Timeout: 3 * time.Second}
40
- resp, err := client.Do(req)
41
- if err != nil {
42
- return ""
43
- }
44
- defer resp.Body.Close()
45
-
46
- if resp.StatusCode != http.StatusOK {
47
- return ""
48
- }
49
-
50
- body, err := io.ReadAll(io.LimitReader(resp.Body, 256))
51
- if err != nil {
52
- return ""
53
- }
54
-
55
- candidate := strings.TrimSpace(string(body))
56
- if net.ParseIP(candidate) == nil {
57
- return ""
58
- }
59
- return candidate
60
-}
utils/network.go
new
+105
@@ -0,0 +1,105 @@
1
+package utils
2
+
3
+import (
4
+ "context"
5
+ "encoding/json"
6
+ "io"
7
+ "net"
8
+ "net/http"
9
+ "strings"
10
+ "time"
11
+
12
+ "github.com/gosuda/portal/v2/types"
13
+)
14
+
15
+// ResolvePublicIP attempts to determine the caller's public IP address
16
+// using well-known external services. Returns empty string on failure.
17
+// Best-effort with a short timeout to avoid blocking registration.
18
+func ResolvePublicIP(ctx context.Context) string {
19
+ ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
20
+ defer cancel()
21
+
22
+ endpoints := []string{
23
+ "https://api.ipify.org",
24
+ "https://ifconfig.me/ip",
25
+ }
26
+ client := &http.Client{Timeout: 3 * time.Second}
27
+
28
+ for _, endpoint := range endpoints {
29
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
30
+ if err != nil {
31
+ continue
32
+ }
33
+ req.Header.Set("User-Agent", "portal-tunnel")
34
+
35
+ resp, err := client.Do(req)
36
+ if err != nil {
37
+ continue
38
+ }
39
+
40
+ body, readErr := io.ReadAll(io.LimitReader(resp.Body, 256))
41
+ _ = resp.Body.Close()
42
+ if resp.StatusCode != http.StatusOK || readErr != nil {
43
+ continue
44
+ }
45
+
46
+ if candidate := SanitizeReportedIP(string(body)); candidate != "" {
47
+ return candidate
48
+ }
49
+ }
50
+
51
+ return ""
52
+}
53
+
54
+func SanitizeReportedIP(raw string) string {
55
+ candidate := strings.TrimSpace(raw)
56
+ if candidate == "" {
57
+ return ""
58
+ }
59
+ if net.ParseIP(candidate) == nil {
60
+ return ""
61
+ }
62
+ return candidate
63
+}
64
+
65
+func ResolvePortalRelayURLs(ctx context.Context, explicit []string, includeDefaults bool) ([]string, error) {
66
+ explicit, err := NormalizeRelayURLs(explicit...)
67
+ if err != nil {
68
+ return nil, err
69
+ }
70
+ if !includeDefaults {
71
+ return explicit, nil
72
+ }
73
+
74
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, types.PortalRelayRegistryURL, nil)
75
+ if err != nil {
76
+ return explicit, nil
77
+ }
78
+
79
+ client := &http.Client{Timeout: 5 * time.Second}
80
+ resp, err := client.Do(req)
81
+ if err != nil {
82
+ return explicit, nil
83
+ }
84
+ defer resp.Body.Close()
85
+
86
+ if resp.StatusCode != http.StatusOK {
87
+ return explicit, nil
88
+ }
89
+
90
+ var registry struct {
91
+ Relays []string `json:"relays"`
92
+ }
93
+ if err := json.NewDecoder(resp.Body).Decode(®istry); err != nil {
94
+ return explicit, nil
95
+ }
96
+
97
+ defaults, err := NormalizeRelayURLs(registry.Relays...)
98
+ if err != nil {
99
+ return explicit, nil
100
+ }
101
+ if len(defaults) == 0 {
102
+ return explicit, nil
103
+ }
104
+ return MergeRelayURLs(defaults, nil, explicit)
105
+}
utils/network_test.go
new
+30
@@ -0,0 +1,30 @@
1
+package utils
2
+
3
+import "testing"
4
+
5
+func TestSanitizeReportedIP(t *testing.T) {
6
+ t.Parallel()
7
+
8
+ tests := []struct {
9
+ name string
10
+ raw string
11
+ want string
12
+ }{
13
+ {name: "empty", raw: "", want: ""},
14
+ {name: "whitespace", raw: " ", want: ""},
15
+ {name: "ipv4", raw: " 203.0.113.10 ", want: "203.0.113.10"},
16
+ {name: "ipv6", raw: " 2001:db8::1 ", want: "2001:db8::1"},
17
+ {name: "invalid", raw: "not-an-ip", want: ""},
18
+ {name: "host port", raw: "203.0.113.10:443", want: ""},
19
+ }
20
+
21
+ for _, tc := range tests {
22
+ t.Run(tc.name, func(t *testing.T) {
23
+ t.Parallel()
24
+
25
+ if got := SanitizeReportedIP(tc.raw); got != tc.want {
26
+ t.Fatalf("SanitizeReportedIP(%q) = %q, want %q", tc.raw, got, tc.want)
27
+ }
28
+ })
29
+ }
30
+}
utils/registry.go
deleted
-52
@@ -1,52 +0,0 @@
1
-package utils
2
-
3
-import (
4
- "context"
5
- "encoding/json"
6
- "net/http"
7
- "time"
8
-
9
- "github.com/gosuda/portal/v2/types"
10
-)
11
-
12
-func ResolvePortalRelayURLs(ctx context.Context, explicit []string, includeDefaults bool) ([]string, error) {
13
- explicit, err := NormalizeRelayURLs(explicit...)
14
- if err != nil {
15
- return nil, err
16
- }
17
- if !includeDefaults {
18
- return explicit, nil
19
- }
20
-
21
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, types.PortalRelayRegistryURL, nil)
22
- if err != nil {
23
- return explicit, nil
24
- }
25
-
26
- client := &http.Client{Timeout: 5 * time.Second}
27
- resp, err := client.Do(req)
28
- if err != nil {
29
- return explicit, nil
30
- }
31
- defer resp.Body.Close()
32
-
33
- if resp.StatusCode != http.StatusOK {
34
- return explicit, nil
35
- }
36
-
37
- var registry struct {
38
- Relays []string `json:"relays"`
39
- }
40
- if err := json.NewDecoder(resp.Body).Decode(®istry); err != nil {
41
- return explicit, nil
42
- }
43
-
44
- defaults, err := NormalizeRelayURLs(registry.Relays...)
45
- if err != nil {
46
- return explicit, nil
47
- }
48
- if len(defaults) == 0 {
49
- return explicit, nil
50
- }
51
- return MergeRelayURLs(defaults, nil, explicit)
52
-}