diff --git a/adapter/router.go b/adapter/router.go index 447c272d..7a07c852 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -47,6 +47,20 @@ type Router interface { SetV2RayServer(server V2RayServer) } +type routerContextKey struct{} + +func ContextWithRouter(ctx context.Context, router Router) context.Context { + return context.WithValue(ctx, (*routerContextKey)(nil), router) +} + +func RouterFromContext(ctx context.Context) Router { + metadata := ctx.Value((*routerContextKey)(nil)) + if metadata == nil { + return nil + } + return metadata.(Router) +} + type Rule interface { Service Type() string diff --git a/constant/dhcp.go b/constant/dhcp.go new file mode 100644 index 00000000..1d9792a2 --- /dev/null +++ b/constant/dhcp.go @@ -0,0 +1,8 @@ +package constant + +import "time" + +const ( + DHCPTTL = time.Hour + DHCPTimeout = time.Minute +) diff --git a/docs/configuration/dns/server.md b/docs/configuration/dns/server.md index a4d2bbbe..7ffe1b06 100644 --- a/docs/configuration/dns/server.md +++ b/docs/configuration/dns/server.md @@ -30,16 +30,17 @@ The tag of the dns server. The address of the dns server. -| Protocol | Format | -|----------|-----------------------------| -| `System` | `local` | -| `TCP` | `tcp://1.0.0.1` | -| `UDP` | `8.8.8.8` `udp://8.8.4.4` | -| `TLS` | `tls://dns.google` | -| `HTTPS` | `https://1.1.1.1/dns-query` | -| `QUIC` | `quic://dns.adguard.com` | -| `HTTP3` | `h3://8.8.8.8/dns-query` | -| `RCode` | `rcode://refused` | +| Protocol | Format | +|----------|-------------------------------| +| `System` | `local` | +| `TCP` | `tcp://1.0.0.1` | +| `UDP` | `8.8.8.8` `udp://8.8.4.4` | +| `TLS` | `tls://dns.google` | +| `HTTPS` | `https://1.1.1.1/dns-query` | +| `QUIC` | `quic://dns.adguard.com` | +| `HTTP3` | `h3://8.8.8.8/dns-query` | +| `RCode` | `rcode://refused` | +| `DHCP` | `dhcp://auto` or `dhcp://en0` | !!! warning "" @@ -53,6 +54,10 @@ The address of the dns server. the RCode transport is often used to block queries. Use with rules and the `disable_cache` rule option. +!!! warning "" + + DHCP transport is not included by default, see [Installation](/#installation). + | RCode | Description | |-------------------|-----------------------| | `success` | `No error` | diff --git a/docs/configuration/dns/server.zh.md b/docs/configuration/dns/server.zh.md index 76471d03..e62e74dd 100644 --- a/docs/configuration/dns/server.zh.md +++ b/docs/configuration/dns/server.zh.md @@ -30,16 +30,17 @@ DNS 服务器的标签。 DNS 服务器的地址。 -| 协议 | 格式 | -|----------|-----------------------------| -| `System` | `local` | -| `TCP` | `tcp://1.0.0.1` | -| `UDP` | `8.8.8.8` `udp://8.8.4.4` | -| `TLS` | `tls://dns.google` | -| `HTTPS` | `https://1.1.1.1/dns-query` | -| `QUIC` | `quic://dns.adguard.com` | -| `HTTP3` | `h3://8.8.8.8/dns-query` | -| `RCode` | `rcode://refused` | +| 协议 | 格式 | +|----------|------------------------------| +| `System` | `local` | +| `TCP` | `tcp://1.0.0.1` | +| `UDP` | `8.8.8.8` `udp://8.8.4.4` | +| `TLS` | `tls://dns.google` | +| `HTTPS` | `https://1.1.1.1/dns-query` | +| `QUIC` | `quic://dns.adguard.com` | +| `HTTP3` | `h3://8.8.8.8/dns-query` | +| `RCode` | `rcode://refused` | +| `DHCP` | `dhcp://auto` 或 `dhcp://en0` | !!! warning "" @@ -53,6 +54,10 @@ DNS 服务器的地址。 RCode 传输层传输层常用于屏蔽请求. 与 DNS 规则和 `disable_cache` 规则选项一起使用。 +!!! warning "" + + 默认安装不包含 DHCP 传输层,请参阅 [安装](/zh/#_2)。 + | RCode | 描述 | |-------------------|----------| | `success` | `无错误` | diff --git a/docs/index.md b/docs/index.md index abfd78e8..e78dc7c8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -24,8 +24,9 @@ go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@lat | Build Tag | Description | |------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `with_quic` | Build with QUIC support, see [QUIC and HTTP3 dns transports](./configuration/dns/server), [Naive inbound](./configuration/inbound/naive), [Hysteria Inbound](./configuration/inbound/hysteria), [Hysteria Outbound](./configuration/outbound/hysteria) and [V2Ray Transport#QUIC](./configuration/shared/v2ray-transport#quic). | +| `with_quic` | Build with QUIC support, see [QUIC and HTTP3 DNS transports](./configuration/dns/server), [Naive inbound](./configuration/inbound/naive), [Hysteria Inbound](./configuration/inbound/hysteria), [Hysteria Outbound](./configuration/outbound/hysteria) and [V2Ray Transport#QUIC](./configuration/shared/v2ray-transport#quic). | | `with_grpc` | Build with standard gRPC support, see [V2Ray Transport#gRPC](./configuration/shared/v2ray-transport#grpc). | +| `with_dhcp` | Build with DHCP support, see [DHCP DNS transport](./configuration/dns/server). | | `with_wireguard` | Build with WireGuard support, see [WireGuard outbound](./configuration/outbound/wireguard). | | `with_shadowsocksr` | Build with ShadowsocksR support, see [ShadowsocksR outbound](./configuration/outbound/shadowsocksr). | | `with_ech` | Build with TLS ECH extension support for TLS outbound, see [TLS](./configuration/shared/tls#ech). | diff --git a/docs/index.zh.md b/docs/index.zh.md index 79f72ee5..27011d9b 100644 --- a/docs/index.zh.md +++ b/docs/index.zh.md @@ -26,6 +26,7 @@ go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@lat |------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `with_quic` | 启用 QUIC 支持,参阅 [QUIC 和 HTTP3 DNS 传输层](./configuration/dns/server),[Naive 入站](./configuration/inbound/naive),[Hysteria 入站](./configuration/inbound/hysteria),[Hysteria 出站](./configuration/outbound/hysteria) 和 [V2Ray 传输层#QUIC](./configuration/shared/v2ray-transport#quic)。 | | `with_grpc` | 启用标准 gRPC 支持,参阅 [V2Ray 传输层#gRPC](./configuration/shared/v2ray-transport#grpc)。 | +| `with_dhcp` | 启用 DHCP 支持,参阅 [DHCP DNS 传输层](./configuration/dns/server)。 | | `with_wireguard` | 启用 WireGuard 支持,参阅 [WireGuard 出站](./configuration/outbound/wireguard)。 | | `with_shadowsocksr` | 启用 ShadowsocksR 支持,参阅 [ShadowsocksR 出站](./configuration/outbound/shadowsocksr)。 | | `with_ech` | 启用 TLS ECH 扩展支持,参阅 [TLS](./configuration/shared/tls#ech)。 | diff --git a/go.mod b/go.mod index 29b7f747..03e14254 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/go-chi/render v1.0.2 github.com/gofrs/uuid v4.4.0+incompatible github.com/hashicorp/yamux v0.1.1 + github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/mholt/acmez v1.0.4 github.com/miekg/dns v1.1.50 @@ -59,6 +60,7 @@ require ( github.com/google/btree v1.0.1 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/josharian/native v1.1.0 // indirect github.com/klauspost/compress v1.15.15 // indirect github.com/klauspost/cpuid/v2 v2.1.1 // indirect github.com/libdns/libdns v0.2.1 // indirect @@ -72,6 +74,7 @@ require ( github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/mod v0.6.0 // indirect diff --git a/go.sum b/go.sum index 1e732c41..11fa7bf1 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= @@ -43,15 +44,32 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 h1:Z72DOke2yOK0Ms4Z2LK1E1OrRJXOxSj5DllTz2FYTRg= +github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8/go.mod h1:m5WMe03WCvWcXjRnhvaAbAAXdCnu20J5P+mmH44ZzpE= +github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= +github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= +github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= +github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -65,6 +83,13 @@ github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis= github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= +github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= +github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= +github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= +github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= +github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= +github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mholt/acmez v1.0.4 h1:N3cE4Pek+dSolbsofIkAYz6H1d3pE+2G0os7QHslf80= github.com/mholt/acmez v1.0.4/go.mod h1:qFGLZ4u+ehWINeJZjzPlsnjJBCPAADWTcIqE/7DAYQY= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= @@ -121,6 +146,8 @@ github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+V github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY= github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo= github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -130,11 +157,14 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/u-root/uio v0.0.0-20221213070652-c3537552635f h1:dpx1PHxYqAnXzbryJrWP1NQLzEjwcVgFLhkknuFQ7ww= +github.com/u-root/uio v0.0.0-20221213070652-c3537552635f/go.mod h1:IogEAUBXDEwX7oR/BMmCctShYs80ql4hF0ySdzGxf7E= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -155,6 +185,7 @@ go4.org/netipx v0.0.0-20230125063823-8449b0a6169f/go.mod h1:tgPU4N2u9RByaTN3NC2p golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= @@ -166,7 +197,14 @@ golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -178,10 +216,20 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -189,6 +237,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -207,6 +256,7 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqG golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= diff --git a/include/dhcp.go b/include/dhcp.go new file mode 100644 index 00000000..0e4b4ccf --- /dev/null +++ b/include/dhcp.go @@ -0,0 +1,5 @@ +//go:build with_dhcp + +package include + +import _ "github.com/sagernet/sing-box/transport/dhcp" diff --git a/include/dhcp_stub.go b/include/dhcp_stub.go new file mode 100644 index 00000000..fe175d07 --- /dev/null +++ b/include/dhcp_stub.go @@ -0,0 +1,18 @@ +//go:build !with_dhcp + +package include + +import ( + "context" + + "github.com/sagernet/sing-dns" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + N "github.com/sagernet/sing/common/network" +) + +func init() { + dns.RegisterTransport([]string{"dhcp"}, func(ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) { + return nil, E.New(`DHCP is not included in this build, rebuild with -tags with_dhcp`) + }) +} diff --git a/route/router.go b/route/router.go index e3554c4d..785a0b24 100644 --- a/route/router.go +++ b/route/router.go @@ -159,6 +159,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route transportTags[i] = tag transportTagMap[tag] = true } + ctx = adapter.ContextWithRouter(ctx, router) for { lastLen := len(dummyTransportMap) for i, server := range dnsOptions.Servers { @@ -173,7 +174,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route detour = dialer.NewDetour(router, server.Detour) } switch server.Address { - case "local", "rcode": + case "local": default: serverURL, _ := url.Parse(server.Address) var serverAddress string @@ -193,11 +194,15 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route } else { continue } - } else if notIpAddress != nil && (serverURL == nil || serverURL.Scheme != "rcode") { - return nil, E.New("parse dns server[", tag, "]: missing address_resolver") + } else if notIpAddress != nil { + switch serverURL.Scheme { + case "rcode", "dhcp": + default: + return nil, E.New("parse dns server[", tag, "]: missing address_resolver") + } } } - transport, err := dns.CreateTransport(ctx, logFactory.NewLogger(F.ToString("dns/transport[", i, "]")), detour, server.Address) + transport, err := dns.CreateTransport(ctx, logFactory.NewLogger(F.ToString("dns/transport[", tag, "]")), detour, server.Address) if err != nil { return nil, E.Cause(err, "parse dns server[", tag, "]") } @@ -392,14 +397,20 @@ func (r *Router) Start() error { return err } } - for _, rule := range r.rules { - err := rule.Start() + if r.interfaceMonitor != nil { + err := r.interfaceMonitor.Start() if err != nil { return err } } - for _, rule := range r.dnsRules { - err := rule.Start() + if r.networkMonitor != nil { + err := r.networkMonitor.Start() + if err != nil { + return err + } + } + if r.packageManager != nil { + err := r.packageManager.Start() if err != nil { return err } @@ -424,22 +435,22 @@ func (r *Router) Start() error { r.geositeCache = nil r.geositeReader = nil } - if r.interfaceMonitor != nil { - err := r.interfaceMonitor.Start() + for i, rule := range r.rules { + err := rule.Start() if err != nil { - return err + return E.Cause(err, "initialize rule[", i, "]") } } - if r.networkMonitor != nil { - err := r.networkMonitor.Start() + for i, rule := range r.dnsRules { + err := rule.Start() if err != nil { - return err + return E.Cause(err, "initialize DNS rule[", i, "]") } } - if r.packageManager != nil { - err := r.packageManager.Start() + for i, transport := range r.transports { + err := transport.Start() if err != nil { - return err + return E.Cause(err, "initialize DNS server[", i, "]") } } return nil @@ -458,6 +469,12 @@ func (r *Router) Close() error { return err } } + for _, transport := range r.transports { + err := transport.Close() + if err != nil { + return err + } + } return common.Close( common.PtrOrNil(r.geoIPReader), r.interfaceMonitor, diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go new file mode 100644 index 00000000..e341b9c6 --- /dev/null +++ b/transport/dhcp/server.go @@ -0,0 +1,262 @@ +package dhcp + +import ( + "context" + "net" + "net/netip" + "net/url" + "os" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/task" + "github.com/sagernet/sing/common/x/list" + + "github.com/insomniacslk/dhcp/dhcpv4" + mDNS "github.com/miekg/dns" +) + +func init() { + dns.RegisterTransport([]string{"dhcp"}, NewTransport) +} + +type Transport struct { + ctx context.Context + router adapter.Router + logger logger.Logger + interfaceName string + autoInterface bool + interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback] + transports []dns.Transport + updateAccess sync.Mutex + updatedAt time.Time +} + +func NewTransport(ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) { + linkURL, err := url.Parse(link) + if err != nil { + return nil, err + } + if linkURL.Host == "" { + return nil, E.New("missing interface name for DHCP") + } + router := adapter.RouterFromContext(ctx) + if router == nil { + return nil, E.New("missing router in context") + } + transport := &Transport{ + ctx: ctx, + router: router, + logger: logger, + interfaceName: linkURL.Host, + autoInterface: linkURL.Host == "auto", + } + return transport, nil +} + +func (t *Transport) Start() error { + err := t.fetchServers() + if err != nil { + return err + } + if t.autoInterface { + t.interfaceCallback = t.router.InterfaceMonitor().RegisterCallback(t.interfaceUpdated) + } + return nil +} + +func (t *Transport) Close() error { + if t.interfaceCallback != nil { + t.router.InterfaceMonitor().UnregisterCallback(t.interfaceCallback) + } + return nil +} + +func (t *Transport) Raw() bool { + return true +} + +func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + err := t.fetchServers() + if err != nil { + return nil, err + } + + if len(t.transports) == 0 { + return nil, E.New("dhcp: empty DNS servers from response") + } + + var response *mDNS.Msg + for _, transport := range t.transports { + response, err = transport.Exchange(ctx, message) + if err == nil { + return response, nil + } + } + return nil, err +} + +func (t *Transport) fetchInterface() (*net.Interface, error) { + interfaceName := t.interfaceName + if t.autoInterface { + if t.router.NetworkMonitor() == nil { + return nil, E.New("missing monitor for auto DHCP, set route.auto_detect_interface") + } + interfaceName = t.router.InterfaceMonitor().DefaultInterfaceName(netip.Addr{}) + } + if interfaceName == "" { + return nil, E.New("missing default interface") + } + return net.InterfaceByName(interfaceName) +} + +func (t *Transport) fetchServers() error { + if time.Since(t.updatedAt) < C.DHCPTTL { + return nil + } + t.updateAccess.Lock() + defer t.updateAccess.Unlock() + if time.Since(t.updatedAt) < C.DHCPTTL { + return nil + } + return t.updateServers() +} + +func (t *Transport) updateServers() error { + iface, err := t.fetchInterface() + if err != nil { + return E.Cause(err, "dhcp: prepare interface") + } + + t.logger.Info("dhcp: query DNS servers on ", iface.Name) + fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout) + err = t.fetchServers0(fetchCtx, iface) + cancel() + if err != nil { + return err + } else if len(t.transports) == 0 { + return E.New("dhcp: empty DNS servers response") + } else { + t.updatedAt = time.Now() + return nil + } +} + +func (t *Transport) interfaceUpdated(int) error { + return t.updateServers() +} + +func (t *Transport) fetchServers0(ctx context.Context, iface *net.Interface) error { + var listener net.ListenConfig + listener.Control = control.Append(listener.Control, control.BindToInterfaceFunc(t.router.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int) { + return iface.Name, iface.Index + })) + listener.Control = control.Append(listener.Control, control.ReuseAddr()) + packetConn, err := listener.ListenPacket(t.ctx, "udp4", "0.0.0.0:68") + if err != nil { + return err + } + defer packetConn.Close() + + discovery, err := dhcpv4.NewDiscovery(iface.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(dhcpv4.OptionDomainNameServer)) + if err != nil { + return err + } + + _, err = packetConn.WriteTo(discovery.ToBytes(), &net.UDPAddr{IP: net.IPv4bcast, Port: 67}) + if err != nil { + return err + } + + var group task.Group + group.Append0(func(ctx context.Context) error { + return t.fetchServersResponse(iface, packetConn, discovery.TransactionID) + }) + group.Cleanup(func() { + packetConn.Close() + }) + return group.Run(ctx) +} + +func (t *Transport) fetchServersResponse(iface *net.Interface, packetConn net.PacketConn, transactionID dhcpv4.TransactionID) error { + _buffer := buf.StackNewSize(dhcpv4.MaxMessageSize) + defer common.KeepAlive(_buffer) + buffer := common.Dup(_buffer) + defer buffer.Release() + + for { + _, _, err := buffer.ReadPacketFrom(packetConn) + if err != nil { + return err + } + + dhcpPacket, err := dhcpv4.FromBytes(buffer.Bytes()) + if err != nil { + t.logger.Trace("dhcp: parse DHCP response: ", err) + return err + } + + if dhcpPacket.MessageType() != dhcpv4.MessageTypeOffer { + t.logger.Trace("dhcp: expected OFFER response, but got ", dhcpPacket.MessageType()) + continue + } + + if dhcpPacket.TransactionID != transactionID { + t.logger.Trace("dhcp: expected transaction ID ", transactionID, ", but got ", dhcpPacket.TransactionID) + continue + } + + dns := dhcpPacket.DNS() + if len(dns) == 0 { + return nil + } + + var addrs []netip.Addr + for _, ip := range dns { + addr, _ := netip.AddrFromSlice(ip) + addrs = append(addrs, addr.Unmap()) + } + return t.recreateServers(iface, addrs) + } +} + +func (t *Transport) recreateServers(iface *net.Interface, serverAddrs []netip.Addr) error { + if len(serverAddrs) > 0 { + t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, func(it netip.Addr) string { + return it.String() + }), ","), "]") + } + + serverDialer := dialer.NewDefault(t.router, option.DialerOptions{ + BindInterface: iface.Name, + UDPFragmentDefault: true, + }) + var transports []dns.Transport + for _, serverAddr := range serverAddrs { + serverTransport, err := dns.NewUDPTransport(t.ctx, serverDialer, M.Socksaddr{Addr: serverAddr, Port: 53}) + if err != nil { + return err + } + transports = append(transports, serverTransport) + } + t.transports = transports + return nil +} + +func (t *Transport) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) { + return nil, os.ErrInvalid +}