github.com/igoogolx/clash@v1.19.8/dns/dhcp.go (about)

     1  package dns
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	C "github.com/igoogolx/clash/constant"
     7  	"net"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/igoogolx/clash/component/dhcp"
    12  	"github.com/igoogolx/clash/component/iface"
    13  	"github.com/igoogolx/clash/component/resolver"
    14  
    15  	D "github.com/miekg/dns"
    16  )
    17  
    18  const (
    19  	IfaceTTL    = time.Second * 20
    20  	DHCPTTL     = time.Hour
    21  	DHCPTimeout = time.Minute
    22  )
    23  
    24  type dhcpClient struct {
    25  	ifaceName string
    26  
    27  	lock            sync.Mutex
    28  	ifaceInvalidate time.Time
    29  	dnsInvalidate   time.Time
    30  
    31  	ifaceAddr *net.IPNet
    32  	done      chan struct{}
    33  	clients   []dnsClient
    34  	err       error
    35  	getDialer func() (C.Proxy, error)
    36  }
    37  
    38  func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
    39  	ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
    40  	defer cancel()
    41  
    42  	return d.ExchangeContext(ctx, m)
    43  }
    44  
    45  func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
    46  	clients, err := d.resolve(ctx)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	return batchExchange(ctx, clients, m)
    52  }
    53  
    54  func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) {
    55  	d.lock.Lock()
    56  
    57  	invalidated, err := d.invalidate()
    58  	if err != nil {
    59  		d.err = err
    60  	} else if invalidated {
    61  		done := make(chan struct{})
    62  
    63  		d.done = done
    64  
    65  		go func() {
    66  			ctx, cancel := context.WithTimeout(context.Background(), DHCPTimeout)
    67  			defer cancel()
    68  
    69  			var res []dnsClient
    70  			dns, err := dhcp.ResolveDNSFromDHCP(ctx, d.ifaceName)
    71  			// dns never empty if err is nil
    72  			if err == nil {
    73  				nameserver := make([]NameServer, 0, len(dns))
    74  				for _, item := range dns {
    75  					nameserver = append(nameserver, NameServer{
    76  						Addr:      net.JoinHostPort(item.String(), "53"),
    77  						Interface: d.ifaceName,
    78  					})
    79  				}
    80  
    81  				res = transform(nameserver, d.getDialer)
    82  			}
    83  
    84  			d.lock.Lock()
    85  			defer d.lock.Unlock()
    86  
    87  			close(done)
    88  
    89  			d.done = nil
    90  			d.clients = res
    91  			d.err = err
    92  		}()
    93  	}
    94  
    95  	d.lock.Unlock()
    96  
    97  	for {
    98  		d.lock.Lock()
    99  
   100  		res, err, done := d.clients, d.err, d.done
   101  
   102  		d.lock.Unlock()
   103  
   104  		// initializing
   105  		if res == nil && err == nil {
   106  			select {
   107  			case <-done:
   108  				continue
   109  			case <-ctx.Done():
   110  				return nil, ctx.Err()
   111  			}
   112  		}
   113  
   114  		// dirty return
   115  		return res, err
   116  	}
   117  }
   118  
   119  func (d *dhcpClient) invalidate() (bool, error) {
   120  	if time.Now().Before(d.ifaceInvalidate) {
   121  		return false, nil
   122  	}
   123  
   124  	d.ifaceInvalidate = time.Now().Add(IfaceTTL)
   125  
   126  	ifaceObj, err := iface.ResolveInterface(d.ifaceName)
   127  	if err != nil {
   128  		return false, err
   129  	}
   130  
   131  	addr, err := ifaceObj.PickIPv4Addr(nil)
   132  	if err != nil {
   133  		return false, err
   134  	}
   135  
   136  	if time.Now().Before(d.dnsInvalidate) && d.ifaceAddr.IP.Equal(addr.IP) && bytes.Equal(d.ifaceAddr.Mask, addr.Mask) {
   137  		return false, nil
   138  	}
   139  
   140  	d.dnsInvalidate = time.Now().Add(DHCPTTL)
   141  	d.ifaceAddr = addr
   142  
   143  	return d.done == nil, nil
   144  }
   145  
   146  func newDHCPClient(ifaceName string, getDialer func() (C.Proxy, error)) *dhcpClient {
   147  	return &dhcpClient{ifaceName: ifaceName, getDialer: getDialer}
   148  }