vnic/examples/complete/main.go

301 lines
6.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"flag"
"fmt"
"log"
"net"
"os"
"os/signal"
"syscall"
"time"
"git.kingecg.top/kingecg/vnic"
)
// 命令行参数
var (
ipAddress string
packetSize int
interval int
verbose bool
)
func init() {
// 解析命令行参数
flag.StringVar(&ipAddress, "ip", "192.168.100.1/24", "虚拟网卡的IP地址 (CIDR格式)")
flag.IntVar(&packetSize, "size", 64, "测试数据包大小 (字节)")
flag.IntVar(&interval, "interval", 5, "发送数据包的间隔 (秒)")
flag.BoolVar(&verbose, "verbose", false, "启用详细日志")
flag.Parse()
}
func main() {
// 配置日志格式
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)
log.Println("虚拟网卡完整示例启动")
// 验证IP地址格式
if _, _, err := net.ParseCIDR(ipAddress); err != nil {
log.Fatalf("无效的IP地址格式: %v", err)
}
// 创建虚拟网卡配置
config := vnic.Config{
IP: ipAddress,
}
// 创建虚拟网卡
log.Printf("正在创建虚拟网卡 (IP: %s)...", ipAddress)
nic, err := vnic.New(config)
if err != nil {
log.Fatalf("创建虚拟网卡失败: %v", err)
}
// 设置清理函数
defer cleanupResources(nic)
log.Printf("虚拟网卡创建成功:")
log.Printf("- 接口名称: %s", nic.Name())
log.Printf("- IP地址: %s", ipAddress)
// 创建一个通道来监听系统信号
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
// 启动数据包处理器
stopCh := make(chan struct{})
go packetReceiver(nic, stopCh)
go packetSender(nic, stopCh)
// 等待中断信号
sig := <-sigCh
fmt.Printf("\n收到信号 %v正在关闭...\n", sig)
// 通知所有goroutine停止
close(stopCh)
// 给goroutine一些时间来完成清理
time.Sleep(500 * time.Millisecond)
}
// cleanupResources 负责清理资源
func cleanupResources(nic *vnic.VirtualNIC) {
log.Println("正在关闭虚拟网卡...")
if err := nic.Close(); err != nil {
log.Printf("关闭虚拟网卡时出错: %v", err)
} else {
log.Println("虚拟网卡已成功关闭")
}
}
// packetReceiver 从虚拟网卡读取数据包
func packetReceiver(nic *vnic.VirtualNIC, stopCh <-chan struct{}) {
log.Println("数据包接收器已启动")
// 创建缓冲区
buffer := make([]byte, 2048)
for {
// 检查是否收到停止信号
select {
case <-stopCh:
log.Println("数据包接收器正在关闭")
return
default:
// 设置读取超时,以便我们可以定期检查停止信号
// 注意:在实际应用中,你可能需要使用更复杂的方法来处理这个问题
nic.Read(buffer)
// 解析IP数据包
if verbose && len(buffer) > 0 {
parseIPPacket(buffer)
}
}
}
}
// packetSender 定期向虚拟网卡发送测试数据包
func packetSender(nic *vnic.VirtualNIC, stopCh <-chan struct{}) {
log.Println("数据包发送器已启动")
// 解析IP地址以获取网络信息
ip, ipNet, _ := net.ParseCIDR(ipAddress)
// 计算网络中的第二个IP地址作为目标
// 这只是一个示例在实际应用中你可能需要一个有效的目标IP
dstIP := calculateNextIP(ip, ipNet)
// 创建测试数据包
packet := createTestPacket(ip, dstIP, packetSize)
// 设置定时器
ticker := time.NewTicker(time.Duration(interval) * time.Second)
defer ticker.Stop()
for {
select {
case <-stopCh:
log.Println("数据包发送器正在关闭")
return
case <-ticker.C:
if _, err := nic.Write(packet); err != nil {
log.Printf("发送数据包时出错: %v", err)
} else if verbose {
log.Printf("已发送 %d 字节的测试数据包到 %s", len(packet), dstIP)
}
}
}
}
// calculateNextIP 计算网络中的下一个IP地址
func calculateNextIP(ip net.IP, ipNet *net.IPNet) net.IP {
// 复制IP以避免修改原始IP
nextIP := make(net.IP, len(ip))
copy(nextIP, ip)
// 将最后一个字节加1
nextIP[len(nextIP)-1]++
// 确保IP仍然在网络范围内
if !ipNet.Contains(nextIP) {
// 如果超出范围使用网络的第一个可用IP
copy(nextIP, ipNet.IP)
nextIP[len(nextIP)-1]++
}
return nextIP
}
// createTestPacket 创建一个测试IP数据包
func createTestPacket(srcIP, dstIP net.IP, size int) []byte {
// 确保最小大小
if size < 20 {
size = 20 // IP头部的最小大小
}
// 创建一个基本的IP头部
packet := make([]byte, size)
// IP版本(4)和头部长度(5*4=20字节)
packet[0] = 0x45
// 服务类型
packet[1] = 0x00
// 总长度 (大端字节序)
packet[2] = byte(size >> 8)
packet[3] = byte(size)
// 标识
packet[4] = 0x00
packet[5] = 0x00
// 标志和片偏移
packet[6] = 0x00
packet[7] = 0x00
// TTL
packet[8] = 64
// 协议 (1 = ICMP)
packet[9] = 0x01
// 头部校验和 (暂时为0)
packet[10] = 0x00
packet[11] = 0x00
// 源IP地址
copy(packet[12:16], srcIP.To4())
// 目标IP地址
copy(packet[16:20], dstIP.To4())
// 计算IP头部校验和
checksum := calculateIPChecksum(packet[:20])
packet[10] = byte(checksum >> 8)
packet[11] = byte(checksum)
// 如果有足够的空间添加一些ICMP数据
if size >= 28 {
// ICMP Echo请求
packet[20] = 0x08 // 类型: Echo
packet[21] = 0x00 // 代码: 0
packet[22] = 0x00 // 校验和 (暂时为0)
packet[23] = 0x00
// ICMP标识符和序列号
packet[24] = 0x00
packet[25] = 0x01
packet[26] = 0x00
packet[27] = 0x01
// 填充剩余空间
for i := 28; i < size; i++ {
packet[i] = byte(i % 256)
}
}
return packet
}
// calculateIPChecksum 计算IP头部的校验和
func calculateIPChecksum(header []byte) uint16 {
var sum uint32
// 将头部视为16位整数的序列并求和
for i := 0; i < len(header); i += 2 {
if i+1 < len(header) {
sum += uint32(header[i])<<8 | uint32(header[i+1])
} else {
sum += uint32(header[i]) << 8
}
}
// 将进位加到结果中
for sum > 0xffff {
sum = (sum & 0xffff) + (sum >> 16)
}
// 取反
return ^uint16(sum)
}
// parseIPPacket 解析并显示IP数据包的基本信息
func parseIPPacket(packet []byte) {
if len(packet) < 20 {
log.Println("收到无效的IP数据包 (太短)")
return
}
// 提取版本和头部长度
version := packet[0] >> 4
headerLen := int(packet[0]&0x0F) * 4
if version != 4 {
log.Printf("收到非IPv4数据包 (版本: %d)", version)
return
}
// 提取总长度
totalLen := int(packet[2])<<8 | int(packet[3])
// 提取协议
protocol := packet[9]
// 提取源IP和目标IP
srcIP := net.IPv4(packet[12], packet[13], packet[14], packet[15])
dstIP := net.IPv4(packet[16], packet[17], packet[18], packet[19])
// 显示基本信息
log.Printf("收到IP数据包: 长度=%d, 协议=%d, %s -> %s",
totalLen, protocol, srcIP, dstIP)
// 如果是ICMP协议显示更多信息
if protocol == 1 && len(packet) >= headerLen+2 {
icmpType := packet[headerLen]
icmpCode := packet[headerLen+1]
log.Printf(" ICMP: 类型=%d, 代码=%d", icmpType, icmpCode)
}
}