mirror of
https://github.com/m13253/dns-over-https.git
synced 2026-03-30 22:55:39 +00:00
Compare commits
15 Commits
m13253/res
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82c5f0d327 | ||
|
|
602b3d6322 | ||
|
|
db0bd43256 | ||
|
|
06e3d67f79 | ||
|
|
d27aef852d | ||
|
|
6c561eb412 | ||
|
|
381bf28a69 | ||
|
|
0b0651a015 | ||
|
|
3130a747f8 | ||
|
|
fe9f9f9ad2 | ||
|
|
00c6af00ed | ||
|
|
04f3e029ac | ||
|
|
87b3eedded | ||
|
|
c57a45deaa | ||
|
|
dfba0c36c5 |
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.24.3
|
||||
go-version: 1.25.6
|
||||
id: go
|
||||
|
||||
- name: Check out repository
|
||||
|
||||
@@ -90,6 +90,29 @@ func NewClient(conf *config.Config) (c *Client, err error) {
|
||||
Net: "tcp",
|
||||
Timeout: time.Duration(conf.Other.Timeout) * time.Second,
|
||||
}
|
||||
|
||||
if c.conf.Other.Interface != "" {
|
||||
localV4, localV6, err := c.getInterfaceIPs()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get interface IPs for %s: %v", c.conf.Other.Interface, err)
|
||||
}
|
||||
var localAddr net.IP
|
||||
if localV4 != nil {
|
||||
localAddr = localV4
|
||||
} else {
|
||||
localAddr = localV6
|
||||
}
|
||||
|
||||
c.udpClient.Dialer = &net.Dialer{
|
||||
Timeout: time.Duration(conf.Other.Timeout) * time.Second,
|
||||
LocalAddr: &net.UDPAddr{IP: localAddr},
|
||||
}
|
||||
c.tcpClient.Dialer = &net.Dialer{
|
||||
Timeout: time.Duration(conf.Other.Timeout) * time.Second,
|
||||
LocalAddr: &net.TCPAddr{IP: localAddr},
|
||||
}
|
||||
}
|
||||
|
||||
for _, addr := range conf.Listen {
|
||||
c.udpServers = append(c.udpServers, &dns.Server{
|
||||
Addr: addr,
|
||||
@@ -120,6 +143,38 @@ func NewClient(conf *config.Config) (c *Client, err error) {
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
var d net.Dialer
|
||||
if c.conf.Other.Interface != "" {
|
||||
localV4, localV6, err := c.getInterfaceIPs()
|
||||
if err != nil {
|
||||
log.Printf("Bootstrap dial warning: %v", err)
|
||||
} else {
|
||||
numServers := len(c.bootstrap)
|
||||
bootstrap := c.bootstrap[rand.Intn(numServers)]
|
||||
host, _, _ := net.SplitHostPort(bootstrap)
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
if ip.To4() != nil {
|
||||
if localV4 != nil {
|
||||
if strings.HasPrefix(network, "udp") {
|
||||
d.LocalAddr = &net.UDPAddr{IP: localV4}
|
||||
} else {
|
||||
d.LocalAddr = &net.TCPAddr{IP: localV4}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if localV6 != nil {
|
||||
if strings.HasPrefix(network, "udp") {
|
||||
d.LocalAddr = &net.UDPAddr{IP: localV6}
|
||||
} else {
|
||||
d.LocalAddr = &net.TCPAddr{IP: localV6}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
conn, err := d.DialContext(ctx, network, bootstrap)
|
||||
return conn, err
|
||||
}
|
||||
}
|
||||
numServers := len(c.bootstrap)
|
||||
bootstrap := c.bootstrap[rand.Intn(numServers)]
|
||||
conn, err := d.DialContext(ctx, network, bootstrap)
|
||||
@@ -235,14 +290,72 @@ func (c *Client) newHTTPClient() error {
|
||||
if c.httpTransport != nil {
|
||||
c.httpTransport.CloseIdleConnections()
|
||||
}
|
||||
dialer := &net.Dialer{
|
||||
|
||||
localV4, localV6, err := c.getInterfaceIPs()
|
||||
if err != nil {
|
||||
log.Printf("Interface binding error: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
baseDialer := &net.Dialer{
|
||||
Timeout: time.Duration(c.conf.Other.Timeout) * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
// DualStack: true,
|
||||
Resolver: c.bootstrapResolver,
|
||||
Resolver: c.bootstrapResolver,
|
||||
}
|
||||
|
||||
c.httpTransport = &http.Transport{
|
||||
DialContext: dialer.DialContext,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if c.conf.Other.Interface == "" {
|
||||
return baseDialer.DialContext(ctx, network, addr)
|
||||
}
|
||||
|
||||
if network == "tcp4" && localV4 != nil {
|
||||
d := *baseDialer
|
||||
d.LocalAddr = &net.TCPAddr{IP: localV4}
|
||||
return d.DialContext(ctx, network, addr)
|
||||
}
|
||||
if network == "tcp6" && localV6 != nil {
|
||||
d := *baseDialer
|
||||
d.LocalAddr = &net.TCPAddr{IP: localV6}
|
||||
return d.DialContext(ctx, network, addr)
|
||||
}
|
||||
|
||||
// Manual Dual-Stack: Resolve host and try compatible families sequentially
|
||||
host, port, _ := net.SplitHostPort(addr)
|
||||
ips, err := c.bootstrapResolver.LookupIPAddr(ctx, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for _, ip := range ips {
|
||||
d := *baseDialer
|
||||
targetAddr := net.JoinHostPort(ip.String(), port)
|
||||
|
||||
if ip.IP.To4() != nil {
|
||||
if localV4 == nil {
|
||||
continue
|
||||
}
|
||||
d.LocalAddr = &net.TCPAddr{IP: localV4}
|
||||
} else {
|
||||
if localV6 == nil {
|
||||
continue
|
||||
}
|
||||
d.LocalAddr = &net.TCPAddr{IP: localV6}
|
||||
}
|
||||
|
||||
conn, err := d.DialContext(ctx, "tcp", targetAddr)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
lastErr = err
|
||||
}
|
||||
|
||||
if lastErr != nil {
|
||||
return nil, lastErr
|
||||
}
|
||||
return nil, fmt.Errorf("connection to %s failed: no matching local/remote IP families on interface %s", addr, c.conf.Other.Interface)
|
||||
},
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
MaxIdleConns: 100,
|
||||
@@ -251,15 +364,18 @@ func (c *Client) newHTTPClient() error {
|
||||
TLSHandshakeTimeout: time.Duration(c.conf.Other.Timeout) * time.Second,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.conf.Other.TLSInsecureSkipVerify},
|
||||
}
|
||||
|
||||
if c.conf.Other.NoIPv6 {
|
||||
originalDial := c.httpTransport.DialContext
|
||||
c.httpTransport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
if strings.HasPrefix(network, "tcp") {
|
||||
network = "tcp4"
|
||||
}
|
||||
return dialer.DialContext(ctx, network, address)
|
||||
return originalDial(ctx, network, address)
|
||||
}
|
||||
}
|
||||
err := http2.ConfigureTransport(c.httpTransport)
|
||||
|
||||
err = http2.ConfigureTransport(c.httpTransport)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -485,3 +601,38 @@ func (c *Client) findClientIP(w dns.ResponseWriter, r *dns.Msg) (ednsClientAddre
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getInterfaceIPs returns the first valid IPv4 and IPv6 addresses found on the interface
|
||||
func (c *Client) getInterfaceIPs() (v4, v6 net.IP, err error) {
|
||||
if c.conf.Other.Interface == "" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
ifi, err := net.InterfaceByName(c.conf.Other.Interface)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
addrs, err := ifi.Addrs()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
ip, _, err := net.ParseCIDR(addr.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
if v4 == nil {
|
||||
v4 = ip4
|
||||
}
|
||||
} else {
|
||||
if v6 == nil && !c.conf.Other.NoIPv6 {
|
||||
v6 = ip
|
||||
}
|
||||
}
|
||||
}
|
||||
if v4 == nil && v6 == nil {
|
||||
return nil, nil, fmt.Errorf("no valid IP addresses found on interface %s", c.conf.Other.Interface)
|
||||
}
|
||||
return v4, v6, nil
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ type others struct {
|
||||
Bootstrap []string `toml:"bootstrap"`
|
||||
Passthrough []string `toml:"passthrough"`
|
||||
Timeout uint `toml:"timeout"`
|
||||
Interface string `toml:"interface"`
|
||||
NoCookies bool `toml:"no_cookies"`
|
||||
NoECS bool `toml:"no_ecs"`
|
||||
NoIPv6 bool `toml:"no_ipv6"`
|
||||
|
||||
@@ -81,7 +81,9 @@ passthrough = [
|
||||
"captive.apple.com",
|
||||
"connectivitycheck.gstatic.com",
|
||||
"detectportal.firefox.com",
|
||||
"globalreachtech.com",
|
||||
"msftconnecttest.com",
|
||||
"network-auth.com",
|
||||
"nmcheck.gnome.org",
|
||||
|
||||
"pool.ntp.org",
|
||||
@@ -95,6 +97,11 @@ passthrough = [
|
||||
# Timeout for upstream request in seconds
|
||||
timeout = 30
|
||||
|
||||
# Interface to bind to for outgoing connections.
|
||||
# If empty, the system default route is used (usually eth0 or wlan0).
|
||||
# Example: "eth1", "wlan0"
|
||||
interface = ""
|
||||
|
||||
# Disable HTTP Cookies
|
||||
#
|
||||
# Cookies may be useful if your upstream resolver is protected by some
|
||||
|
||||
18
go.mod
18
go.mod
@@ -1,20 +1,20 @@
|
||||
module github.com/m13253/dns-over-https/v2
|
||||
|
||||
go 1.24
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.5.0
|
||||
github.com/BurntSushi/toml v1.6.0
|
||||
github.com/gorilla/handlers v1.5.2
|
||||
github.com/infobloxopen/go-trees v0.0.0-20221216143356-66ceba885ebc
|
||||
github.com/miekg/dns v1.1.66
|
||||
golang.org/x/net v0.40.0
|
||||
github.com/miekg/dns v1.1.70
|
||||
golang.org/x/net v0.49.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
golang.org/x/mod v0.32.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/tools v0.41.0 // indirect
|
||||
)
|
||||
|
||||
34
go.sum
34
go.sum
@@ -1,5 +1,5 @@
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
@@ -8,21 +8,23 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE
|
||||
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
||||
github.com/infobloxopen/go-trees v0.0.0-20221216143356-66ceba885ebc h1:RhT2pjLo3EVRmldbEcBdeRA7CGPWsNEJC+Y/N1aXQbg=
|
||||
github.com/infobloxopen/go-trees v0.0.0-20221216143356-66ceba885ebc/go.mod h1:BaIJzjD2ZnHmx2acPF6XfGLPzNCMiBbMRqJr+8/8uRI=
|
||||
github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=
|
||||
github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=
|
||||
github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
|
||||
github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
Reference in New Issue
Block a user