基于Go的网络嗅探测试

2023-05-16

本文中主要代码参考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(使用前将#替换为@)

基于Go的网络嗅探测试 的相关文章

  • PHP面试题

    PHP知识 php反转字符串的三种方法 1 把分割字符串到一个数组中 xff0c 然后遍历拼接 xff1a lt php function joinStrrev str if strlen str lt 61 1 return str ne
  • tp6实现商城后台登录功能

    后台登录逻辑 1 没有登录状态 xff0c 访问后台页面都应该跳转到登录页 已经登录状态 xff1a 1 访问登录页面 xff0c 应该跳转到后台首页 解决方案 xff1a 后台登录拦截 xff1a 1 传统common base php控
  • Jenkins环境部署

    简介 Jenkins是一款开源 CI amp CD 软件 xff0c 用于自动化各种任务 xff0c 包括构建 测试和部署软件 Jenkins 支持各种运行方式 xff0c 可通过系统包 Docker 或者通过一个独立的 Java 程序 J
  • Nginx中Return指令使用

    Return指令使用 1 返回状态码 return 404 location test set name 39 user123 39 return 404 name curl i http 192 168 56 10 test 2 返回字符
  • MySQL触发器

    介绍 触发器是与表有关的数据库对象 xff0c 指在 insert update delete 之前或之后 xff0c 触发并执行触发器中定义的SQL语句集合 触发器的这种特性可以协助应用在数据库端确保数据的完整性 日志记录 数据校验等操作
  • 制作composer包提供sdk扩展

    目录 1 初始化包 2 将代码推送到github远程仓库 3 为写好扩展包打上tag标签标记当前代码版本 4 将包发布到包管理平台 初始化包 xff0c 生成 Creates a basic composer json file in cu
  • Go环境搭建与IDE开发工具配置

    安装Go语言编译器 Go语言编译器 61 编译器 61 将源代码编译为可执行程序 61 源代码 61 程序员使用高级语言所书写的代码文件 61 高级语言 61 c c 43 43 go 61 机器语言 61 0和1构成 xff0c 机器能直
  • 计算机中的速率、带宽、时延、利用率解读

    计算机网络的性能一般是指它的几个重要的性能指标 但除了这些重要的性能指标外 xff0c 还有一些非性能特征 xff08 nonperformance characteristics xff09 也对计算机网络的性能有很大的影响 那么 xff
  • 无人机原理

    文章目录 1基本运动2为何无人机螺旋桨多为偶数3机器人的运动控制4加速度计5陀螺仪6姿态角解算欧拉角的平面换算 1基本运动 为了平衡扭矩 xff0c 上图中1 3逆时针转动 xff0c 电机2 4顺时针转动进行反扭矩对消 上升 F1 61
  • 新浪微博PC端模拟登陆

    点击上方 程序人生 xff0c 选择 置顶公众号 第一时间关注程序猿 xff08 媛 xff09 身边的故事 图 xff1a Fantastic Beasts and Where to Find Them 投稿 星星在线 简介 一个从妹子图
  • win10下 docker build 报错 “Failed to fetch xxxxxxxx Temporary failure resolving 'archive.ubuntu.com'”

    错误信息 在docker中搭建禅道的时候执行docker build t zentao 命令后执行到apt get update amp amp apt get install指令的时候报错 xff0c 错误如下 xff1a W Faile
  • docker-ros-gui

    主机环境 系统 Ubuntu 16 04 1 安装docker 1 1 在线安装 物理机 默认使用当前最新版本 1 更新apt包索引 xff1a sudo apt get update 2 安装以下包以使apt可以通过HTTPS使用存储库
  • halcon opencv 图像处理面试指南

    珠海某上市公司算法总监 xff1a 1 相机标定的原理与坐标系之间的转换 xff0c 如何转换 2 激光三角原理 xff0c 如何搭建 3 测量拟合的过程中有哪些算子 xff0c 原理什么 xff0c 接着问5 xff0c 跌代多少次 xf
  • 【3D计算机视觉】从PointNet到PointNet++理论及pytorch代码

    从PointNet到PointNet 43 43 理论及代码详解 1 点云是什么1 1 三维数据的表现形式1 2 为什么使用点云1 3 点云上以往的相关工作 2 PointNet2 1 基于点云的置换不变性2 1 1 由对称函数到Point
  • 在ubuntu上安装mavlink-router

    版本说明 xff1a ubuntu 20 0 04 mavlink router 2 为了安装mavlink router 需要预先安装一些依赖库 xff1a autoconf libtool python future python3 f
  • PX4多机仿真(gazebo)

    版本说明 xff1a 操作系统 宿主机 xff08 或开发机 xff09 xff1a ubuntu server 20 0 04 thinkpad 目标机 xff08 机载计算机 xff09 xff1a ubuntu server 20 0
  • 为PX4建立J-Link Eclipse交叉调试环境

    版本说明 1 自驾仪 xff1a Holybro的Pixhawk 4 2 PX4版本 xff1a 1 12 3 宿主机 xff1a Thinkpad笔记本 43 Ubuntu 20 04 2 LTS 4 Eclipse xff1a 2022
  • 为Ubuntu增加swap空间并调整性能

    版本说明 xff1a Raspberry Pi 3B 43 Ubuntu 20 04 server 本文参考翻译自这篇文章 SWAP文件的设置 Swap是硬盘存储的一部分 xff0c 它被预留给操作系统 当RAM中不再有足够的空间来保存正在
  • PX4的中间件Fast DDS

    本文参考官网 xff1a RTPS DDS Interface PX4 Fast RTPS DDS Bridge PX4 User Guide 版本说明 xff1a PX4固件版本 xff1a 1 13 0d 自驾仪 xff1a PIXHA
  • 为PX4建立ROS2环境,x86与树梅派

    本文参考官网 xff1a a href https docs px4 io master en ros ros2 comm html title htt htt a ps docs px4 io master en ros ros2 com

随机推荐