GO-创建TUN/TAP接口并实现报文的收发

  背景

        最近在学习Linux 内核二三层转发的知识,同时也在学习GO编程,于是有了如何通过Go实现创建TUN/TAP接口,并实现收发包的想法,通过查阅相关资料及不断调试终于实现,在此分享给大家,希望能共同学习交流。

概念

        TUN/TAP 设备一端连着操作系统协议栈,另一端连着用户空间的程序:用户空间程序 --- tap0&tun0 --- TCP/IP 协议栈 --- ping,如下图所示:

        TUN 工作在三层,无 MAC 地址,无法加入网桥;TAP 工作在二层,更接近物理网卡;

        TUN 设备常用于 VPN 场景;TAP 设备常用于虚拟机网卡;

实现

库说明

        GO下实现创建TAP/TUN接口及收发包,主要依赖如下库:

        water:是一个用于TUN/TAP接口的原生库

        gopacket:是GO语言中用于处理网络数据包的一个库,它提供了一种方便的方式来解析和构建网络数据包,支持多种网络协议的解析。gopacket还包含许多子包用于提供额外的功能,包括 :layers、pcap、pfring、afpacket、tcpassembly;

        gopacket/pcap:用于读取和分析网络流量,可以从具体线路上(OpenLive)读取,也可从具体的pcap文件(OpenOffline)中读取。

        gopacket/layers:用于将报文解码为具体协议的片段。

        具体使用可参考官方文档使用说明:GO 库使用说明手册

GO下TAP创建及收发包

package main

import (
	"log"
	"github.com/songgao/water"
	"net"
	"github.com/google/gopacket"
	"github.com/google/gopacket/pcap"
	"github.com/google/gopacket/layers"
	"os/exec"
	"fmt"
)

func main() {
	/* 依据配置信息创建并生成相应接口 */
	config := water.Config{
		/* 设置接口类型 */
		DeviceType : water.TAP,
	}
	/* 设置接口名 */
	config.Name = "tap0"

	/* 依据配置信息创建并生成相应接口 */
	iface, err := water.New(config)
	if err != nil {
		log.Fatal(err)
	}

	/* 通exec库,构建linux 命令 */
	cmd := exec.Command("ip", "addr", "add", "10.1.1.200/24", "dev", "tap0")
	/* 命令执行,为接口绑定IP */
	_ = cmd.Run()

	/* 设置接口状态为up */
	cmd = exec.Command("ip", "link", "set", "tap0", "up")
	_ = cmd.Run()

	/* 构建SMAC, SIP 由于回复ARP */
	sourceMACAddr, _:= net.ParseMAC("00:00:00:00:00:01")
	sourceIPAddr := net.ParseIP("10.1.1.200")

	/* 通pcap库捕获tap0接口上的报文,打开tap0接口 */
	handle, err := pcap.OpenLive("tap0", 1600, true, pcap.BlockForever)
	if err != nil {
		log.Fatal(err)
	}
	/* 构建包接收源 */
	packetSource := gopacket.NewPacketSource(handle, handle.LinkType())

	/* 接收数据包 */
	for packet := range packetSource.Packets() {
		/* 将数据报文解码为ARP报文 */
		arpLayer := packet.Layer(layers.LayerTypeARP)
		if arpLayer != nil {
			/* 强制转换并获取ARP头部信息 */
			arpPacket, _ := arpLayer.(*layers.ARP)
			/* 判断是否为ARP请求 */
			if arpPacket.Operation == layers.ARPRequest {
				// 输出 ARP 报文信息
				fmt.Println("ARP Operation:", arpPacket.Operation)
				fmt.Println("ARP Source Hardware Address:", arpPacket.SourceHwAddress)
				fmt.Println("ARP Source Protocol Address:", arpPacket.SourceProtAddress)
				fmt.Println("ARP Target Hardware Address:", arpPacket.DstHwAddress)
				fmt.Println("ARP Target Protocol Address:", arpPacket.DstProtAddress)
				fmt.Println("--------------------------")

				/* 构造arpReply 头部信息 */
				arpReply := &layers.ARP{
					AddrType:          layers.LinkTypeEthernet,
					Protocol:          layers.EthernetTypeIPv4,
					HwAddressSize:     6,
					ProtAddressSize:   4,
					Operation:         layers.ARPReply,
					SourceHwAddress:   sourceMACAddr,
					SourceProtAddress: sourceIPAddr.To4(),
					DstHwAddress:      arpPacket.SourceHwAddress,
					DstProtAddress:    arpPacket.SourceProtAddress,
				}

				/* 构建以太网头部信息, ARP属于三层协议,报文格式却是二层报文 */
				ethernetLayer := &layers.Ethernet{
					SrcMAC:       sourceMACAddr,
					DstMAC:       arpPacket.SourceHwAddress,
					EthernetType: layers.EthernetTypeARP,
				}

				/* 构建并通过tun0接口发送数据包 */
				frame1 := gopacket.NewSerializeBuffer()
				gopacket.SerializeLayers(frame1, gopacket.SerializeOptions{}, ethernetLayer, arpReply)
				_, err = iface.Write(frame1.Bytes())
			}
			continue
		}

		/* 将数据报文解码为ICMPv4报文 */
		icmpLayer := packet.Layer(layers.LayerTypeICMPv4)
		if icmpLayer != nil {
			icmpPacket, _ := icmpLayer.(*layers.ICMPv4)
			// 输出 ICMP 报文信息
			fmt.Printf("ICMP Type: %d\n", icmpPacket.TypeCode.Type())
			fmt.Printf("ICMP Code: %d\n", icmpPacket.TypeCode.Code())
			fmt.Printf("ICMP Checksum: %d\n", icmpPacket.Checksum)
			fmt.Printf("ICMP Payload: %v\n", icmpPacket.Payload)
			fmt.Println("--------------------------")

			/* 依据icmp的 type ,code判断是否为 icmp请求报文 */
			if icmpPacket.TypeCode.String() == "EchoRequest" {
				/* 构造 icmpreply 报文头部信息 */
				icmpReplyPacket := &layers.ICMPv4{
					TypeCode: layers.ICMPv4TypeEchoReply,
					Id:       icmpPacket.Id,
					Seq:      icmpPacket.Seq,
				}

				/* icmpLayer只获取了报文的三层及以上信息,不需设置以太网头部信息 */
				ipPacket := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
				ipPacket.DstIP, ipPacket.SrcIP = ipPacket.SrcIP, ipPacket.DstIP
				fmt.Printf("ICMP SIP: %v\n", ipPacket.SrcIP)
				fmt.Printf("ICMP DIP: %v\n", ipPacket.DstIP)

				/* tap口工作在二层,需要构建以太网头部信息 */
				ethernetPacket := packet.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
				ethernetPacket.DstMAC, ethernetPacket.SrcMAC = ethernetPacket.SrcMAC, ethernetPacket.DstMAC

				/* 构造并发送icmp reply报文 */
				frame2 := gopacket.NewSerializeBuffer()
				gopacket.SerializeLayers(frame2, gopacket.SerializeOptions{
					FixLengths:       true,
					ComputeChecksums: true,
				},ethernetPacket, ipPacket, icmpReplyPacket, gopacket.Payload(icmpPacket.Payload))

				_, err = iface.Write(frame2.Bytes())
			}
			continue
		}
	}
}

 效果验证

        注意:该程序只适用于Linux系统。

        1)在shell窗口执行./tap程序

        2)在另一个shell窗口通过ping -c3 -i2 10.1.1.200

        3)shell1可以接收到ARP,ICMP请求报文,并构造回复ARP,ICMP响应报文

        shell1效果如下:

        shell2 ping报文有回复,且arp -a可以查看到对应的arp表信息,如下所示:

