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 }