better picking of main local-ip based on gateway-ip
Massimo Melina committed
May 8, 2024 at 23:56 UTC
2a62b4b54e15c6f6ab6ed16f0623fd88f9c87bc3
3 files changed
+14
-11
admin/src/InternetPage.ts
+6
-5
@@ -35,10 +35,11 @@ export default function InternetPage() {
35
h && s ? 'error' : h || s ? 'warning' : 'success')
36
type GetNat = Awaited<ReturnType<typeof getNatInfo>>
37
const nat = useApiEx<GetNat>('get_nat', {}, { timeout: 20 })
38
+ const { data: publicIps } = useApiEx('get_public_ips')
39
const { data } = nat
40
const port = data?.internalPort
41
const wrongMap = data?.mapped && data.mapped.private.port !== port && data.mapped.private.port
41
- const doubleNat = data?.externalIp && data?.publicIps && !data.publicIps.includes(data.externalIp)
42
+ const doubleNat = data?.externalIp && publicIps && !publicIps.includes(data.externalIp)
43
const verifyAgain = useRequestRender()
44
useEffect(() => {
45
if (verifyAgain.state) // skip first
@@ -286,8 +287,8 @@ export default function InternetPage() {
287
288
function networkBox() {
289
if (nat.error) return nat.element
289
- if (!data) return h(CircularProgress)
290
- const direct = data?.publicIps.includes(data?.localIp!)
290
+ if (!data || !publicIps) return h(CircularProgress)
291
+ const direct = publicIps.includes(data?.localIp!)
292
return h(Flex, { justifyContent: 'space-around' },
293
h(Device, { name: "Server", icon: direct ? Storage : HomeWorkTwoTone, color: localColor, ip: data?.localIp,
294
below: port && h(Box, { fontSize: 'smaller' }, "port ", port),
@@ -301,13 +302,13 @@ export default function InternetPage() {
302
"port ", wrongMap ? 'is wrong' : data?.externalPort || "unknown"),
303
}),
304
h(DataLine),
304
- h(Device, { name: "Internet", icon: PublicTwoTone, ip: data?.publicIps,
305
+ h(Device, { name: "Internet", icon: PublicTwoTone, ip: publicIps,
306
color: checkResult ? 'success' : checkResult === false ? 'error' : doubleNat ? 'warning' : undefined,
307
below: checking ? h(LinearProgress, { sx: { height: '1em' } }) : h(Box, { fontSize: 'smaller' },
308
doubleNat && h(LinkBtn, { display: 'block', onClick: () => alertDialog(MSG_ISP, 'warning') }, "Double NAT"),
309
checkResult ? "Working!" : checkResult === false ? "Failed!" : '',
310
' ',
310
- data?.publicIps.length > 0 && data.internalPort && h(LinkBtn, { onClick: () => verify() }, "Verify")
311
+ publicIps.length > 0 && data.internalPort && h(LinkBtn, { onClick: () => verify() }, "Verify")
312
)
313
}),
314
)
src/api.net.ts
+4
-3
@@ -15,6 +15,7 @@ import { selfCheck } from './selfCheck'
15
16
const apis: ApiHandlers = {
17
get_nat: getNatInfo,
18
+ get_public_ips: getPublicIps,
19
20
async check_domain({ domain }) {
21
apiAssertTypes({ string: domain })
@@ -60,15 +61,15 @@ const apis: ApiHandlers = {
61
if (url)
62
return await selfCheck(url)
63
|| new ApiError(HTTP_SERVICE_UNAVAILABLE)
63
- const nat = await getNatInfo()
64
- if (!nat.publicIps.length)
64
+ const [publicIps, nat] = await Promise.all([getPublicIps(), getNatInfo()])
65
+ if (!publicIps.length)
66
return new ApiError(HTTP_FAILED_DEPENDENCY, 'cannot detect public ip')
67
if (!nat.internalPort)
68
return new ApiError(HTTP_FAILED_DEPENDENCY, 'no internal port')
69
const finalPort = nat.externalPort || nat.internalPort
70
const proto = nat.proto || (getCertObject() ? 'https' : 'http')
71
const defPort = proto === 'https' ? 443 : 80
71
- const results = onlyTruthy(await promiseBestEffort(nat.publicIps.map(ip =>
72
+ const results = onlyTruthy(await promiseBestEffort(publicIps.map(ip =>
73
selfCheck(`${proto}://${ip}${finalPort === defPort ? '' : ':' + finalPort}`) )))
74
return results.length ? results : new ApiError(HTTP_SERVICE_UNAVAILABLE)
75
},
src/nat.ts
+4
-3
@@ -59,11 +59,10 @@ export const getPublicIps = debounceAsync(async () => {
59
if (!validIps.length) throw "no good"
60
return validIps
61
}) )))
62
- return _.uniq(ips.flat())
62
+ return defaultBaseUrl.publicIps = _.uniq(ips.flat())
63
}, 0, { retain: 10 * MINUTE })
64
65
export const getNatInfo = debounceAsync(async () => {
66
- const gettingIps = getPublicIps() // don't wait, do it in parallel
66
const res = await haveTimeout(10_000, upnpClient.getGateway()).catch(() => null)
67
const status = await getServerStatus()
68
const mappings = res && await haveTimeout(5_000, upnpClient.getMappings()).catch(() => null)
@@ -74,12 +73,13 @@ export const getNatInfo = debounceAsync(async () => {
73
const internalPort = status?.https?.listening && status.https.port || status?.http?.listening && status.http.port || undefined
74
const mapped = _.find(mappings, x => x.private.host === localIp && x.private.port === internalPort)
75
const externalPort = mapped?.public.port
76
+ if (localIp)
77
+ defaultBaseUrl.localIp = localIp
78
defaultBaseUrl.port = externalPort || internalPort || 0
79
return {
80
upnp: Boolean(res),
81
localIp,
82
gatewayIp,
82
- publicIps: defaultBaseUrl.publicIps = await gettingIps,
83
externalIp: defaultBaseUrl.externalIp,
84
mapped,
85
mapped80: _.find(mappings, x => x.private.host === localIp && x.private.port === 80 && x.public.port === 80),
@@ -88,6 +88,7 @@ export const getNatInfo = debounceAsync(async () => {
88
proto: status?.https?.listening ? 'https' : status?.http?.listening ? 'http' : '',
89
}
90
})
91
+getNatInfo()
92
93
function findGateway(): Promise<string | undefined> {
94
return new Promise((resolve, reject) =>