GO下TUN创建及收发包

package main

/* 导入依赖包 */
import (
	"fmt"
	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
	"github.com/songgao/water"
	"log"
	"os/exec"
)

func main() {
	/* 通过water库配置 tun接口信息 */
	config := water.Config{
		/* 设置接口类型 */
		DeviceType: water.TUN,
	}
	/* 设置接口名 */
	config.Name = "tun0"

	/* 依据配置信息创建并生成相应接口 */
	iface, err := water.New(config)
	if err != nil {
		log.Fatal(err)
	}

	/* 通exec库,构建linux 命令 */
	cmd := exec.Command("ip", "addr", "add", "10.1.1.100/24", "dev", "tun0")
	/* 命令执行,为接口绑定IP */
	_ = cmd.Run()

	/* 设置接口状态为up */
	cmd = exec.Command("ip", "link", "set", "tun0", "up")
	_ = cmd.Run()

	/* 通pcap库捕获tun0接口上的报文,打开tun0接口 */
	handle, err := pcap.OpenLive("tun0", 1600, true, pcap.BlockForever)
	if err != nil {
		log.Fatal(err)
	}
	/* 构建包接收源 */
	packetSource := gopacket.NewPacketSource(handle, handle.LinkType())

	/* 接收数据包 */
	for packet := range packetSource.Packets() {
		/* 将数据报文解码为ICMPv4报文 */
		icmpLayer := packet.Layer(layers.LayerTypeICMPv4)
		if icmpLayer != nil {
			icmpPacket, _ := icmpLayer.(*layers.ICMPv4)
			// 输出 ICMP 报文信息
			fmt.Printf("ICMP Type: %d\n", icmpPacket.TypeCode.Type())
			fmt.Printf("ICMP Code: %d\n", icmpPacket.TypeCode.Code())
			fmt.Printf("ICMP Checksum: %d\n", icmpPacket.Checksum)
			fmt.Printf("ICMP Payload: %v\n", icmpPacket.Payload)
			fmt.Println("--------------------------")

			/* 依据icmp的 type ,code判断是否为 icmp请求报文 */
			if icmpPacket.TypeCode.String() == "EchoRequest" {
				/* 构造 icmpreply 报文头部信息 */
				icmpReplyPacket := &layers.ICMPv4{
					TypeCode: layers.ICMPv4TypeEchoReply,
					Id:       icmpPacket.Id,
					Seq:      icmpPacket.Seq,
				}

				/* icmpLayer只获取了报文的三层及以上信息,不需设置以太网头部信息 */
				ipPacket := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
				ipPacket.DstIP, ipPacket.SrcIP = ipPacket.SrcIP, ipPacket.DstIP
				fmt.Printf("ICMP SIP: %v\n", ipPacket.SrcIP)
				fmt.Printf("ICMP DIP: %v\n", ipPacket.DstIP)

				/* 构造并发送icmp reply报文 */
				frame2 := gopacket.NewSerializeBuffer()
				gopacket.SerializeLayers(frame2, gopacket.SerializeOptions{
					FixLengths:       true,
					ComputeChecksums: true,
				}, ipPacket, icmpReplyPacket, gopacket.Payload(icmpPacket.Payload))

				_, err = iface.Write(frame2.Bytes())
			}
		}
	}
}

效果验证

        注意:该程序只适用于Linux系统。

        1)在shell窗口执行./tun程序

        2)在另一个shell窗口通过ping -c3 -i2 10.1.1.200

        3)shell1可以接收到ICMP请求报文,并构造回复ICMP响应报文

         shell1效果如图所示:

        shell2效果如图所示:

 

参考链接 

Linux 网络设备- TUN/TAP

  • 15
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值