all: watch link state on Linux using netlink

So we can detect changed to link and trigger re-bootstrap.
This commit is contained in:
Cuong Manh Le
2023-04-29 13:56:18 +07:00
committed by Cuong Manh Le
parent 56d8dc865f
commit 5cad0d6be1
8 changed files with 60 additions and 43 deletions

View File

@@ -0,0 +1,27 @@
package main
import (
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
)
func (p *prog) watchLinkState() {
ch := make(chan netlink.LinkUpdate)
done := make(chan struct{})
defer close(done)
if err := netlink.LinkSubscribe(ch, done); err != nil {
mainLog.Warn().Err(err).Msg("could not subscribe link")
return
}
for lu := range ch {
if lu.Change == 0xFFFFFFFF {
continue
}
if lu.Change&unix.IFF_UP != 0 {
mainLog.Debug().Msgf("link state changed, re-bootstrapping")
for _, uc := range p.cfg.Upstream {
uc.ReBootstrap()
}
}
}
}

View File

@@ -0,0 +1,5 @@
//go:build !linux
package main
func (p *prog) watchLinkState() {}

View File

@@ -82,6 +82,8 @@ func (p *prog) run() {
uc.SetupTransport()
}
go p.watchLinkState()
for listenerNum := range p.cfg.Listener {
p.cfg.Listener[listenerNum].Init()
go func(listenerNum string) {

View File

@@ -9,6 +9,7 @@ import (
"net/url"
"os"
"strings"
"sync"
"sync/atomic"
"time"
@@ -127,6 +128,7 @@ type UpstreamConfig struct {
u *url.URL `mapstructure:"-" toml:"-"`
g singleflight.Group
mu sync.Mutex
bootstrapIPs []string
nextBootstrapIP atomic.Uint32
}
@@ -268,6 +270,8 @@ func (uc *UpstreamConfig) setupDOHTransport() {
}
func (uc *UpstreamConfig) setupDOHTransportWithoutPingUpstream() {
uc.mu.Lock()
defer uc.mu.Unlock()
uc.transport = http.DefaultTransport.(*http.Transport).Clone()
uc.transport.IdleConnTimeout = 5 * time.Second
uc.transport.TLSClientConfig = &tls.Config{RootCAs: uc.certPool}

View File

@@ -19,6 +19,8 @@ func (uc *UpstreamConfig) setupDOH3Transport() {
}
func (uc *UpstreamConfig) setupDOH3TransportWithoutPingUpstream() {
uc.mu.Lock()
defer uc.mu.Unlock()
rt := &http3.RoundTripper{}
rt.TLSClientConfig = &tls.Config{RootCAs: uc.certPool}
rt.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {

54
doh.go
View File

@@ -8,8 +8,6 @@ import (
"io"
"net/http"
"net/url"
"runtime"
"time"
"github.com/miekg/dns"
)
@@ -23,10 +21,13 @@ const (
)
func newDohResolver(uc *UpstreamConfig) *dohResolver {
uc.mu.Lock()
transport := uc.transport
uc.mu.Unlock()
r := &dohResolver{
endpoint: uc.u,
isDoH3: uc.Type == ResolverTypeDOH3,
transport: uc.transport,
transport: transport,
http3RoundTripper: uc.http3RoundTripper,
sendClientInfo: uc.UpstreamSendClientInfo(),
uc: uc,
@@ -61,12 +62,14 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro
}
addHeader(ctx, req, r.sendClientInfo)
var resp *http.Response
if runtime.GOOS == "linux" {
resp, err = r.doRequestWithFailover(req)
} else {
resp, err = r.doRequest(req)
c := http.Client{Transport: r.transport}
if r.isDoH3 {
if r.http3RoundTripper == nil {
return nil, errors.New("DoH3 is not supported")
}
c.Transport = r.http3RoundTripper
}
resp, err := c.Do(req)
if err != nil {
if r.isDoH3 {
if closer, ok := r.http3RoundTripper.(io.Closer); ok {
@@ -93,41 +96,6 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro
return answer, nil
}
func (r *dohResolver) doRequest(req *http.Request) (*http.Response, error) {
c := http.Client{Transport: r.transport}
if r.isDoH3 {
if r.http3RoundTripper == nil {
return nil, errors.New("DoH3 is not supported")
}
c.Transport = r.http3RoundTripper
}
return c.Do(req)
}
const failoverTimeout = 500 * time.Millisecond
// doRequestWithFailover is like doRequest, but wrap the request with initial timeout.
// If the first request failed, it's likely that the transport was broken, then trigger
// re-bootstrapping and retry the request.
func (r *dohResolver) doRequestWithFailover(req *http.Request) (*http.Response, error) {
c := http.Client{Transport: r.transport}
if r.isDoH3 {
if r.http3RoundTripper == nil {
return nil, errors.New("DoH3 is not supported")
}
c.Transport = r.http3RoundTripper
}
ctx, cancel := context.WithTimeout(context.Background(), failoverTimeout)
defer cancel()
resp, err := c.Do(req.WithContext(ctx))
if err == nil {
return resp, err
}
r.uc.ReBootstrap()
c.Transport = r.uc.transport
return c.Do(req)
}
func addHeader(ctx context.Context, req *http.Request, sendClientInfo bool) {
req.Header.Set("Content-Type", headerApplicationDNS)
req.Header.Set("Accept", headerApplicationDNS)

2
go.mod
View File

@@ -21,6 +21,7 @@ require (
github.com/spf13/cobra v1.4.0
github.com/spf13/viper v1.14.0
github.com/stretchr/testify v1.8.1
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
golang.org/x/net v0.7.0
golang.org/x/sync v0.1.0
golang.org/x/sys v0.5.0
@@ -65,6 +66,7 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
go4.org/mem v0.0.0-20210711025021-927187094b94 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect

7
go.sum
View File

@@ -282,6 +282,11 @@ github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
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/netlink v1.1.1-0.20211118161826-650dca95af54 h1:8mhqcHPqTMhSPoslhGYihEgSfc77+7La1P6kiB6+9So=
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
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.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -429,6 +434,7 @@ golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -437,6 +443,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=