🌳
pt0/serverF/isOnSameLanF.mts
1import { getMyCfExtIp } from './cfIpF.mts'
2import { dnsLookup } from './dnsF.mts'
4import * as os from 'os'
6const isIpInLocalSubnet = (targetIp: string) => {
7 const ifaces = os.networkInterfaces()
8 const toInt = (ip: string) => ip.split('.').reduce((acc, oct) => (acc << 8) + parseInt(oct), 0) >>> 0
9 const targetInt = toInt(targetIp)
10 for (const [name, addrs] of Object.entries(ifaces)) {
11 if (!addrs || name === 'lo' || name === 'lo0') continue
12 for (const a of addrs) {
13 if (a.family !== 'IPv4' || a.internal) continue
14 if ((toInt(a.address) & toInt(a.netmask)) === (targetInt & toInt(a.netmask))) return true
15 }
16 }
17 return false
20export const isOnSameLan = async ({cluster_name, nodeIpsExtHost, clusterVip}: {
21 cluster_name: string
22 nodeIpsExtHost?: string
23 clusterVip?: string
24}) => {
25 if (getLanCache(cluster_name) !== undefined) return getLanCache(cluster_name)
26 if (!nodeIpsExtHost || cluster_name === 'minik') return false
28 // same-site clusters share a WAN egress IP (ext-ip check below false-positives for them),
29 // so clusterVip subnet membership is a required condition, not just a fast-path negative
30 if (!clusterVip || !isIpInLocalSubnet(clusterVip)) {
31 setLanCache(cluster_name, false)
32 return false
33 }
35 try {
36 const myExtIp = await getMyCfExtIp()
37 const {address: clusterExtIp} = await dnsLookup(nodeIpsExtHost, {family: 4})
38 const inLan = myExtIp === clusterExtIp
39 setLanCache(cluster_name, inLan)
40 return inLan
41 } catch (err: unknown) {
42 console.warn('isOnSameLan detection failed, defaulting to external:', (err as Error)?.message || err)
43 setLanCache(cluster_name, false)
44 return false
45 }
48let tailnetCached: boolean | undefined
49export const isOnTailnet = () => {
50 if (tailnetCached !== undefined) return tailnetCached
51 const ifaces = os.networkInterfaces()
52 for (const addrs of Object.values(ifaces)) {
53 if (!addrs) continue
54 for (const a of addrs) {
55 if (a.family !== 'IPv4' || a.internal) continue
56 const [first, second] = a.address.split('.').map(Number)
57 if (first === 100 && second >= 64 && second <= 127) return (tailnetCached = true)
58 }
59 }
60 return (tailnetCached = false)