remove ip logic, fixed for docker-compose cli.
Hee Sung Son committed
Nov 18, 2025 at 15:43 UTC
de7c3b58dff3efed542b51b04ca951f1df518057
15 files changed
+278
-690
Dockerfile
+24
-1
@@ -1,5 +1,25 @@
1
# syntax=docker/dockerfile:1
2
3
+# Frontend build stage
4
+FROM --platform=$BUILDPLATFORM node:22-alpine AS frontend-builder
5
+
6
+WORKDIR /src
7
+
8
+# Copy frontend package files
9
+COPY cmd/relay-server/frontend/package*.json ./cmd/relay-server/frontend/
10
+
11
+# Install frontend dependencies
12
+RUN --mount=type=cache,target=/root/.npm \
13
+ cd cmd/relay-server/frontend && \
14
+ npm ci
15
+
16
+# Copy frontend source code
17
+COPY cmd/relay-server/frontend ./cmd/relay-server/frontend
18
+
19
+# Build frontend
20
+RUN cd cmd/relay-server/frontend && npm run build
21
+
22
+# Go builder stage
23
FROM --platform=$BUILDPLATFORM golang:1 AS builder
24
25
WORKDIR /src
@@ -12,7 +32,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
32
&& rm -rf /var/lib/apt/lists/*
33
34
# Set GOMODCACHE to cache Go modules in cache volume
15
-RUN go env -w GOMODCACHE=/root/.cache/go-build
35
+RUN go env -w GOMODCACHE=/root/.cache/go-build
36
37
# Copy go.mod and go.sum
38
COPY go.mod go.sum ./
@@ -24,6 +44,9 @@ RUN --mount=type=cache,target=/go/pkg/mod \
44
# Copy the rest of the source code
45
COPY . .
46
47
+# Copy built frontend from frontend-builder stage
48
+COPY --from=frontend-builder /src/cmd/relay-server/app ./cmd/relay-server/app
49
+
50
RUN --mount=type=cache,target=/go/pkg/mod \
51
--mount=type=cache,target=/root/.cache/go-build \
52
make build-wasm
Makefile
+2
-2
@@ -85,11 +85,11 @@ compress-wasm:
85
# Build React frontend with Tailwind CSS 4
86
build-frontend:
87
@echo "[frontend] building React frontend..."
88
- @cd cmd/relay-server/frontend && npm run build
88
+ @cd cmd/relay-server/frontend && npm i && npm run build
89
@echo "[frontend] build complete"
90
91
# Build Go relay server (embeds WASM from cmd/relay-server/static)
92
-build-server: build-frontend
92
+build-server:
93
@echo "[server] building Go portal..."
94
CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o bin/relay-server ./cmd/relay-server
95
cmd/relay-server/frontend.go
+1
-13
@@ -493,19 +493,7 @@ func isPortalSubdomain(host string) bool {
493
return false
494
}
495
496
- // Remove port from host for comparison
497
- hostWithoutPort := host
498
- if idx := strings.LastIndex(host, ":"); idx != -1 {
499
- hostWithoutPort = host[:idx]
500
- }
501
-
502
- // Remove port from portalHost for comparison
503
- portalHostWithoutPort := portalHost
504
- if idx := strings.LastIndex(portalHost, ":"); idx != -1 {
505
- portalHostWithoutPort = portalHost[:idx]
506
- }
507
-
508
- return strings.HasSuffix(hostWithoutPort, "."+portalHostWithoutPort)
496
+ return strings.HasSuffix(host, "."+portalHost)
497
}
498
499
// matchesWildcardPattern checks if a host matches a wildcard pattern (e.g., *.localhost:4017)
cmd/relay-server/frontend/src/App.tsx
+3
-38
@@ -28,26 +28,16 @@ function convertSSRDataToServers(ssrData: ServerData[]) {
28
console.error("[App] Failed to parse metadata:", err, row.Metadata);
29
}
30
31
- // Get region from SSR data (GeoIP-detected region)
32
- const region = row.Region || "unknown";
33
-
34
- // Combine region tag with metadata tags
35
- const metadataTags = Array.isArray(metadata.tags) ? metadata.tags : [];
36
- const allTags =
37
- region !== "unknown" ? [region, ...metadataTags] : metadataTags;
38
-
31
return {
32
id: index + 1,
33
name: row.Name || row.DNS || "(unnamed)",
34
description: metadata.description || "",
43
- tags: allTags,
35
+ tags: Array.isArray(metadata.tags) ? metadata.tags : [],
36
thumbnail: metadata.thumbnail || "",
37
owner: metadata.owner || "",
38
online: row.Connected,
39
dns: row.DNS || "",
40
link: row.Link,
49
- region: region,
50
- countryCode: row.CountryCode || "",
41
};
42
});
43
}
@@ -55,7 +45,6 @@ function convertSSRDataToServers(ssrData: ServerData[]) {
45
function App() {
46
const [currentPage, setCurrentPage] = useState(1);
47
const [searchQuery, setSearchQuery] = useState("");
58
- const [region, setRegion] = useState("all");
48
const [status, setStatus] = useState("all");
49
const [sortBy, setSortBy] = useState("default");
50
@@ -75,17 +64,6 @@ function App() {
64
return [];
65
}, [ssrData]);
66
78
- // Extract unique available countries from servers
79
- const availableCountries = useMemo(() => {
80
- const countryCodes = new Set<string>();
81
- servers.forEach((server) => {
82
- if (server.countryCode) {
83
- countryCodes.add(server.countryCode);
84
- }
85
- });
86
- return Array.from(countryCodes).sort();
87
- }, [servers]);
88
-
67
// Filter and sort servers
68
const filteredServers = useMemo(() => {
69
let filtered = servers.filter((server) => {
@@ -98,18 +76,13 @@ function App() {
76
tag.toLowerCase().includes(searchQuery.toLowerCase())
77
);
78
101
- // Country filter (using country code)
102
- const matchesRegion =
103
- region === "all" ||
104
- server.countryCode === region;
105
-
79
// Status filter
80
const matchesStatus =
81
status === "all" ||
82
(status === "online" && server.online) ||
83
(status === "offline" && !server.online);
84
112
- return matchesSearch && matchesRegion && matchesStatus;
85
+ return matchesSearch && matchesStatus;
86
});
87
88
// Sort based on sortBy value
@@ -130,7 +103,7 @@ function App() {
103
}
104
105
return filtered;
133
- }, [servers, searchQuery, region, status, sortBy]);
106
+ }, [servers, searchQuery, status, sortBy]);
107
108
// Pagination
109
const totalPages = Math.ceil(filteredServers.length / ITEMS_PER_PAGE);
@@ -145,11 +118,6 @@ function App() {
118
setCurrentPage(1);
119
};
120
148
- const handleRegionChange = (value: string) => {
149
- setRegion(value);
150
- setCurrentPage(1);
151
- };
152
-
121
const handleStatusChange = (value: string) => {
122
setStatus(value);
123
setCurrentPage(1);
@@ -170,13 +138,10 @@ function App() {
138
<SearchBar
139
searchQuery={searchQuery}
140
onSearchChange={handleSearchChange}
173
- region={region}
174
- onRegionChange={handleRegionChange}
141
status={status}
142
onStatusChange={handleStatusChange}
143
sortBy={sortBy}
144
onSortByChange={handleSortByChange}
179
- availableCountries={availableCountries}
145
/>
146
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 p-4 sm:p-6 mt-4">
147
{paginatedServers.length > 0 ? (
cmd/relay-server/frontend/src/components/SearchBar.tsx
-21
@@ -7,30 +7,23 @@ import {
7
SelectTrigger,
8
SelectValue,
9
} from "./ui/select";
10
-import { COUNTRY_NAMES } from "../lib/countries";
10
11
interface SearchBarProps {
12
searchQuery: string;
13
onSearchChange: (value: string) => void;
15
- region: string;
16
- onRegionChange: (value: string) => void;
14
status: string;
15
onStatusChange: (value: string) => void;
16
sortBy: string;
17
onSortByChange: (value: string) => void;
21
- availableCountries: string[];
18
}
19
20
export function SearchBar({
21
searchQuery,
22
onSearchChange,
27
- region,
28
- onRegionChange,
23
status,
24
onStatusChange,
25
sortBy,
26
onSortByChange,
33
- availableCountries,
27
}: SearchBarProps) {
28
return (
29
<div className="space-y-4 px-4 sm:px-6">
@@ -47,20 +40,6 @@ export function SearchBar({
40
</div>
41
</label>
42
<div className="flex flex-wrap gap-3">
50
- <Select value={region} onValueChange={onRegionChange}>
51
- <SelectTrigger className="w-[140px] h-8">
52
- <SelectValue placeholder="Country" />
53
- </SelectTrigger>
54
- <SelectContent>
55
- <SelectItem value="all">All Countries</SelectItem>
56
- {availableCountries.map((countryCode) => (
57
- <SelectItem key={countryCode} value={countryCode}>
58
- {COUNTRY_NAMES[countryCode] || countryCode}
59
- </SelectItem>
60
- ))}
61
- </SelectContent>
62
- </Select>
63
-
43
<Select value={status} onValueChange={onStatusChange}>
44
<SelectTrigger className="w-[140px] h-8">
45
<SelectValue placeholder="Status" />
cmd/relay-server/frontend/src/hooks/useSSRData.ts
-3
@@ -21,9 +21,6 @@ export interface ServerData {
21
StaleRed: boolean;
22
Hide: boolean;
23
Metadata: string;
24
- Region: string;
25
- CountryCode: string;
26
- City: string;
24
}
25
26
/**
cmd/relay-server/frontend/src/lib/countries.ts
deleted
-199
@@ -1,199 +0,0 @@
1
-// ISO 3166-1 alpha-2 country code to country name mapping
2
-export const COUNTRY_NAMES: Record<string, string> = {
3
- AF: "Afghanistan",
4
- AL: "Albania",
5
- DZ: "Algeria",
6
- AD: "Andorra",
7
- AO: "Angola",
8
- AR: "Argentina",
9
- AM: "Armenia",
10
- AU: "Australia",
11
- AT: "Austria",
12
- AZ: "Azerbaijan",
13
- BS: "Bahamas",
14
- BH: "Bahrain",
15
- BD: "Bangladesh",
16
- BB: "Barbados",
17
- BY: "Belarus",
18
- BE: "Belgium",
19
- BZ: "Belize",
20
- BJ: "Benin",
21
- BT: "Bhutan",
22
- BO: "Bolivia",
23
- BA: "Bosnia and Herzegovina",
24
- BW: "Botswana",
25
- BR: "Brazil",
26
- BN: "Brunei",
27
- BG: "Bulgaria",
28
- BF: "Burkina Faso",
29
- BI: "Burundi",
30
- KH: "Cambodia",
31
- CM: "Cameroon",
32
- CA: "Canada",
33
- CV: "Cape Verde",
34
- CF: "Central African Republic",
35
- TD: "Chad",
36
- CL: "Chile",
37
- CN: "China",
38
- CO: "Colombia",
39
- KM: "Comoros",
40
- CG: "Congo",
41
- CD: "Congo (DRC)",
42
- CR: "Costa Rica",
43
- HR: "Croatia",
44
- CU: "Cuba",
45
- CY: "Cyprus",
46
- CZ: "Czech Republic",
47
- DK: "Denmark",
48
- DJ: "Djibouti",
49
- DM: "Dominica",
50
- DO: "Dominican Republic",
51
- EC: "Ecuador",
52
- EG: "Egypt",
53
- SV: "El Salvador",
54
- GQ: "Equatorial Guinea",
55
- ER: "Eritrea",
56
- EE: "Estonia",
57
- ET: "Ethiopia",
58
- FJ: "Fiji",
59
- FI: "Finland",
60
- FR: "France",
61
- GA: "Gabon",
62
- GM: "Gambia",
63
- GE: "Georgia",
64
- DE: "Germany",
65
- GH: "Ghana",
66
- GR: "Greece",
67
- GD: "Grenada",
68
- GT: "Guatemala",
69
- GN: "Guinea",
70
- GW: "Guinea-Bissau",
71
- GY: "Guyana",
72
- HT: "Haiti",
73
- HN: "Honduras",
74
- HK: "Hong Kong",
75
- HU: "Hungary",
76
- IS: "Iceland",
77
- IN: "India",
78
- ID: "Indonesia",
79
- IR: "Iran",
80
- IQ: "Iraq",
81
- IE: "Ireland",
82
- IL: "Israel",
83
- IT: "Italy",
84
- JM: "Jamaica",
85
- JP: "Japan",
86
- JO: "Jordan",
87
- KZ: "Kazakhstan",
88
- KE: "Kenya",
89
- KI: "Kiribati",
90
- KP: "North Korea",
91
- KR: "South Korea",
92
- KW: "Kuwait",
93
- KG: "Kyrgyzstan",
94
- LA: "Laos",
95
- LV: "Latvia",
96
- LB: "Lebanon",
97
- LS: "Lesotho",
98
- LR: "Liberia",
99
- LY: "Libya",
100
- LI: "Liechtenstein",
101
- LT: "Lithuania",
102
- LU: "Luxembourg",
103
- MO: "Macau",
104
- MK: "North Macedonia",
105
- MG: "Madagascar",
106
- MW: "Malawi",
107
- MY: "Malaysia",
108
- MV: "Maldives",
109
- ML: "Mali",
110
- MT: "Malta",
111
- MH: "Marshall Islands",
112
- MR: "Mauritania",
113
- MU: "Mauritius",
114
- MX: "Mexico",
115
- FM: "Micronesia",
116
- MD: "Moldova",
117
- MC: "Monaco",
118
- MN: "Mongolia",
119
- ME: "Montenegro",
120
- MA: "Morocco",
121
- MZ: "Mozambique",
122
- MM: "Myanmar",
123
- NA: "Namibia",
124
- NR: "Nauru",
125
- NP: "Nepal",
126
- NL: "Netherlands",
127
- NZ: "New Zealand",
128
- NI: "Nicaragua",
129
- NE: "Niger",
130
- NG: "Nigeria",
131
- NO: "Norway",
132
- OM: "Oman",
133
- PK: "Pakistan",
134
- PW: "Palau",
135
- PS: "Palestine",
136
- PA: "Panama",
137
- PG: "Papua New Guinea",
138
- PY: "Paraguay",
139
- PE: "Peru",
140
- PH: "Philippines",
141
- PL: "Poland",
142
- PT: "Portugal",
143
- QA: "Qatar",
144
- RO: "Romania",
145
- RU: "Russia",
146
- RW: "Rwanda",
147
- KN: "Saint Kitts and Nevis",
148
- LC: "Saint Lucia",
149
- VC: "Saint Vincent",
150
- WS: "Samoa",
151
- SM: "San Marino",
152
- ST: "Sao Tome and Principe",
153
- SA: "Saudi Arabia",
154
- SN: "Senegal",
155
- RS: "Serbia",
156
- SC: "Seychelles",
157
- SL: "Sierra Leone",
158
- SG: "Singapore",
159
- SK: "Slovakia",
160
- SI: "Slovenia",
161
- SB: "Solomon Islands",
162
- SO: "Somalia",
163
- ZA: "South Africa",
164
- SS: "South Sudan",
165
- ES: "Spain",
166
- LK: "Sri Lanka",
167
- SD: "Sudan",
168
- SR: "Suriname",
169
- SZ: "Swaziland",
170
- SE: "Sweden",
171
- CH: "Switzerland",
172
- SY: "Syria",
173
- TW: "Taiwan",
174
- TJ: "Tajikistan",
175
- TZ: "Tanzania",
176
- TH: "Thailand",
177
- TL: "Timor-Leste",
178
- TG: "Togo",
179
- TO: "Tonga",
180
- TT: "Trinidad and Tobago",
181
- TN: "Tunisia",
182
- TR: "Turkey",
183
- TM: "Turkmenistan",
184
- TV: "Tuvalu",
185
- UG: "Uganda",
186
- UA: "Ukraine",
187
- AE: "United Arab Emirates",
188
- GB: "United Kingdom",
189
- US: "United States",
190
- UY: "Uruguay",
191
- UZ: "Uzbekistan",
192
- VU: "Vanuatu",
193
- VA: "Vatican City",
194
- VE: "Venezuela",
195
- VN: "Vietnam",
196
- YE: "Yemen",
197
- ZM: "Zambia",
198
- ZW: "Zimbabwe",
199
-};
cmd/relay-server/static/GeoLite2-Country.mmdb
Binary files a/cmd/relay-server/static/GeoLite2-Country.mmdb and /dev/null differ
cmd/relay-server/view.go
+2
-160
@@ -5,14 +5,11 @@ import (
5
"embed"
6
"encoding/json"
7
"fmt"
8
- "net"
8
"net/http"
9
"strings"
11
- "sync"
10
"time"
11
12
"github.com/gorilla/websocket"
15
- "github.com/oschwald/geoip2-golang"
13
"github.com/rs/zerolog/log"
14
15
"gosuda.org/portal/portal"
@@ -23,16 +20,6 @@ import (
20
//go:embed static
21
var assetsFS embed.FS
22
26
-//go:embed static/GeoLite2-Country.mmdb
27
-var geoipFS embed.FS
28
-
29
-// GeoIP database reader (global instance with lazy initialization)
30
-var (
31
- geoipReader *geoip2.Reader
32
- geoipReaderOnce sync.Once
33
- geoipReaderErr error
34
-)
35
-
23
func serveAsset(mux *http.ServeMux, route, assetPath, contentType string) {
24
mux.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
25
b, err := assetsFS.ReadFile(assetPath)
@@ -73,7 +60,7 @@ func serveHTTP(_ context.Context, addr string, serv *portal.RelayServer, nodeID
60
serveAppStatic(w, r, path, serv)
61
})
62
76
- // Portal frontend files (for unified caching)
63
+ // Portal frontend files (for unified caching)
64
appMux.HandleFunc("/frontend/", func(w http.ResponseWriter, r *http.Request) {
65
setCORSHeaders(w)
66
if r.Method == http.MethodOptions {
@@ -105,7 +92,7 @@ func serveHTTP(_ context.Context, addr string, serv *portal.RelayServer, nodeID
92
}
93
94
stream := &wsstream.WsStream{Conn: wsConn}
108
- if err := serv.HandleConnection(stream, r.RemoteAddr); err != nil {
95
+ if err := serv.HandleConnection(stream); err != nil {
96
log.Error().Err(err).Msg("[server] websocket relay connection error")
97
wsConn.Close()
98
return
@@ -165,121 +152,6 @@ type leaseRow struct {
152
StaleRed bool
153
Hide bool
154
Metadata string
168
- Region string
169
- CountryCode string
170
-}
171
-
172
-// initGeoIP initializes the GeoIP database reader (lazy loaded)
173
-func initGeoIP() error {
174
- geoipReaderOnce.Do(func() {
175
- // Try to load from embedded FS first
176
- data, err := geoipFS.ReadFile("static/GeoLite2-Country.mmdb")
177
- if err == nil {
178
- reader, err := geoip2.FromBytes(data)
179
- if err == nil {
180
- geoipReader = reader
181
- log.Info().
182
- Str("source", "embedded").
183
- Int("size", len(data)).
184
- Msg("GeoIP database loaded successfully from embedded FS")
185
- return
186
- }
187
- log.Warn().Err(err).Msg("Failed to parse embedded GeoIP database")
188
- } else {
189
- log.Debug().Err(err).Msg("Embedded GeoIP database not found, trying file paths")
190
- }
191
- })
192
-
193
- return geoipReaderErr
194
-}
195
-
196
-// getRegionFromIP extracts region information from an IP address
197
-func getRegionFromIP(ipStr string) (region, countryCode string) {
198
- // Initialize GeoIP if needed
199
- if err := initGeoIP(); err != nil {
200
- return "unknown", ""
201
- }
202
-
203
- // Parse IP address
204
- ip := net.ParseIP(ipStr)
205
- if ip == nil {
206
- return "unknown", ""
207
- }
208
-
209
- // Skip private/local IPs
210
- if ip.IsLoopback() || ip.IsPrivate() {
211
- log.Debug().Str("ip", ipStr).Msg("[GeoIP] Skipping local/private IP")
212
- return "local", ""
213
- }
214
-
215
- // Lookup country record
216
- record, err := geoipReader.Country(ip)
217
- if err != nil {
218
- log.Debug().Err(err).Str("ip", ipStr).Msg("GeoIP lookup failed")
219
- return "unknown", ""
220
- }
221
-
222
- // Extract country code
223
- countryCode = record.Country.IsoCode
224
-
225
- // Map country code to region
226
- // Based on common regional groupings used in gaming/CDN services
227
- switch countryCode {
228
- // North America
229
- case "US", "CA", "MX", "GT", "HN", "SV", "NI", "CR", "PA", "BZ":
230
- region = "us-east"
231
-
232
- // South America
233
- case "BR", "AR", "CL", "CO", "PE", "VE", "EC", "BO", "PY", "UY", "GY", "SR", "GF":
234
- region = "south-america"
235
-
236
- // Europe - West
237
- case "GB", "IE", "PT", "ES", "FR", "BE", "NL", "LU":
238
- region = "eu-west"
239
-
240
- // Europe - Central/East
241
- case "DE", "AT", "CH", "IT", "PL", "CZ", "SK", "HU", "RO", "BG", "SI", "HR", "BA", "RS", "ME", "AL", "MK", "GR", "CY":
242
- region = "eu-central"
243
-
244
- // Europe - North
245
- case "SE", "NO", "DK", "FI", "IS", "EE", "LV", "LT":
246
- region = "eu-west"
247
-
248
- // Asia - East
249
- case "JP", "KR", "CN", "TW", "HK", "MO":
250
- region = "asia-pacific"
251
-
252
- // Asia - Southeast
253
- case "SG", "MY", "TH", "ID", "PH", "VN", "LA", "KH", "MM", "BN":
254
- region = "asia-pacific"
255
-
256
- // Asia - South
257
- case "IN", "PK", "BD", "LK", "NP", "BT", "MV":
258
- region = "asia-pacific"
259
-
260
- // Oceania
261
- case "AU", "NZ", "FJ", "PG", "NC", "PF", "SB", "VU", "WS", "TO":
262
- region = "asia-pacific"
263
-
264
- // Middle East
265
- case "AE", "SA", "IL", "TR", "EG", "IQ", "IR", "JO", "LB", "SY", "YE", "OM", "KW", "BH", "QA":
266
- region = "eu-central"
267
-
268
- // Africa
269
- case "ZA", "NG", "KE", "GH", "TZ", "UG", "ET", "MA", "DZ", "TN", "LY", "SD", "AO", "MZ", "ZW", "BW", "NA", "ZM", "MW", "MG":
270
- region = "eu-west"
271
-
272
- default:
273
- region = "unknown"
274
- }
275
-
276
- log.Debug().
277
- Str("ip", ipStr).
278
- Str("region", region).
279
- Str("country", countryCode).
280
- Msg("[GeoIP] Region mapped successfully")
281
-
282
- return region, countryCode
155
}
156
157
// convertLeaseEntriesToRows converts LeaseEntry data from LeaseManager to leaseRow format for the app page
@@ -387,34 +259,6 @@ func convertLeaseEntriesToRows(serv *portal.RelayServer) []leaseRow {
259
link = fmt.Sprintf("//%s.%s/", lease.Name, portalHost)
260
}
261
390
- // Get GeoIP information from RemoteAddr
391
- var region string
392
- var countryCode string
393
- if leaseEntry.RemoteAddr != "" {
394
- // Extract IP from RemoteAddr (format: "ip:port")
395
- ipStr := leaseEntry.RemoteAddr
396
- if idx := strings.LastIndex(ipStr, ":"); idx != -1 {
397
- ipStr = ipStr[:idx]
398
- }
399
- log.Info().
400
- Str("lease_id", identityID).
401
- Str("remote_addr", leaseEntry.RemoteAddr).
402
- Str("extracted_ip", ipStr).
403
- Msg("[GeoIP] Processing lease RemoteAddr")
404
- region, countryCode = getRegionFromIP(ipStr)
405
- log.Info().
406
- Str("lease_id", identityID).
407
- Str("region", region).
408
- Str("country_code", countryCode).
409
- Msg("[GeoIP] Region detected")
410
- } else {
411
- log.Info().
412
- Str("lease_id", identityID).
413
- Msg("[GeoIP] No RemoteAddr available for lease")
414
- region = "unknown"
415
- countryCode = ""
416
- }
417
-
262
row := leaseRow{
263
Peer: identityID,
264
Name: name,
@@ -428,8 +272,6 @@ func convertLeaseEntriesToRows(serv *portal.RelayServer) []leaseRow {
272
StaleRed: !connected && since >= 15*time.Second,
273
Hide: meta.Hide,
274
Metadata: lease.Metadata,
431
- Region: region,
432
- CountryCode: countryCode,
275
}
276
277
if row.Hide != true {
portal/core/cryptoops/handshaker.go
-3
@@ -190,9 +190,6 @@ func (sc *SecureConnection) writeFragmentation(p []byte) (int, error) {
190
191
// Read reads and decrypts data from the underlying connection
192
func (sc *SecureConnection) Read(p []byte) (int, error) {
193
- if sc == nil || sc.conn == nil {
194
- return 0, errors.New("connection is nil")
195
- }
193
if len(sc.readBuffer.B) > 0 {
194
n := copy(p, sc.readBuffer.B)
195
copy(sc.readBuffer.B[:len(sc.readBuffer.B)-n], sc.readBuffer.B[n:])
portal/handlers.go
+1
-1
@@ -65,7 +65,7 @@ func (g *RelayServer) handleLeaseUpdateRequest(ctx *StreamContext, packet *rdver
65
var resp rdverb.LeaseUpdateResponse
66
67
// Update lease in lease manager
68
- if g.leaseManager.UpdateLease(req.Lease, ctx.ConnectionID, ctx.Connection.RemoteAddr) {
68
+ if g.leaseManager.UpdateLease(req.Lease, ctx.ConnectionID) {
69
resp.Code = rdverb.ResponseCode_RESPONSE_CODE_ACCEPTED
70
71
// Register lease connection
portal/lease.go
+2
-4
@@ -13,8 +13,7 @@ type LeaseEntry struct {
13
Lease *rdverb.Lease
14
Expires time.Time
15
LastSeen time.Time
16
- ConnectionID int64 // Store the connection ID
17
- RemoteAddr string // Store the remote IP address
16
+ ConnectionID int64 // Store the connection ID
17
}
18
19
type LeaseManager struct {
@@ -79,7 +78,7 @@ func (lm *LeaseManager) cleanupExpiredLeases() {
78
}
79
}
80
82
-func (lm *LeaseManager) UpdateLease(lease *rdverb.Lease, connectionID int64, remoteAddr string) bool {
81
+func (lm *LeaseManager) UpdateLease(lease *rdverb.Lease, connectionID int64) bool {
82
lm.leasesLock.Lock()
83
defer lm.leasesLock.Unlock()
84
@@ -129,7 +128,6 @@ func (lm *LeaseManager) UpdateLease(lease *rdverb.Lease, connectionID int64, rem
128
Expires: expires,
129
LastSeen: time.Now(),
130
ConnectionID: connectionID,
132
- RemoteAddr: remoteAddr,
131
}
132
133
// Apply default BPS limit for this lease if configured and no explicit limit set
portal/lease_test.go
+10
-10
@@ -40,12 +40,12 @@ func TestLeaseManager_NameConflict(t *testing.T) {
40
}
41
42
// First lease should succeed
43
- if !lm.UpdateLease(lease1, 1, "192.168.1.1:1234") {
43
+ if !lm.UpdateLease(lease1, 1) {
44
t.Fatal("First lease registration should succeed")
45
}
46
47
// Second lease with same name should fail (name conflict)
48
- if lm.UpdateLease(lease2, 2, "192.168.1.2:1234") {
48
+ if lm.UpdateLease(lease2, 2) {
49
t.Fatal("Second lease registration should fail due to name conflict")
50
}
51
@@ -91,12 +91,12 @@ func TestLeaseManager_SameIdentityUpdate(t *testing.T) {
91
}
92
93
// First registration
94
- if !lm.UpdateLease(lease1, 1, "192.168.1.1:1234") {
94
+ if !lm.UpdateLease(lease1, 1) {
95
t.Fatal("First lease registration should succeed")
96
}
97
98
// Update with same identity should succeed (no conflict)
99
- if !lm.UpdateLease(lease2, 1, "192.168.1.1:1234") {
99
+ if !lm.UpdateLease(lease2, 1) {
100
t.Fatal("Updating own lease should succeed")
101
}
102
@@ -139,11 +139,11 @@ func TestLeaseManager_EmptyNameAllowed(t *testing.T) {
139
Expires: time.Now().Add(10 * time.Minute).Unix(),
140
}
141
142
- if !lm.UpdateLease(lease1, 1, "192.168.1.1:1234") {
142
+ if !lm.UpdateLease(lease1, 1) {
143
t.Fatal("First lease with empty name should succeed")
144
}
145
146
- if !lm.UpdateLease(lease2, 2, "192.168.1.2:1234") {
146
+ if !lm.UpdateLease(lease2, 2) {
147
t.Fatal("Second lease with empty name should succeed (empty names don't conflict)")
148
}
149
}
@@ -177,11 +177,11 @@ func TestLeaseManager_UnnamedAllowed(t *testing.T) {
177
Expires: time.Now().Add(10 * time.Minute).Unix(),
178
}
179
180
- if !lm.UpdateLease(lease1, 1, "192.168.1.1:1234") {
180
+ if !lm.UpdateLease(lease1, 1) {
181
t.Fatal("First lease with '(unnamed)' should succeed")
182
}
183
184
- if !lm.UpdateLease(lease2, 2, "192.168.1.2:1234") {
184
+ if !lm.UpdateLease(lease2, 2) {
185
t.Fatal("Second lease with '(unnamed)' should succeed (unnamed don't conflict)")
186
}
187
}
@@ -215,11 +215,11 @@ func TestLeaseManager_UnicodeNameConflict(t *testing.T) {
215
Expires: time.Now().Add(10 * time.Minute).Unix(),
216
}
217
218
- if !lm.UpdateLease(lease1, 1, "192.168.1.1:1234") {
218
+ if !lm.UpdateLease(lease1, 1) {
219
t.Fatal("First lease with Korean name should succeed")
220
}
221
222
- if lm.UpdateLease(lease2, 2, "192.168.1.2:1234") {
222
+ if lm.UpdateLease(lease2, 2) {
223
t.Fatal("Second lease with same Korean name should fail")
224
}
225
}
portal/relay.go
+6
-8
@@ -14,9 +14,8 @@ import (
14
)
15
16
type Connection struct {
17
- conn io.ReadWriteCloser
18
- sess *yamux.Session
19
- RemoteAddr string
17
+ conn io.ReadWriteCloser
18
+ sess *yamux.Session
19
20
streams map[uint32]*yamux.Stream
21
streamsLock sync.Mutex
@@ -254,7 +253,7 @@ func (g *RelayServer) handleStream(stream *yamux.Stream, id int64, connection *C
253
}
254
}
255
257
-func (g *RelayServer) HandleConnection(conn io.ReadWriteCloser, remoteAddr string) error {
256
+func (g *RelayServer) HandleConnection(conn io.ReadWriteCloser) error {
257
log.Debug().Msg("[RelayServer] New connection received")
258
259
sess, err := yamux.Server(conn, _yamux_config)
@@ -267,10 +266,9 @@ func (g *RelayServer) HandleConnection(conn io.ReadWriteCloser, remoteAddr strin
266
g.connidCounter++
267
connID := g.connidCounter
268
connection := &Connection{
270
- conn: conn,
271
- sess: sess,
272
- RemoteAddr: remoteAddr,
273
- streams: make(map[uint32]*yamux.Stream),
269
+ conn: conn,
270
+ sess: sess,
271
+ streams: make(map[uint32]*yamux.Stream),
272
}
273
g.connections[connID] = connection
274
g.connectionsLock.Unlock()
sdk/types.go
+227
-227
@@ -1,227 +1,227 @@
1
-package sdk
2
-
3
-import (
4
- "context"
5
- "errors"
6
- "io"
7
- "net"
8
- "sync"
9
- "time"
10
-
11
- "github.com/rs/zerolog/log"
12
- "gosuda.org/portal/portal"
13
- "gosuda.org/portal/portal/core/cryptoops"
14
- "gosuda.org/portal/portal/core/proto/rdverb"
15
-)
16
-
17
-var (
18
- ErrNoAvailableRelay = errors.New("no available relay")
19
- ErrClientClosed = errors.New("client is closed")
20
- ErrListenerExists = errors.New("listener already exists for this credential")
21
- ErrRelayExists = errors.New("relay already exists")
22
- ErrRelayNotFound = errors.New("relay not found")
23
- ErrInvalidName = errors.New("lease name contains invalid characters (only alphanumeric, hyphen, underscore allowed)")
24
- ErrFailedToCreateClient = errors.New("failed to create relay client")
25
- ErrInvalidMetadata = errors.New("invalid metadata")
26
-)
27
-
28
-type ClientConfig struct {
29
- BootstrapServers []string
30
- Dialer func(context.Context, string) (io.ReadWriteCloser, error)
31
- HealthCheckInterval time.Duration // Interval for health checks (default: 10 seconds)
32
- ReconnectMaxRetries int // Maximum reconnection attempts (default: 0 = infinite)
33
- ReconnectInterval time.Duration // Interval between reconnection attempts (default: 5 seconds)
34
-}
35
-
36
-type ClientOption func(*ClientConfig)
37
-
38
-func WithBootstrapServers(servers []string) ClientOption {
39
- return func(c *ClientConfig) {
40
- c.BootstrapServers = servers
41
- }
42
-}
43
-
44
-func WithDialer(dialer func(context.Context, string) (io.ReadWriteCloser, error)) ClientOption {
45
- return func(c *ClientConfig) {
46
- c.Dialer = dialer
47
- }
48
-}
49
-
50
-func WithHealthCheckInterval(interval time.Duration) ClientOption {
51
- return func(c *ClientConfig) {
52
- c.HealthCheckInterval = interval
53
- }
54
-}
55
-
56
-func WithReconnectMaxRetries(retries int) ClientOption {
57
- return func(c *ClientConfig) {
58
- c.ReconnectMaxRetries = retries
59
- }
60
-}
61
-
62
-func WithReconnectInterval(interval time.Duration) ClientOption {
63
- return func(c *ClientConfig) {
64
- c.ReconnectInterval = interval
65
- }
66
-}
67
-
68
-type Metadata struct {
69
- Description string `json:"description"`
70
- Tags []string `json:"tags"`
71
- Thumbnail string `json:"thumbnail"`
72
- Owner string `json:"owner"`
73
- Hide bool `json:"hide"`
74
-}
75
-
76
-func (m Metadata) isEmpty() bool {
77
- return m.Description == "" &&
78
- len(m.Tags) == 0 &&
79
- m.Thumbnail == "" &&
80
- m.Owner == ""
81
-}
82
-
83
-type MetadataOption func(*Metadata)
84
-
85
-func WithDescription(description string) MetadataOption {
86
- return func(m *Metadata) {
87
- m.Description = description
88
- }
89
-}
90
-
91
-func WithTags(tags []string) MetadataOption {
92
- return func(m *Metadata) {
93
- m.Tags = tags
94
- }
95
-}
96
-
97
-func WithThumbnail(thumbnail string) MetadataOption {
98
- return func(m *Metadata) {
99
- m.Thumbnail = thumbnail
100
- }
101
-}
102
-
103
-func WithOwner(owner string) MetadataOption {
104
- return func(m *Metadata) {
105
- m.Owner = owner
106
- }
107
-}
108
-
109
-func WithHide(hide bool) MetadataOption {
110
- return func(m *Metadata) {
111
- m.Hide = hide
112
- }
113
-}
114
-
115
-type listener struct {
116
- mu sync.Mutex
117
-
118
- cred *cryptoops.Credential
119
- lease *rdverb.Lease
120
-
121
- conns map[*connection]struct{}
122
-
123
- connCh chan *connection
124
- closed bool
125
-}
126
-
127
-// Implement net.Listener interface for Listener
128
-func (l *listener) Accept() (net.Conn, error) {
129
- conn, ok := <-l.connCh
130
- if !ok {
131
- return nil, net.ErrClosed
132
- }
133
- return conn, nil
134
-}
135
-
136
-func (l *listener) Close() error {
137
- l.mu.Lock()
138
- defer l.mu.Unlock()
139
-
140
- if l.closed {
141
- return nil
142
- }
143
-
144
- l.closed = true
145
-
146
- // Close the connection channel first to prevent new connections
147
- close(l.connCh)
148
-
149
- // Close all active connections
150
- for conn := range l.conns {
151
- if err := conn.Close(); err != nil {
152
- log.Error().Err(err).Msg("[SDK] Error closing connection")
153
- }
154
- delete(l.conns, conn)
155
- }
156
-
157
- // Clear the connections map
158
- l.conns = make(map[*connection]struct{})
159
-
160
- return nil
161
-}
162
-
163
-func (l *listener) Addr() net.Addr {
164
- return addr(l.cred.ID())
165
-}
166
-
167
-type connRelay struct {
168
- addr string
169
- client *portal.RelayClient
170
- dialer func(context.Context, string) (io.ReadWriteCloser, error)
171
- stop chan struct{}
172
- stopOnce sync.Once // Ensure stop channel is closed only once
173
- mu sync.Mutex
174
-}
175
-
176
-var _ net.Conn = (*connection)(nil)
177
-
178
-type connection struct {
179
- via *connRelay
180
- localAddr string
181
- remoteAddr string
182
- conn *cryptoops.SecureConnection
183
-}
184
-
185
-func (r *connection) Read(b []byte) (n int, err error) {
186
- return r.conn.Read(b)
187
-}
188
-
189
-func (r *connection) Write(b []byte) (n int, err error) {
190
- return r.conn.Write(b)
191
-}
192
-
193
-func (r *connection) Close() error {
194
- return r.conn.Close()
195
-}
196
-
197
-func (r *connection) LocalAddr() net.Addr {
198
- return addr(r.localAddr)
199
-}
200
-
201
-func (r *connection) RemoteAddr() net.Addr {
202
- return addr(r.remoteAddr)
203
-}
204
-
205
-func (r *connection) SetDeadline(t time.Time) error {
206
- return r.conn.SetDeadline(t)
207
-}
208
-
209
-func (r *connection) SetReadDeadline(t time.Time) error {
210
- return r.conn.SetReadDeadline(t)
211
-}
212
-
213
-func (r *connection) SetWriteDeadline(t time.Time) error {
214
- return r.conn.SetWriteDeadline(t)
215
-}
216
-
217
-var _ net.Addr = (*addr)(nil)
218
-
219
-type addr string
220
-
221
-func (a addr) Network() string {
222
- return "portal"
223
-}
224
-
225
-func (a addr) String() string {
226
- return string(a)
227
-}
1
+package sdk
2
+
3
+import (
4
+ "context"
5
+ "errors"
6
+ "io"
7
+ "net"
8
+ "sync"
9
+ "time"
10
+
11
+ "github.com/rs/zerolog/log"
12
+ "gosuda.org/portal/portal"
13
+ "gosuda.org/portal/portal/core/cryptoops"
14
+ "gosuda.org/portal/portal/core/proto/rdverb"
15
+)
16
+
17
+var (
18
+ ErrNoAvailableRelay = errors.New("no available relay")
19
+ ErrClientClosed = errors.New("client is closed")
20
+ ErrListenerExists = errors.New("listener already exists for this credential")
21
+ ErrRelayExists = errors.New("relay already exists")
22
+ ErrRelayNotFound = errors.New("relay not found")
23
+ ErrInvalidName = errors.New("lease name contains invalid characters (only alphanumeric, hyphen, underscore allowed)")
24
+ ErrFailedToCreateClient = errors.New("failed to create relay client")
25
+ ErrInvalidMetadata = errors.New("invalid metadata")
26
+)
27
+
28
+type ClientConfig struct {
29
+ BootstrapServers []string
30
+ Dialer func(context.Context, string) (io.ReadWriteCloser, error)
31
+ HealthCheckInterval time.Duration // Interval for health checks (default: 10 seconds)
32
+ ReconnectMaxRetries int // Maximum reconnection attempts (default: 0 = infinite)
33
+ ReconnectInterval time.Duration // Interval between reconnection attempts (default: 5 seconds)
34
+}
35
+
36
+type ClientOption func(*ClientConfig)
37
+
38
+func WithBootstrapServers(servers []string) ClientOption {
39
+ return func(c *ClientConfig) {
40
+ c.BootstrapServers = servers
41
+ }
42
+}
43
+
44
+func WithDialer(dialer func(context.Context, string) (io.ReadWriteCloser, error)) ClientOption {
45
+ return func(c *ClientConfig) {
46
+ c.Dialer = dialer
47
+ }
48
+}
49
+
50
+func WithHealthCheckInterval(interval time.Duration) ClientOption {
51
+ return func(c *ClientConfig) {
52
+ c.HealthCheckInterval = interval
53
+ }
54
+}
55
+
56
+func WithReconnectMaxRetries(retries int) ClientOption {
57
+ return func(c *ClientConfig) {
58
+ c.ReconnectMaxRetries = retries
59
+ }
60
+}
61
+
62
+func WithReconnectInterval(interval time.Duration) ClientOption {
63
+ return func(c *ClientConfig) {
64
+ c.ReconnectInterval = interval
65
+ }
66
+}
67
+
68
+type Metadata struct {
69
+ Description string `json:"description"`
70
+ Tags []string `json:"tags"`
71
+ Thumbnail string `json:"thumbnail"`
72
+ Owner string `json:"owner"`
73
+ Hide bool `json:"hide"`
74
+}
75
+
76
+func (m Metadata) isEmpty() bool {
77
+ return m.Description == "" &&
78
+ len(m.Tags) == 0 &&
79
+ m.Thumbnail == "" &&
80
+ m.Owner == ""
81
+}
82
+
83
+type MetadataOption func(*Metadata)
84
+
85
+func WithDescription(description string) MetadataOption {
86
+ return func(m *Metadata) {
87
+ m.Description = description
88
+ }
89
+}
90
+
91
+func WithTags(tags []string) MetadataOption {
92
+ return func(m *Metadata) {
93
+ m.Tags = tags
94
+ }
95
+}
96
+
97
+func WithThumbnail(thumbnail string) MetadataOption {
98
+ return func(m *Metadata) {
99
+ m.Thumbnail = thumbnail
100
+ }
101
+}
102
+
103
+func WithOwner(owner string) MetadataOption {
104
+ return func(m *Metadata) {
105
+ m.Owner = owner
106
+ }
107
+}
108
+
109
+func WithHide(hide bool) MetadataOption {
110
+ return func(m *Metadata) {
111
+ m.Hide = hide
112
+ }
113
+}
114
+
115
+type listener struct {
116
+ mu sync.Mutex
117
+
118
+ cred *cryptoops.Credential
119
+ lease *rdverb.Lease
120
+
121
+ conns map[*connection]struct{}
122
+
123
+ connCh chan *connection
124
+ closed bool
125
+}
126
+
127
+// Implement net.Listener interface for Listener
128
+func (l *listener) Accept() (net.Conn, error) {
129
+ conn, ok := <-l.connCh
130
+ if !ok {
131
+ return nil, net.ErrClosed
132
+ }
133
+ return conn, nil
134
+}
135
+
136
+func (l *listener) Close() error {
137
+ l.mu.Lock()
138
+ defer l.mu.Unlock()
139
+
140
+ if l.closed {
141
+ return nil
142
+ }
143
+
144
+ l.closed = true
145
+
146
+ // Close the connection channel first to prevent new connections
147
+ close(l.connCh)
148
+
149
+ // Close all active connections
150
+ for conn := range l.conns {
151
+ if err := conn.Close(); err != nil {
152
+ log.Error().Err(err).Msg("[SDK] Error closing connection")
153
+ }
154
+ delete(l.conns, conn)
155
+ }
156
+
157
+ // Clear the connections map
158
+ l.conns = make(map[*connection]struct{})
159
+
160
+ return nil
161
+}
162
+
163
+func (l *listener) Addr() net.Addr {
164
+ return addr(l.cred.ID())
165
+}
166
+
167
+type connRelay struct {
168
+ addr string
169
+ client *portal.RelayClient
170
+ dialer func(context.Context, string) (io.ReadWriteCloser, error)
171
+ stop chan struct{}
172
+ stopOnce sync.Once // Ensure stop channel is closed only once
173
+ mu sync.Mutex
174
+}
175
+
176
+var _ net.Conn = (*connection)(nil)
177
+
178
+type connection struct {
179
+ via *connRelay
180
+ localAddr string
181
+ remoteAddr string
182
+ conn *cryptoops.SecureConnection
183
+}
184
+
185
+func (r *connection) Read(b []byte) (n int, err error) {
186
+ return r.conn.Read(b)
187
+}
188
+
189
+func (r *connection) Write(b []byte) (n int, err error) {
190
+ return r.conn.Write(b)
191
+}
192
+
193
+func (r *connection) Close() error {
194
+ return r.conn.Close()
195
+}
196
+
197
+func (r *connection) LocalAddr() net.Addr {
198
+ return addr(r.localAddr)
199
+}
200
+
201
+func (r *connection) RemoteAddr() net.Addr {
202
+ return addr(r.remoteAddr)
203
+}
204
+
205
+func (r *connection) SetDeadline(t time.Time) error {
206
+ return r.conn.SetDeadline(t)
207
+}
208
+
209
+func (r *connection) SetReadDeadline(t time.Time) error {
210
+ return r.conn.SetReadDeadline(t)
211
+}
212
+
213
+func (r *connection) SetWriteDeadline(t time.Time) error {
214
+ return r.conn.SetWriteDeadline(t)
215
+}
216
+
217
+var _ net.Addr = (*addr)(nil)
218
+
219
+type addr string
220
+
221
+func (a addr) Network() string {
222
+ return "portal"
223
+}
224
+
225
+func (a addr) String() string {
226
+ return string(a)
227
+}