github.com/yaling888/clash@v1.53.0/component/dhcp/dhcp.go (about)

     1  package dhcp
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"net"
     7  	"net/netip"
     8  
     9  	"github.com/insomniacslk/dhcp/dhcpv4"
    10  
    11  	"github.com/yaling888/clash/common/nnip"
    12  	"github.com/yaling888/clash/component/iface"
    13  )
    14  
    15  var (
    16  	ErrNotResponding = errors.New("DHCP not responding")
    17  	ErrNotFound      = errors.New("DNS option not found")
    18  )
    19  
    20  func ResolveDNSFromDHCP(context context.Context, ifaceName string) ([]netip.Addr, error) {
    21  	conn, err := ListenDHCPClient(context, ifaceName)
    22  	if err != nil {
    23  		return nil, err
    24  	}
    25  	defer func() {
    26  		_ = conn.Close()
    27  	}()
    28  
    29  	result := make(chan []netip.Addr, 1)
    30  
    31  	ifaceObj, err := iface.ResolveInterface(ifaceName)
    32  	if err != nil {
    33  		return nil, err
    34  	}
    35  
    36  	discovery, err := dhcpv4.NewDiscovery(ifaceObj.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(dhcpv4.OptionDomainNameServer))
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  
    41  	go receiveOffer(conn, discovery.TransactionID, result)
    42  
    43  	_, err = conn.WriteTo(discovery.ToBytes(), &net.UDPAddr{IP: net.IPv4bcast, Port: 67})
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	select {
    49  	case r, ok := <-result:
    50  		if !ok {
    51  			return nil, ErrNotFound
    52  		}
    53  		return r, nil
    54  	case <-context.Done():
    55  		return nil, ErrNotResponding
    56  	}
    57  }
    58  
    59  func receiveOffer(conn net.PacketConn, id dhcpv4.TransactionID, result chan<- []netip.Addr) {
    60  	defer close(result)
    61  
    62  	buf := make([]byte, dhcpv4.MaxMessageSize)
    63  
    64  	for {
    65  		n, _, err := conn.ReadFrom(buf)
    66  		if err != nil {
    67  			return
    68  		}
    69  
    70  		pkt, err := dhcpv4.FromBytes(buf[:n])
    71  		if err != nil {
    72  			continue
    73  		}
    74  
    75  		if pkt.MessageType() != dhcpv4.MessageTypeOffer {
    76  			continue
    77  		}
    78  
    79  		if pkt.TransactionID != id {
    80  			continue
    81  		}
    82  
    83  		dns := pkt.DNS()
    84  		l := len(dns)
    85  		if l == 0 {
    86  			return
    87  		}
    88  
    89  		dnsAddr := make([]netip.Addr, l)
    90  		for i := 0; i < l; i++ {
    91  			dnsAddr[i] = nnip.IpToAddr(dns[i])
    92  		}
    93  
    94  		result <- dnsAddr
    95  
    96  		return
    97  	}
    98  }