{"record":{"id":"5bc2d9ab4f287c58","repo":"XTLS/Xray-core","slug":"the-source-address-is-not-from-local-machine","errorCode":null,"errorMessage":"the source address is not from local machine.","messagePattern":"the source address is not from local machine\\.","errorType":"exception","errorClass":"ErrNotLocal","httpStatus":null,"severity":"error","filePath":"common/net/net.go","lineNumber":22,"sourceCode":"import (\n\t\"net\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\n// defines the maximum time an idle TCP session can survive in the tunnel, so\n// it should be consistent across HTTP versions and with other transports.\nconst ConnIdleTimeout = 300 * time.Second\n\n// consistent with quic-go\nconst QuicgoH3KeepAlivePeriod = 10 * time.Second\n\n// consistent with chrome\nconst ChromeH2KeepAlivePeriod = 45 * time.Second\n\nvar ErrNotLocal = errors.New(\"the source address is not from local machine.\")\n\ntype localIPCacheEntry struct {\n\taddrs      []net.Addr\n\tlastUpdate time.Time\n}\n\nvar localIPCache = atomic.Pointer[localIPCacheEntry]{}\n\nfunc IsLocal(ip net.IP) (bool, error) {\n\tvar addrs []net.Addr\n\tif entry := localIPCache.Load(); entry == nil || time.Since(entry.lastUpdate) > time.Minute {\n\t\tvar err error\n\t\taddrs, err = net.InterfaceAddrs()\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tlocalIPCache.Store(&localIPCacheEntry{\n\t\t\taddrs:      addrs,","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/common/net/net.go#L4-L40","documentation":"ErrNotLocal is returned by net.IsLocal when the given IP does not match any address on the local machine's interfaces. The library uses it to verify that a dial/request source actually originates from this host (spoofing/loopback security check in transparent proxying).","triggerScenarios":"Calling IsLocal(ip) with a remote peer IP, an IP from an interface that went down, or an IP whose interface list changed after the 1-minute localIPCache was populated is refreshed (it does refresh on mismatch). Typically triggered in transparent proxy handlers validating the original source of a connection.","commonSituations":"Misconfigured transparent proxy where the sniffed source is the LAN client rather than the local machine, containers/VMs whose interfaces change dynamically, or NAT scenarios where the source address was rewritten.","solutions":["Check the address actually belongs to a local interface with `ip addr` / `ipconfig` and fix routing/NAT so the original source reaches the proxy unmodified","If operating as a gateway for other machines, disable or adapt the local-source check rather than forcing traffic through it","Verify you are not passing the remote destination IP instead of the source IP to IsLocal"],"exampleFix":"// before\nif ok, _ := net.IsLocal(srcIP); !ok {\n    return net.ErrNotLocal\n}\n\n// after (gateway mode: allow LAN clients)\nif ok, _ := net.IsLocal(srcIP); !ok && !gatewayMode {\n    return net.ErrNotLocal\n}","handlingStrategy":"validation","validationCode":"if ok, err := net.IsLocal(ip); err == nil && ok {\n    // proceed: source is genuinely local\n} else {\n    return net.ErrNotLocal\n}","typeGuard":null,"tryCatchPattern":"if errors.Is(err, net.ErrNotLocal) { /* gateway mode: allow; strict mode: drop connection */ }","preventionTips":["errors.Is against the exported ErrNotLocal sentinel","Ensure NAT preserves true source IPs to the proxy","Decide gateway vs local mode explicitly in config"],"tags":["network","security","transparent-proxy","validation"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}