github.com/metacubex/mihomo@v1.18.5/dns/dhcp.go (about) 1 //go:build !(android && cmfa) 2 3 package dns 4 5 import ( 6 "context" 7 "net" 8 "net/netip" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/metacubex/mihomo/component/dhcp" 14 "github.com/metacubex/mihomo/component/iface" 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 netip.Prefix 32 done chan struct{} 33 clients []dnsClient 34 err error 35 } 36 37 var _ dnsClient = (*dhcpClient)(nil) 38 39 // Address implements dnsClient 40 func (d *dhcpClient) Address() string { 41 addrs := make([]string, 0) 42 for _, c := range d.clients { 43 addrs = append(addrs, c.Address()) 44 } 45 return strings.Join(addrs, ",") 46 } 47 48 func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { 49 clients, err := d.resolve(ctx) 50 if err != nil { 51 return nil, err 52 } 53 54 msg, _, err = batchExchange(ctx, clients, m) 55 return 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 }) 83 } 84 85 res = transform(nameserver, nil) 86 } 87 88 d.lock.Lock() 89 defer d.lock.Unlock() 90 91 close(done) 92 93 d.done = nil 94 d.clients = res 95 d.err = err 96 }() 97 } 98 99 d.lock.Unlock() 100 101 for { 102 d.lock.Lock() 103 104 res, err, done := d.clients, d.err, d.done 105 106 d.lock.Unlock() 107 108 // initializing 109 if res == nil && err == nil { 110 select { 111 case <-done: 112 continue 113 case <-ctx.Done(): 114 return nil, ctx.Err() 115 } 116 } 117 118 // dirty return 119 return res, err 120 } 121 } 122 123 func (d *dhcpClient) invalidate() (bool, error) { 124 if time.Now().Before(d.ifaceInvalidate) { 125 return false, nil 126 } 127 128 d.ifaceInvalidate = time.Now().Add(IfaceTTL) 129 130 ifaceObj, err := iface.ResolveInterface(d.ifaceName) 131 if err != nil { 132 return false, err 133 } 134 135 addr, err := ifaceObj.PickIPv4Addr(netip.Addr{}) 136 if err != nil { 137 return false, err 138 } 139 140 if time.Now().Before(d.dnsInvalidate) && d.ifaceAddr == addr { 141 return false, nil 142 } 143 144 d.dnsInvalidate = time.Now().Add(DHCPTTL) 145 d.ifaceAddr = addr 146 147 return d.done == nil, nil 148 } 149 150 func newDHCPClient(ifaceName string) *dhcpClient { 151 return &dhcpClient{ifaceName: ifaceName} 152 }