github.com/kelleygo/clashcore@v1.0.2/component/dhcp/dhcp.go (about) 1 package dhcp 2 3 import ( 4 "context" 5 "errors" 6 "net" 7 "net/netip" 8 9 "github.com/kelleygo/clashcore/common/nnip" 10 "github.com/kelleygo/clashcore/component/iface" 11 12 "github.com/insomniacslk/dhcp/dhcpv4" 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 }