mihomo/dns/dhcp.go

161 lines
3 KiB
Go
Raw Permalink Normal View History

package dns
import (
"context"
"net"
2022-04-20 01:52:51 +08:00
"net/netip"
2023-01-28 22:33:03 +08:00
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/component/dhcp"
"github.com/Dreamacro/clash/component/iface"
"github.com/Dreamacro/clash/component/resolver"
D "github.com/miekg/dns"
)
const (
IfaceTTL = time.Second * 20
DHCPTTL = time.Hour
DHCPTimeout = time.Minute
)
type dhcpClient struct {
ifaceName string
lock sync.Mutex
ifaceInvalidate time.Time
dnsInvalidate time.Time
2022-04-20 01:52:51 +08:00
ifaceAddr *netip.Prefix
done chan struct{}
2022-08-24 21:36:19 +08:00
clients []dnsClient
err error
}
2023-01-28 22:33:03 +08:00
var _ dnsClient = (*dhcpClient)(nil)
// Address implements dnsClient
func (d *dhcpClient) Address() string {
addrs := make([]string, 0)
for _, c := range d.clients {
addrs = append(addrs, c.Address())
}
return strings.Join(addrs, ",")
}
func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
defer cancel()
return d.ExchangeContext(ctx, m)
}
func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
2022-08-24 21:36:19 +08:00
clients, err := d.resolve(ctx)
if err != nil {
return nil, err
}
2023-06-11 20:58:51 +08:00
msg, _, err = batchExchange(ctx, clients, m)
return
}
2022-08-24 21:36:19 +08:00
func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) {
d.lock.Lock()
invalidated, err := d.invalidate()
if err != nil {
d.err = err
} else if invalidated {
done := make(chan struct{})
d.done = done
go func() {
ctx, cancel := context.WithTimeout(context.Background(), DHCPTimeout)
defer cancel()
2022-08-24 21:36:19 +08:00
var res []dnsClient
dns, err := dhcp.ResolveDNSFromDHCP(ctx, d.ifaceName)
2022-08-24 21:36:19 +08:00
// dns never empty if err is nil
if err == nil {
nameserver := make([]NameServer, 0, len(dns))
for _, item := range dns {
nameserver = append(nameserver, NameServer{
Addr: net.JoinHostPort(item.String(), "53"),
Interface: atomic.NewTypedValue(d.ifaceName),
})
}
2022-08-24 21:36:19 +08:00
res = transform(nameserver, nil)
}
d.lock.Lock()
defer d.lock.Unlock()
close(done)
d.done = nil
2022-08-24 21:36:19 +08:00
d.clients = res
d.err = err
}()
}
d.lock.Unlock()
for {
d.lock.Lock()
2022-08-24 21:36:19 +08:00
res, err, done := d.clients, d.err, d.done
d.lock.Unlock()
// initializing
if res == nil && err == nil {
select {
case <-done:
continue
case <-ctx.Done():
return nil, ctx.Err()
}
}
// dirty return
return res, err
}
}
func (d *dhcpClient) invalidate() (bool, error) {
if time.Now().Before(d.ifaceInvalidate) {
return false, nil
}
d.ifaceInvalidate = time.Now().Add(IfaceTTL)
ifaceObj, err := iface.ResolveInterface(d.ifaceName)
if err != nil {
return false, err
}
2022-04-20 01:52:51 +08:00
addr, err := ifaceObj.PickIPv4Addr(netip.Addr{})
if err != nil {
return false, err
}
2022-04-20 01:52:51 +08:00
if time.Now().Before(d.dnsInvalidate) && d.ifaceAddr == addr {
return false, nil
}
d.dnsInvalidate = time.Now().Add(DHCPTTL)
d.ifaceAddr = addr
return d.done == nil, nil
}
func newDHCPClient(ifaceName string) *dhcpClient {
return &dhcpClient{ifaceName: ifaceName}
}