├── .gitignore ├── README.md ├── packetparser ├── README.md ├── iputils.go └── main.go └── dhclient └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | packetparser/packetparser 2 | dhclient/dhclient 3 | .*.swp 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # exdhcp 2 | 3 | Examples for `github.com/insomniacslk/dhcp` 4 | -------------------------------------------------------------------------------- /packetparser/README.md: -------------------------------------------------------------------------------- 1 | # packetparser 2 | 3 | An example of the encoding/decoding capabilities of the `dhcp` library, for both 4 | DHCPv4 and DHCPv6. It can read a `pcap` file, or work as a client (which is the 5 | default). See `./packetparser -h` for details, shown below for convenience: 6 | 7 | ``` 8 | $ ./packetparser -h 9 | Usage of ./packetparser: 10 | -debug 11 | Enable debug output (default: false) 12 | -etherip 13 | Enables LayerTypeEtherIP instead of LayerTypeEthernet, use with linux-cooked PCAP files. (default: false) 14 | -i string 15 | Network interface to send packets through (default "eth0") 16 | -live 17 | Sniff DHCP packets from the network (default: false) 18 | -r string 19 | PCAP file to read from. If not specified, try to send an actual DHCP request 20 | -v int 21 | IP version to use (default 6) 22 | ``` 23 | -------------------------------------------------------------------------------- /packetparser/iputils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/milosgajdos83/tenus" 7 | "net" 8 | "time" 9 | ) 10 | 11 | func GetLinkLocalAddr(ifname string) (*net.IP, *net.IPNet, error) { 12 | ifaces, err := net.Interfaces() 13 | if err != nil { 14 | return nil, nil, err 15 | } 16 | var iface *net.Interface 17 | var linkLocalAddr *net.IP 18 | for _, ifi := range ifaces { 19 | if ifi.Name == ifname { 20 | iface = &ifi 21 | break 22 | } 23 | } 24 | // build the addr from the interface 25 | hwa := iface.HardwareAddr 26 | linkLocalAddr = &net.IP{ 27 | 0xfe, 0x80, 0x00, 0x00, 28 | 0x00, 0x00, 0x00, 0x00, 29 | hwa[0] ^ 2, hwa[1], hwa[2], 0xff, 30 | 0xfe, hwa[3], hwa[4], hwa[5], 31 | } 32 | m := net.CIDRMask(64, 128) 33 | linkLocalNet := net.IPNet{IP: linkLocalAddr.Mask(m), Mask: m} // a /64 34 | return linkLocalAddr, &linkLocalNet, nil 35 | } 36 | 37 | // Wait for an interface to be up. Will return an error if it is not up before 38 | // the timeout expires. The status is synchronously polled every 100msec 39 | func WaitForInterfaceStatusUp(ifname string, timeout time.Duration) error { 40 | // FIXME should use netlink events rather than polling like this 41 | ifaces, err := net.Interfaces() 42 | if err != nil { 43 | return err 44 | } 45 | deadline := time.Now().Add(timeout) 46 | for { 47 | if time.Now().After(deadline) { 48 | return fmt.Errorf("Timed out while waiting for interface to be up") 49 | } 50 | for _, ifi := range ifaces { 51 | if ifi.Flags&net.FlagUp != 0 { 52 | return nil 53 | } 54 | } 55 | time.Sleep(100 * time.Millisecond) 56 | } 57 | } 58 | 59 | func ConfigureLinkLocalAddress(ifname string) (*net.IP, error) { 60 | // Configure the link-local address for the given interface, via Linux's 61 | // netlink, and bring the interface up 62 | llAddr, llNet, err := GetLinkLocalAddr(ifname) 63 | if err != nil { 64 | return nil, err 65 | } 66 | dl, err := tenus.NewLinkFrom(ifname) 67 | if err != nil { 68 | return nil, err 69 | } 70 | addrs, err := dl.NetInterface().Addrs() 71 | if err != nil { 72 | return nil, err 73 | } 74 | found := false 75 | for _, addr := range addrs { 76 | if uAddr, ok := addr.(*net.IPNet); ok { 77 | if uAddr.IP.To16() != nil && bytes.Equal(uAddr.IP, *llAddr) { 78 | found = true 79 | } 80 | } 81 | } 82 | if !found { 83 | if err = dl.SetLinkIp(*llAddr, llNet); err != nil { 84 | return nil, err 85 | } 86 | if err = dl.SetLinkUp(); err != nil { 87 | return nil, err 88 | } 89 | } 90 | return llAddr, nil 91 | } 92 | -------------------------------------------------------------------------------- /dhclient/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | "github.com/insomniacslk/dhcp/dhcpv4" 10 | "github.com/insomniacslk/dhcp/dhcpv4/client4" 11 | "github.com/insomniacslk/dhcp/dhcpv6" 12 | "github.com/insomniacslk/dhcp/dhcpv6/client6" 13 | "github.com/insomniacslk/dhcp/netboot" 14 | ) 15 | 16 | var ( 17 | ver = flag.Int("v", 6, "IP version to use") 18 | ifname = flag.String("i", "eth0", "Interface name") 19 | dryrun = flag.Bool("dryrun", false, "Do not change network configuration") 20 | debug = flag.Bool("d", false, "Print debug output") 21 | retries = flag.Int("r", 3, "Number of retries before giving up") 22 | noIfup = flag.Bool("noifup", false, "If set, don't wait for the interface to be up") 23 | ) 24 | 25 | func dhclient6(ifname string, attempts int, verbose bool) (*netboot.BootConf, error) { 26 | if attempts < 1 { 27 | attempts = 1 28 | } 29 | llAddr, err := dhcpv6.GetLinkLocalAddr(ifname) 30 | if err != nil { 31 | return nil, err 32 | } 33 | laddr := net.UDPAddr{ 34 | IP: llAddr, 35 | Port: dhcpv6.DefaultClientPort, 36 | Zone: ifname, 37 | } 38 | raddr := net.UDPAddr{ 39 | IP: dhcpv6.AllDHCPRelayAgentsAndServers, 40 | Port: dhcpv6.DefaultServerPort, 41 | Zone: ifname, 42 | } 43 | c := client6.NewClient() 44 | c.LocalAddr = &laddr 45 | c.RemoteAddr = &raddr 46 | var conv []dhcpv6.DHCPv6 47 | for attempt := 0; attempt < attempts; attempt++ { 48 | log.Printf("Attempt %d of %d", attempt+1, attempts) 49 | conv, err = c.Exchange(ifname, dhcpv6.WithNetboot) 50 | if err != nil && attempt < attempts { 51 | log.Printf("Error: %v", err) 52 | continue 53 | } 54 | break 55 | } 56 | if verbose { 57 | for _, m := range conv { 58 | log.Print(m.Summary()) 59 | } 60 | } 61 | if err != nil { 62 | return nil, err 63 | } 64 | // extract the network configuration 65 | netconf, err := netboot.ConversationToNetconf(conv) 66 | return netconf, err 67 | } 68 | 69 | func dhclient4(ifname string, attempts int, verbose bool) (*netboot.BootConf, error) { 70 | if attempts < 1 { 71 | attempts = 1 72 | } 73 | client := client4.NewClient() 74 | var ( 75 | conv []*dhcpv4.DHCPv4 76 | err error 77 | ) 78 | for attempt := 0; attempt < attempts; attempt++ { 79 | log.Printf("Attempt %d of %d", attempt+1, attempts) 80 | conv, err = client.Exchange(ifname) 81 | if err != nil && attempt < attempts { 82 | log.Printf("Error: %v", err) 83 | continue 84 | } 85 | break 86 | } 87 | if verbose { 88 | for _, m := range conv { 89 | log.Print(m.Summary()) 90 | } 91 | } 92 | if err != nil { 93 | return nil, err 94 | } 95 | // extract the network configuration 96 | netconf, err := netboot.ConversationToNetconfv4(conv) 97 | return netconf, err 98 | } 99 | 100 | func main() { 101 | flag.Parse() 102 | 103 | var ( 104 | err error 105 | bootconf *netboot.BootConf 106 | ) 107 | // bring interface up 108 | if !*noIfup { 109 | _, err = netboot.IfUp(*ifname, 5*time.Second) 110 | if err != nil { 111 | log.Fatalf("failed to bring interface %s up: %v", *ifname, err) 112 | } 113 | } 114 | if *ver == 6 { 115 | bootconf, err = dhclient6(*ifname, *retries+1, *debug) 116 | } else { 117 | bootconf, err = dhclient4(*ifname, *retries+1, *debug) 118 | } 119 | if err != nil { 120 | log.Fatal(err) 121 | } 122 | // configure the interface 123 | log.Printf("Setting network configuration:") 124 | log.Printf("%+v", bootconf) 125 | if *dryrun { 126 | log.Printf("dry run requested, not changing network configuration") 127 | } else { 128 | if err := netboot.ConfigureInterface(*ifname, &bootconf.NetConf); err != nil { 129 | log.Fatal(err) 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /packetparser/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net" 9 | "strconv" 10 | 11 | "github.com/google/gopacket" 12 | "github.com/google/gopacket/layers" 13 | "github.com/google/gopacket/pcap" 14 | "github.com/insomniacslk/dhcp/dhcpv4" 15 | "github.com/insomniacslk/dhcp/dhcpv4/client4" 16 | "github.com/insomniacslk/dhcp/dhcpv6" 17 | "github.com/insomniacslk/dhcp/dhcpv6/client6" 18 | ) 19 | 20 | var ver = flag.Int("v", 6, "IP version to use") 21 | var infile = flag.String("r", "", "PCAP file to read from. If not specified, try to send an actual DHCP request") 22 | var iface = flag.String("i", "eth0", "Network interface to send packets through") 23 | var useEtherIP = flag.Bool("etherip", false, "Enables LayerTypeEtherIP instead of LayerTypeEthernet, use with linux-cooked PCAP files. (default: false)") 24 | var debug = flag.Bool("debug", false, "Enable debug output (default: false)") 25 | var live = flag.Bool("live", false, "Sniff DHCP packets from the network (default: false)") 26 | var snaplen = flag.Int("s", 0, "Set the snaplen when using -live (default: 0)") 27 | var count = flag.Int("c", 0, "Stop after packets (default: 0)") 28 | var unpack = flag.Bool("unpack", false, "Unpack inner DHCPv6 messages when parsing relay messages") 29 | var to = flag.String("to", "", "Destination to send packets to. If empty, will use [ff02::1:2]:547") 30 | 31 | func Clientv4() { 32 | client := client4.NewClient() 33 | conv, err := client.Exchange(*iface) 34 | // don't exit immediately if there's an error, since `conv` will always 35 | // contain at least the SOLICIT message. So print it out first 36 | for _, m := range conv { 37 | log.Print(m.Summary()) 38 | } 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | } 43 | 44 | func Clientv6() { 45 | var ( 46 | laddr, raddr net.UDPAddr 47 | ) 48 | llAddr, err := dhcpv6.GetLinkLocalAddr(*iface) 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | laddr = net.UDPAddr{ 53 | IP: llAddr, 54 | Port: dhcpv6.DefaultClientPort, 55 | Zone: *iface, 56 | } 57 | if *to == "" { 58 | raddr = net.UDPAddr{ 59 | IP: dhcpv6.AllDHCPRelayAgentsAndServers, 60 | Port: dhcpv6.DefaultServerPort, 61 | Zone: *iface, 62 | } 63 | } else { 64 | dstHost, dstPort, err := net.SplitHostPort(*to) 65 | if err != nil { 66 | log.Fatal(err) 67 | } 68 | dstPortInt, err := strconv.Atoi(dstPort) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | raddr = net.UDPAddr{ 73 | IP: net.ParseIP(dstHost), 74 | Port: dstPortInt, 75 | Zone: *iface, // this may clash with the scope passed in the dstHost, if any 76 | } 77 | } 78 | c := client6.NewClient() 79 | c.LocalAddr = &laddr 80 | c.RemoteAddr = &raddr 81 | conv, err := c.Exchange(*iface) 82 | // don't exit immediately if there's an error, since `conv` will always 83 | // contain at least the SOLICIT message. So print it out first 84 | for _, m := range conv { 85 | fmt.Print(m.Summary()) 86 | } 87 | if err != nil { 88 | log.Fatal(err) 89 | } 90 | } 91 | 92 | func main() { 93 | flag.Parse() 94 | if *infile == "" && !*live { 95 | if *ver == 4 { 96 | Clientv4() 97 | } else { 98 | Clientv6() 99 | } 100 | } else { 101 | var ( 102 | handle *pcap.Handle 103 | err error 104 | ) 105 | if *count < 0 { 106 | log.Fatal("count cannot be negative") 107 | } 108 | if *live { 109 | if *snaplen < 0 { 110 | log.Fatal("snaplen cannot be negative") 111 | } 112 | var slen int32 113 | slen = int32(*snaplen) 114 | if slen == 0 { 115 | // some libpcap versions don't support 0 as 'no snap len limit'. 116 | // Setting it to 262144 as per tcpdump's manual page 117 | slen = 262144 118 | } 119 | handle, err = pcap.OpenLive(*iface, slen, false /* promisc */, -1 /* timeout */) 120 | } else { 121 | handle, err = pcap.OpenOffline(*infile) 122 | } 123 | if err != nil { 124 | log.Fatal(err) 125 | } 126 | defer handle.Close() 127 | var pcapFilter string 128 | if *ver == 6 { 129 | pcapFilter = "ip6 and udp portrange 546-547" 130 | } else { 131 | pcapFilter = "ip and udp portrange 67-68" 132 | } 133 | err = handle.SetBPFFilter(pcapFilter) 134 | if err != nil { 135 | log.Fatal(err) 136 | } 137 | var layerType gopacket.LayerType 138 | if *useEtherIP { 139 | layerType = layers.LayerTypeEtherIP 140 | } else { 141 | layerType = layers.LayerTypeEthernet 142 | } 143 | packetCount := 0 144 | for { 145 | packetCount++ 146 | if *count != 0 && packetCount > *count { 147 | break 148 | } 149 | data, _, err := handle.ReadPacketData() 150 | if err != nil { 151 | if err == io.EOF { 152 | break 153 | } 154 | log.Fatal(err) 155 | } 156 | pkt := gopacket.NewPacket(data, layerType, gopacket.Default) 157 | if *debug { 158 | fmt.Println(pkt) 159 | } 160 | if udpLayer := pkt.Layer(layers.LayerTypeUDP); udpLayer != nil { 161 | udp, _ := udpLayer.(*layers.UDP) 162 | if *debug { 163 | fmt.Println(udp.Payload) 164 | } 165 | if *ver == 4 { 166 | d, err := dhcpv4.FromBytes(udp.Payload) 167 | if err != nil { 168 | log.Fatal(err) 169 | } 170 | fmt.Println(d.Summary()) 171 | } else { 172 | var packet dhcpv6.DHCPv6 173 | d, err := dhcpv6.FromBytes(udp.Payload) 174 | if err != nil { 175 | log.Fatal(err) 176 | } 177 | packet = d 178 | if *unpack { 179 | if d.IsRelay() { 180 | inner, err := d.(*dhcpv6.RelayMessage).GetInnerMessage() 181 | if err != nil { 182 | log.Fatal(err) 183 | } 184 | packet = inner 185 | } 186 | } 187 | fmt.Println(packet.Summary()) 188 | } 189 | } 190 | } 191 | } 192 | } 193 | --------------------------------------------------------------------------------