From ad532a5a22899caca4d3c8a07e01f468a5e62d60 Mon Sep 17 00:00:00 2001 From: maride Date: Tue, 3 Dec 2019 17:47:38 +0100 Subject: [PATCH] Record and summarize network-related DHCP options --- ethernet/dhcpv4/dhcp.go | 3 + ethernet/dhcpv4/network.go | 153 +++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 ethernet/dhcpv4/network.go diff --git a/ethernet/dhcpv4/dhcp.go b/ethernet/dhcpv4/dhcp.go index 4c52ddf..1b26f69 100644 --- a/ethernet/dhcpv4/dhcp.go +++ b/ethernet/dhcpv4/dhcp.go @@ -43,6 +43,7 @@ func HandleDHCPv4Packet(packet gopacket.Packet) error { // Check for Hostname DHCP option (12) checkForHostname(dhcppacket) + checkForNetworkInfos(dhcppacket) return nil } @@ -50,6 +51,8 @@ func HandleDHCPv4Packet(packet gopacket.Packet) error { // Print summary after all packets are processed func PrintDHCPv4Summary() { headline := color.New(color.FgRed, color.Bold) + headline.Println("DHCP Network Overview") + printNetworkSummary() headline.Println("DHCP Requests") printRequestSummary() headline.Println("DHCP Responses/Offers") diff --git a/ethernet/dhcpv4/network.go b/ethernet/dhcpv4/network.go new file mode 100644 index 0000000..aaf4d52 --- /dev/null +++ b/ethernet/dhcpv4/network.go @@ -0,0 +1,153 @@ +package dhcpv4 + +import ( + "bytes" + "encoding/binary" + "fmt" + "github.com/fatih/color" + "github.com/google/gopacket/layers" + "log" + "net" +) + +var ( + networkSetup = make(map[layers.DHCPOpt][]byte) + watchedOpts = []layers.DHCPOpt{ + layers.DHCPOptSubnetMask, // Option 1 + layers.DHCPOptRouter, // Option 3 + layers.DHCPOptDNS, // Option 6 + layers.DHCPOptBroadcastAddr, // Option 28 + layers.DHCPOptNTPServers, // Option 42 + layers.DHCPOptLeaseTime, // Option 51 + layers.DHCPOptT1, // Option 58 + } +) + +// Looks for information specifying the setup of the network. This includes +// - Option 1: Subnet Mask +// - Option 3: Router address +// - Option 6: Domain Name Server address +// - Option 28: Broadcast address +// - Option 42: NTP Server address +// - Option 51: IP Address Lease time +// - Option 58: IP Renewal time +func checkForNetworkInfos(dhcppacket layers.DHCPv4) { + // Check if it is a DHCP request + if dhcppacket.Operation == layers.DHCPOpRequest { + // We can ignore requests, they won't help us here + return + } + + // Search for different options (1, 3, 6, 28, 42, 51, 58) in DHCP Packet Options + for _, o := range dhcppacket.Options { + if isRelevantOption(o) { + // Found DHCP option to be watched, let's watch it + saveOption(o) + } + } + +} + +// Checks if the given DHCPOption is part of the watchlist +func isRelevantOption(opt layers.DHCPOption) bool { + // Iterate over all DHCP options in our watchlist + for _, o := range watchedOpts { + if o == opt.Type { + // Found. + return true + } + } + + // This option is not on our watchlist. + return false +} + +// Saves the given option in the networkSetup map, and informs the user if the value changes +func saveOption(opt layers.DHCPOption) { + // check if we already stored this value + if networkSetup[opt.Type] != nil { + // We already stored a value, let's check if it's the same as the new one + if !bytes.Equal(networkSetup[opt.Type], opt.Data) { + // Already stored a value and it's different from our new value - inform user and overwrite value later + log.Printf("Received different values for DHCP Option %s (ID %d). (Old: %s, New. %s)", opt.Type.String(), opt.Type, networkSetup[opt.Type], opt.Data) + } else { + // Exactly this value was already stored, no need to overwrite it + return + } + } + + networkSetup[opt.Type] = opt.Data +} + +// Formats the given byte array as string representing the IP address, or returns an error (as string) +func formatIP(rawIP []byte) string { + // Check if we even have an IP + if rawIP == nil { + // We don't have an IP, construct an error message (as string) + error := color.New(color.FgRed) + return error.Sprint("(not found)") + } + + // Return formatted IP + return net.IP(rawIP).String() +} + +func formatDate(rawDate []byte) string { + // Check if we even have a date + if rawDate == nil { + // We don't have a date, construct an error message (as string) + error := color.New(color.FgRed) + return error.Sprint("(not found)") + } + + // Actually format date + intDate := binary.LittleEndian.Uint32(rawDate) + seconds := intDate % 60 + minutes := intDate / 60 % 60 + hours := intDate / 60 / 60 % 60 + formattedDate := "" + + // Check which words we need to pick + // ... regarding hours + if hours > 0 { + formattedDate = fmt.Sprintf("%d hours", hours) + } + + // ... regarding minutes + if minutes > 0 { + // check if we got a previous string we need to take care of + if len(formattedDate) > 0 { + // yes, append our information to existing string + formattedDate = fmt.Sprintf("%s, %d minutes", formattedDate, minutes) + } else { + // no, use our string + formattedDate = fmt.Sprintf("%d minutes", minutes) + } + } + + // ... regarding seconds + if seconds > 0 { + // check if we got a previous string we need to take care of + if len(formattedDate) > 0 { + // yes, append our information to existing string + formattedDate = fmt.Sprintf("%s, %d seconds", formattedDate, seconds) + } else { + // no, use our string + formattedDate = fmt.Sprintf("%d seconds", seconds) + } + } + + return formattedDate +} + +// Prints the summary of relevant DHCP options +func printNetworkSummary() { + fmt.Printf("Subnet Mask: %s\n", formatIP(networkSetup[layers.DHCPOptSubnetMask])) + fmt.Printf("Broadcast: %s\n", formatIP(networkSetup[layers.DHCPOptBroadcastAddr])) + fmt.Printf("Router: %s\n", formatIP(networkSetup[layers.DHCPOptRouter])) + fmt.Printf("DNS Server: %s\n", formatIP(networkSetup[layers.DHCPOptDNS])) + fmt.Printf("NTP Server: %s\n", formatIP(networkSetup[layers.DHCPOptNTPServers])) + fmt.Printf("Lease Time: %s\n", formatDate(networkSetup[layers.DHCPOptLeaseTime])) + fmt.Printf("Renewal Time: %s\n", formatDate(networkSetup[layers.DHCPOptT1])) +} +