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 20export const isOnSameLan = async ({cluster_name, nodeIpsExtHost, clusterVip}: { 22 nodeIpsExtHost?: string 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)) { 37 const {address: clusterExtIp} = await dnsLookup(nodeIpsExtHost, {family: 4}) 38 const inLan = myExtIp === clusterExtIp 41 } catch (err: unknown) { 42 console.warn('isOnSameLan detection failed, defaulting to external:', (err as Error)?.message || err) 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)) { 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) 60 return (tailnetCached = false)