@@ -90,6 +90,29 @@ func NewClient(conf *config.Config) (c *Client, err error) {
9090 Net : "tcp" ,
9191 Timeout : time .Duration (conf .Other .Timeout ) * time .Second ,
9292 }
93+
94+ if c .conf .Other .Interface != "" {
95+ localV4 , localV6 , err := c .getInterfaceIPs ()
96+ if err != nil {
97+ return nil , fmt .Errorf ("failed to get interface IPs for %s: %v" , c .conf .Other .Interface , err )
98+ }
99+ var localAddr net.IP
100+ if localV4 != nil {
101+ localAddr = localV4
102+ } else {
103+ localAddr = localV6
104+ }
105+
106+ c .udpClient .Dialer = & net.Dialer {
107+ Timeout : time .Duration (conf .Other .Timeout ) * time .Second ,
108+ LocalAddr : & net.UDPAddr {IP : localAddr },
109+ }
110+ c .tcpClient .Dialer = & net.Dialer {
111+ Timeout : time .Duration (conf .Other .Timeout ) * time .Second ,
112+ LocalAddr : & net.TCPAddr {IP : localAddr },
113+ }
114+ }
115+
93116 for _ , addr := range conf .Listen {
94117 c .udpServers = append (c .udpServers , & dns.Server {
95118 Addr : addr ,
@@ -120,6 +143,38 @@ func NewClient(conf *config.Config) (c *Client, err error) {
120143 PreferGo : true ,
121144 Dial : func (ctx context.Context , network , address string ) (net.Conn , error ) {
122145 var d net.Dialer
146+ if c .conf .Other .Interface != "" {
147+ localV4 , localV6 , err := c .getInterfaceIPs ()
148+ if err != nil {
149+ log .Printf ("Bootstrap dial warning: %v" , err )
150+ } else {
151+ numServers := len (c .bootstrap )
152+ bootstrap := c .bootstrap [rand .Intn (numServers )]
153+ host , _ , _ := net .SplitHostPort (bootstrap )
154+ ip := net .ParseIP (host )
155+ if ip != nil {
156+ if ip .To4 () != nil {
157+ if localV4 != nil {
158+ if strings .HasPrefix (network , "udp" ) {
159+ d .LocalAddr = & net.UDPAddr {IP : localV4 }
160+ } else {
161+ d .LocalAddr = & net.TCPAddr {IP : localV4 }
162+ }
163+ }
164+ } else {
165+ if localV6 != nil {
166+ if strings .HasPrefix (network , "udp" ) {
167+ d .LocalAddr = & net.UDPAddr {IP : localV6 }
168+ } else {
169+ d .LocalAddr = & net.TCPAddr {IP : localV6 }
170+ }
171+ }
172+ }
173+ }
174+ conn , err := d .DialContext (ctx , network , bootstrap )
175+ return conn , err
176+ }
177+ }
123178 numServers := len (c .bootstrap )
124179 bootstrap := c .bootstrap [rand .Intn (numServers )]
125180 conn , err := d .DialContext (ctx , network , bootstrap )
@@ -235,14 +290,72 @@ func (c *Client) newHTTPClient() error {
235290 if c .httpTransport != nil {
236291 c .httpTransport .CloseIdleConnections ()
237292 }
238- dialer := & net.Dialer {
293+
294+ localV4 , localV6 , err := c .getInterfaceIPs ()
295+ if err != nil {
296+ log .Printf ("Interface binding error: %v" , err )
297+ return err
298+ }
299+
300+ baseDialer := & net.Dialer {
239301 Timeout : time .Duration (c .conf .Other .Timeout ) * time .Second ,
240302 KeepAlive : 30 * time .Second ,
241- // DualStack: true,
242- Resolver : c .bootstrapResolver ,
303+ Resolver : c .bootstrapResolver ,
243304 }
305+
244306 c .httpTransport = & http.Transport {
245- DialContext : dialer .DialContext ,
307+ DialContext : func (ctx context.Context , network , addr string ) (net.Conn , error ) {
308+ if c .conf .Other .Interface == "" {
309+ return baseDialer .DialContext (ctx , network , addr )
310+ }
311+
312+ if network == "tcp4" && localV4 != nil {
313+ d := * baseDialer
314+ d .LocalAddr = & net.TCPAddr {IP : localV4 }
315+ return d .DialContext (ctx , network , addr )
316+ }
317+ if network == "tcp6" && localV6 != nil {
318+ d := * baseDialer
319+ d .LocalAddr = & net.TCPAddr {IP : localV6 }
320+ return d .DialContext (ctx , network , addr )
321+ }
322+
323+ // Manual Dual-Stack: Resolve host and try compatible families sequentially
324+ host , port , _ := net .SplitHostPort (addr )
325+ ips , err := c .bootstrapResolver .LookupIPAddr (ctx , host )
326+ if err != nil {
327+ return nil , err
328+ }
329+
330+ var lastErr error
331+ for _ , ip := range ips {
332+ d := * baseDialer
333+ targetAddr := net .JoinHostPort (ip .String (), port )
334+
335+ if ip .IP .To4 () != nil {
336+ if localV4 == nil {
337+ continue
338+ }
339+ d .LocalAddr = & net.TCPAddr {IP : localV4 }
340+ } else {
341+ if localV6 == nil {
342+ continue
343+ }
344+ d .LocalAddr = & net.TCPAddr {IP : localV6 }
345+ }
346+
347+ conn , err := d .DialContext (ctx , "tcp" , targetAddr )
348+ if err == nil {
349+ return conn , nil
350+ }
351+ lastErr = err
352+ }
353+
354+ if lastErr != nil {
355+ return nil , lastErr
356+ }
357+ return nil , fmt .Errorf ("connection to %s failed: no matching local/remote IP families on interface %s" , addr , c .conf .Other .Interface )
358+ },
246359 ExpectContinueTimeout : 1 * time .Second ,
247360 IdleConnTimeout : 90 * time .Second ,
248361 MaxIdleConns : 100 ,
@@ -251,15 +364,18 @@ func (c *Client) newHTTPClient() error {
251364 TLSHandshakeTimeout : time .Duration (c .conf .Other .Timeout ) * time .Second ,
252365 TLSClientConfig : & tls.Config {InsecureSkipVerify : c .conf .Other .TLSInsecureSkipVerify },
253366 }
367+
254368 if c .conf .Other .NoIPv6 {
369+ originalDial := c .httpTransport .DialContext
255370 c .httpTransport .DialContext = func (ctx context.Context , network , address string ) (net.Conn , error ) {
256371 if strings .HasPrefix (network , "tcp" ) {
257372 network = "tcp4"
258373 }
259- return dialer . DialContext (ctx , network , address )
374+ return originalDial (ctx , network , address )
260375 }
261376 }
262- err := http2 .ConfigureTransport (c .httpTransport )
377+
378+ err = http2 .ConfigureTransport (c .httpTransport )
263379 if err != nil {
264380 return err
265381 }
@@ -485,3 +601,38 @@ func (c *Client) findClientIP(w dns.ResponseWriter, r *dns.Msg) (ednsClientAddre
485601 }
486602 return
487603}
604+
605+ // getInterfaceIPs returns the first valid IPv4 and IPv6 addresses found on the interface
606+ func (c * Client ) getInterfaceIPs () (v4 , v6 net.IP , err error ) {
607+ if c .conf .Other .Interface == "" {
608+ return nil , nil , nil
609+ }
610+ ifi , err := net .InterfaceByName (c .conf .Other .Interface )
611+ if err != nil {
612+ return nil , nil , err
613+ }
614+ addrs , err := ifi .Addrs ()
615+ if err != nil {
616+ return nil , nil , err
617+ }
618+
619+ for _ , addr := range addrs {
620+ ip , _ , err := net .ParseCIDR (addr .String ())
621+ if err != nil {
622+ continue
623+ }
624+ if ip4 := ip .To4 (); ip4 != nil {
625+ if v4 == nil {
626+ v4 = ip4
627+ }
628+ } else {
629+ if v6 == nil && ! c .conf .Other .NoIPv6 {
630+ v6 = ip
631+ }
632+ }
633+ }
634+ if v4 == nil && v6 == nil {
635+ return nil , nil , fmt .Errorf ("no valid IP addresses found on interface %s" , c .conf .Other .Interface )
636+ }
637+ return v4 , v6 , nil
638+ }
0 commit comments