本文中主要代码参考https://github.com/timest/goscan进行结构上的修改并处理了其中一个并发访问Map的问题。
主要原理是利用gopacket
实现对网段的IPv4地址批量发送ARP包,并等待响应。
程序运行需要root权限。
主要目录结构如下图:
相关代码如下:
ip.go:
package util
import (
"bytes"
"github.com/labstack/gommon/log"
"math"
"net"
"strconv"
"strings"
)
type IP uint32
// 将 IP(uint32) 转换成 可读性IP字符串
func (ip IP) String() string {
var bf bytes.Buffer
for i := 1; i <= 4; i++ {
bf.WriteString(strconv.Itoa(int((ip >> ((4 - uint(i)) * 8)) & 0xff)))
if i != 4 {
bf.WriteByte('.')
}
}
return bf.String()
}
// 根据IP和mask换算内网IP范围
func Table(ipNet *net.IPNet) []IP {
ip := ipNet.IP.To4()
log.Info("本机ip:", ip)
var min, max IP
var data []IP
for i := 0; i < 4; i++ {
b := IP(ip[i] & ipNet.Mask[i])
min += b << ((3 - uint(i)) * 8)
}
one, _ := ipNet.Mask.Size()
max = min | IP(math.Pow(2, float64(32-one))-1)
log.Infof("内网IP范围:%s --- %s", min, max)
// max 是广播地址,忽略
// i & 0x000000ff == 0 是尾段为0的IP,根据RFC的规定,忽略
for i := min; i < max; i++ {
if i&0x000000ff == 0 {
continue
}
data = append(data, i)
}
return data
}
// []byte --> IP
func ParseIP(b []byte) IP {
return IP(IP(b[0])<<24 + IP(b[1])<<16 + IP(b[2])<<8 + IP(b[3]))
}
// string --> IP
func ParseIPString(s string) IP {
var b []byte
for _, i := range strings.Split(s, ".") {
v, _ := strconv.Atoi(i)
b = append(b, uint8(v))
}
return ParseIP(b)
}
// IPSlice ,实现了sort的排序接口
type IPSlice []IP
func (ip IPSlice) Len() int { return len(ip) }
func (ip IPSlice) Swap(i, j int) {
ip[i], ip[j] = ip[j], ip[i]
}
func (ip IPSlice) Less(i, j int) bool {
return ip[i] < ip[j]
}
sniffer.go:
package sniffer
import (
"QSsipServer/sniffer/util"
"context"
"flag"
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"log"
"math"
"net"
"os"
"sort"
"strings"
"sync"
"time"
)
type IP uint32
type Sniffer struct {
ipNet *net.IPNet // ipNet 存放 IP地址和子网掩码
localHaddr net.HardwareAddr // 本机的mac地址,发以太网包需要用到
iface string // 网络接口名
mu sync.RWMutex // 锁
data map[string]Info // 存放最终的数据,key[string] 存放的是IP地址
t *time.Ticker // 计时器,在一段时间没有新的数据写入data中,退出程序,反之重置计时器
do chan string // 操作指令
}
const (
// 计时器指令
START = "start"
END = "end"
)
type Info struct {
Mac net.HardwareAddr // IP地址
Hostname string // 主机名
Manuf string // 厂商信息
}
// 获取本地网络信息
func (snf *Sniffer) setupNetInfo(f string) {
var ifs []net.Interface
var err error
if f == "" {
ifs, err = net.Interfaces()
} else {
// 已经选择iface
var it *net.Interface
it, err = net.InterfaceByName(f)
if err == nil {
ifs = append(ifs, *it)
}
}
if err != nil {
log.Fatal("无法获取本地网络信息:", err)
}
for _, it := range ifs {
addr, _ := it.Addrs()
for _, a := range addr {
if ip, ok := a.(*net.IPNet); ok && !ip.IP.IsLoopback() {
if ip.IP.To4() != nil {
snf.ipNet = ip
snf.localHaddr = it.HardwareAddr
snf.iface = it.Name
goto END
}
}
}
}
END:
if snf.ipNet == nil || len(snf.localHaddr) == 0 {
log.Fatal("无法获取本地网络信息")
}
}
func (snf *Sniffer) localHost() {
host, _ := os.Hostname()
snf.mu.RLock()
snf.data[snf.ipNet.IP.String()] = Info{Mac: snf.localHaddr, Hostname: strings.TrimSuffix(host, ".local"), Manuf: "nil"}
snf.mu.RUnlock()
}
// 根据IP和mask换算内网IP范围
func Table(ipNet *net.IPNet) []util.IP {
ip := ipNet.IP.To4()
log.Println("本机ip:", ip)
var min, max util.IP
var data []util.IP
for i := 0; i < 4; i++ {
b := util.IP(ip[i] & ipNet.Mask[i])
min += b << ((3 - uint(i)) * 8)
}
one, _ := ipNet.Mask.Size()
max = min | util.IP(math.Pow(2, float64(32-one))-1)
log.Println("内网IP范围:%s --- %s", min, max)
// max 是广播地址,忽略
// i & 0x000000ff == 0 是尾段为0的IP,根据RFC的规定,忽略
for i := min; i < max; i++ {
if i&0x000000ff == 0 {
continue
}
data = append(data, i)
}
return data
}
// 针对网段内所有IPv4地址发送ARP
func (snf *Sniffer) sendARP() {
// ips 是内网IP地址集合
ips := Table(snf.ipNet)
for _, ip := range ips {
go snf.sendArpPackage(ip)
}
}
// 监听ARP应答
func (snf *Sniffer) listenARP(ctx context.Context) {
handle, err := pcap.OpenLive(snf.iface, 1024, false, 10*time.Second)
if err != nil {
log.Fatal("pcap打开失败:", err)
}
defer handle.Close()
handle.SetBPFFilter("arp")
ps := gopacket.NewPacketSource(handle, handle.LinkType())
for {
select {
case <-ctx.Done():
return
case p := <-ps.Packets():
arp := p.Layer(layers.LayerTypeARP).(*layers.ARP)
if arp.Operation == 2 {
mac := net.HardwareAddr(arp.SourceHwAddress)
//m := manuf.Search(mac.String())
fmt.Println("found one")
snf.pushData(util.ParseIP(arp.SourceProtAddress).String(), mac, "", "")
//if strings.Contains(m, "Apple") {
// //go sendMdns(ParseIP(arp.SourceProtAddress), mac)
//} else {
// //go sendNbns(ParseIP(arp.SourceProtAddress), mac)
//}
}
}
}
}
// 发送arp包
// ip 目标IP地址
func (snf *Sniffer) sendArpPackage(ip util.IP) {
srcIp := net.ParseIP(snf.ipNet.IP.String()).To4()
dstIp := net.ParseIP(ip.String()).To4()
if srcIp == nil || dstIp == nil {
log.Fatal("ip 解析出问题")
}
// 以太网首部
// EthernetType 0x0806 ARP
ether := &layers.Ethernet{
SrcMAC: snf.localHaddr,
DstMAC: net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
EthernetType: layers.EthernetTypeARP,
}
a := &layers.ARP{
AddrType: layers.LinkTypeEthernet,
Protocol: layers.EthernetTypeIPv4,
HwAddressSize: uint8(6),
ProtAddressSize: uint8(4),
Operation: uint16(1), // 0x0001 arp request 0x0002 arp response
SourceHwAddress: snf.localHaddr,
SourceProtAddress: srcIp,
DstHwAddress: net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
DstProtAddress: dstIp,
}
buffer := gopacket.NewSerializeBuffer()
var opt gopacket.SerializeOptions
gopacket.SerializeLayers(buffer, opt, ether, a)
outgoingPacket := buffer.Bytes()
handle, err := pcap.OpenLive(snf.iface, 2048, false, 30*time.Second)
if err != nil {
log.Fatal("pcap打开失败:", err)
}
defer handle.Close()
err = handle.WritePacketData(outgoingPacket)
if err != nil {
log.Fatal("发送arp数据包失败..")
}
}
// 格式化输出结果
// xxx.xxx.xxx.xxx xx:xx:xx:xx:xx:xx hostname manuf
// xxx.xxx.xxx.xxx xx:xx:xx:xx:xx:xx hostname manuf
func (snf *Sniffer) PrintData() {
var keys util.IPSlice
for k := range snf.data {
keys = append(keys, util.ParseIPString(k))
}
sort.Sort(keys)
for _, k := range keys {
d := snf.data[k.String()]
mac := ""
if d.Mac != nil {
mac = d.Mac.String()
}
fmt.Printf("%-15s %-17s %-30s %-10s\n", k.String(), mac, d.Hostname, d.Manuf)
}
}
// 将抓到的数据集加入到data中,同时重置计时器
func (snf *Sniffer) pushData(ip string, mac net.HardwareAddr, hostname, manuf string) {
// 停止计时器
snf.do <- START
// FIXED: 这种局部的锁有什么用
//var mu sync.RWMutex
snf.mu.RLock()
defer func() {
// 重置计时器
snf.do <- END
snf.mu.RUnlock()
}()
if _, ok := snf.data[ip]; !ok {
snf.data[ip] = Info{Mac: mac, Hostname: hostname, Manuf: manuf}
return
}
info := snf.data[ip]
if len(hostname) > 0 && len(info.Hostname) == 0 {
info.Hostname = hostname
}
if len(manuf) > 0 && len(info.Manuf) == 0 {
info.Manuf = manuf
}
if mac != nil {
info.Mac = mac
}
snf.data[ip] = info
}
// 运行
func (snf *Sniffer) Run() {
// allow non root user to execute by compare with euid
if os.Geteuid() != 0 {
log.Fatal("goscan must run as root.")
}
flag.StringVar(&snf.iface, "I", "", "Network interface name")
flag.Parse()
// 初始化 data
snf.data = make(map[string]Info)
snf.do = make(chan string)
snf.setupNetInfo(snf.iface)
ctx, cancel := context.WithCancel(context.Background())
go snf.listenARP(ctx)
go snf.sendARP()
go snf.localHost()
snf.t = time.NewTicker(30 * time.Second)
for {
select {
case <-snf.t.C:
snf.PrintData()
cancel()
goto END
case d := <-snf.do:
switch d {
case START:
snf.t.Stop()
case END:
// 接收到新数据,重置30秒的计数器
snf.t = time.NewTicker(30 * time.Second)
}
}
}
END:
}
运行时,在对应main函数中声明并使用Sniffer的Run()
方法即可,使用-I
指定对应的网络接口。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)