github.com/yaling888/clash@v1.53.0/dns/dhcp.go (about)

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