dhcp.go 2.7 KB

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