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 }