This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"flag" | |
"fmt" | |
"io/ioutil" | |
"log" | |
"net" | |
"sort" | |
"strings" | |
"time" | |
"github.com/google/gopacket" | |
"github.com/google/gopacket/examples/util" | |
"github.com/google/gopacket/layers" | |
"github.com/google/gopacket/pcap" | |
) | |
var iface = flag.String("i", "eth0", "Interface to read packets from") | |
func main() { | |
defer util.Run()() | |
var handle *pcap.Handle | |
var err error | |
inactive, err := pcap.NewInactiveHandle(*iface) | |
if err != nil { | |
log.Fatalf("could not create: %v", err) | |
} | |
defer inactive.CleanUp() | |
if err = inactive.SetPromisc(true); err != nil { | |
log.Fatalf("could not set promisc mode: %v", err) | |
} else if err = inactive.SetTimeout(time.Second); err != nil { | |
log.Fatalf("could not set timeout: %v", err) | |
} | |
if handle, err = inactive.Activate(); err != nil { | |
log.Fatal("PCAP Activate error:", err) | |
} | |
defer handle.Close() | |
mac := "" | |
if netIfs, err := net.Interfaces(); err == nil { | |
for _, ifa := range netIfs { | |
if *iface == ifa.Name { | |
mac = ifa.HardwareAddr.String() | |
} | |
} | |
} else { | |
log.Fatal("Failed to get the interfaces:", err) | |
} | |
source := gopacket.NewPacketSource(handle, handle.LinkType()) | |
source.Lazy = true | |
source.NoCopy = true | |
source.DecodeStreamsAsDatagrams = true | |
categorize(source, mac) | |
} | |
func categorize(source *gopacket.PacketSource, localMac string) { | |
const ( | |
Unknown int = 0 | |
In = 1 | |
Out = 2 | |
) | |
packetCount := 0 | |
ipMacList := newIpMacList() | |
for packet := range source.Packets() { | |
remoteMac := "" | |
remoteIP := "" | |
direction := Unknown | |
for _, layer := range packet.Layers() { | |
switch layer.LayerType() { | |
case layers.LayerTypeEthernet: | |
eth := layer.(*layers.Ethernet) | |
if eth.DstMAC[0] == 0x33 && eth.DstMAC[1] == 0x33 { | |
// Broadcast- skip it | |
} else if eth.SrcMAC[0] == 0x33 && eth.SrcMAC[1] == 0x33 { | |
// Broadcast- skip it | |
} else if eth.SrcMAC.String() == localMac { | |
direction = In | |
remoteMac = ">" + eth.DstMAC.String() | |
} else if eth.DstMAC.String() == localMac { | |
direction = In | |
remoteMac = "<" + eth.SrcMAC.String() | |
} else { | |
fmt.Printf("[ %s ? %s ]\n", eth.SrcMAC, eth.DstMAC) | |
} | |
case layers.LayerTypeIPv4: | |
ipv4 := layer.(*layers.IPv4) | |
switch direction { | |
case In: | |
remoteIP = ipv4.DstIP.String() | |
case Out: | |
remoteIP = ipv4.SrcIP.String() | |
} | |
case layers.LayerTypeIPv6: | |
ipv6 := layer.(*layers.IPv6) | |
switch direction { | |
case In: | |
remoteIP = ipv6.DstIP.String() | |
case Out: | |
remoteIP = ipv6.SrcIP.String() | |
} | |
} | |
} | |
if remoteMac != "" { | |
ipMacList.add(remoteMac, remoteIP) | |
} | |
packetCount++ | |
if packetCount > 1000 { | |
ipMacList.dump() | |
packetCount = 0 | |
} | |
} | |
} | |
/* ------------------------------------------------- */ | |
type IpMacList struct { | |
hosts *KnownHosts | |
ips map[string]map[string]int | |
} | |
func newIpMacList() *IpMacList { | |
return &IpMacList{ | |
hosts: newKnownHosts(), | |
ips: make(map[string]map[string]int), | |
} | |
} | |
func (l *IpMacList) add(mac, ip string) { | |
if _, has := l.ips[ip]; !has { | |
l.ips[ip] = make(map[string]int) | |
} | |
if v, has := l.ips[ip][mac]; !has { | |
l.ips[ip][mac] = 1 | |
} else { | |
l.ips[ip][mac] = v + 1 | |
} | |
} | |
func (l *IpMacList) dump() { | |
uniqueMacsList := make([]string, 0) | |
uniqueMacsMap := make(map[string]int) | |
for _, macs := range l.ips { | |
if len(macs) != 1 { | |
continue | |
} | |
for mac, _ := range macs { | |
mac = mac[1:] | |
if _, has := uniqueMacsMap[mac]; !has { | |
uniqueMacsMap[mac] = 0 | |
uniqueMacsList = append(uniqueMacsList, mac) | |
} | |
} | |
} | |
sort.Strings(uniqueMacsList) | |
for _, mac := range uniqueMacsList { | |
l.dumpIPs(func(ip string, macs map[string]int) bool { | |
return len(macs) == 1 && (macs[">"+mac] != 0 || macs["<"+mac] != 0) | |
}) | |
} | |
l.dumpIPs(func(ip string, macs map[string]int) bool { | |
return len(macs) > 1 | |
}) | |
fmt.Println("-") | |
} | |
func (l *IpMacList) dumpIPs(filter func(ip string, macs map[string]int) bool) { | |
ips := make([]string, 0) | |
for ip, macs := range l.ips { | |
if filter(ip, macs) { | |
ips = append(ips, ip) | |
} | |
} | |
sort.Strings(ips) | |
hasContent := false | |
for _, ip := range ips { | |
nPackets := 0 | |
for _, n := range l.ips[ip] { | |
nPackets = nPackets + n | |
} | |
if nPackets < 100 { | |
continue | |
} | |
hasContent = true | |
fmt.Printf("%-32s:", l.hosts.ip2host(ip)) | |
for mac, n := range l.ips[ip] { | |
fmt.Printf(" %s(%d)", mac, n) | |
} | |
fmt.Println("") | |
} | |
if hasContent { | |
fmt.Println("") | |
} | |
} | |
/* ------------------------------------------------- */ | |
type KnownHosts struct { | |
names map[string]string | |
} | |
func newKnownHosts() *KnownHosts { | |
names := make(map[string]string) | |
if data, err := ioutil.ReadFile("./hosts.txt"); err == nil { | |
for _, line := range strings.Split(string(data), "\n") { | |
line = strings.Replace(line, "\t", " ", -1) | |
for strings.Contains(line, " ") { | |
line = strings.ReplaceAll(line, " ", " ") | |
} | |
p := strings.Split(line, " ") | |
if len(p) >= 2 { | |
names[strings.Trim(p[0], " ")] = strings.Trim(p[1], " ") | |
} | |
} | |
} | |
return &KnownHosts{names} | |
} | |
func (k *KnownHosts) ip2host(ip string) string { | |
if name, has := k.names[ip]; has && name != "" { | |
return name | |
} | |
return ip | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment