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