Skip to content

Instantly share code, notes, and snippets.

@ronanj
Created March 20, 2022 02:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ronanj/627a6931ad4c0e24f8c225ef79c9bf0f to your computer and use it in GitHub Desktop.
Save ronanj/627a6931ad4c0e24f8c225ef79c9bf0f to your computer and use it in GitHub Desktop.
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