零入门kubernetes网络实战-25->基于tap虚拟网络设备的测试用例以及协议栈封装解析介绍(helloworld级别)

《零入门kubernetes网络实战》视频专栏地址

https://www.ixigua.com/7193641905282875942

本篇文章视频地址(稍后上传)


本篇文章主要是分享一下tap虚拟网络设备。

创建tap网络设备的方式跟tun完全一样,只需要将类型改为tap即可。

这里不再占用篇幅介绍了。

1、本篇文章的核心点

  • tap虚拟网络设备一般用在什么场景下
  • tap虚拟网络设备使用什么文件描述符
  • 简单验证/dev/net/tun文件描述符是否支持并发读写操作?即验证在同一个宿主机上实现多个kvm类型的虚拟机网络通信的关键点

2、tap虚拟网络设备一般用在什么场景下

  • openvpn
  • kvm

3、tap虚拟网络设备使用什么文件描述符

tap跟tun一样,都是将此文件

/dev/net/tun

作为文件描述符的。
通过此文件跟虚拟网络设备tap、tun进行数据交互。

在centos7.5系统下,其他其他并为测试。

4、简单验证/dev/net/tun文件描述符是否支持并发读写操作?即验证在同一个宿主机上实现多个kvm类型的虚拟机网络通信的关键点

在kvm里使用tap设备进行的网络构建,那么,

本小节主要是想测试一下,文件描述符/dev/net/tun是否支持并发的读写操作。

如果支持并发读写操作的话,那么,下面的网络构建是成立的,

即在同一个宿主机上可以创建多个kvm类型的虚拟机。

在这里插入图片描述

在上图中:

  • 在一个宿主机内部,通过物理网卡eth0跟外部网络进行数据通信
  • 在宿主机内部,创建一个虚拟网桥br0
  • 可以创建多个tap虚拟网络设备,
    • 这些tap设备挂载到虚拟网桥上br0
    • 并且这些tap设备都跟宿主机上的/dev/net/tun文件进行绑定,所有tap设备通过此文件进行读写
  • 创建好的kvm类型的虚拟机里可以将数据包写入到宿主机上的/dev/net/tun文件里,
  • 数据一旦到达/dev/net/tunW文件里,就相当于到达了虚拟网络设备tap里了,即到达了虚拟网桥br0

这样的话,kvm类型的虚拟机就可以跟外界进行通信了。

因此,主要是测试/dev/net/tun是否支持并发读写,其他相关操作不再测试了,如将tap设备挂载到虚拟网桥上。

4.1、测试/dev/net/tun支持并发读写的方案

我们创建两个测试用例,两个测试用例里都绑定了同一个宿主机上的/dev/net/tun文件,

运行这两个测试用例,观察是否出现问题,没有问题说明支持并发读写操作:

  • 测试用例1,如何实现?
    • 在前面的文章中,我们使用了tun设备来实现点对点的VPN,在此基础上,我们对其代码进行改造,将tun设备改成tap设备来实现。
  • 测试用例2,如何实现?
    • 将前面文章<<tun设备编程ICMP协议>>里的代码直接拿过来使用即可。

4.2、测试环境

两台虚拟机

  • centos7.5 10.211.55.122
  • centos7.5 10.211.55.123

4.3、测试用例1:基于tap虚拟网络设备实现点对点的VPN

4.3.1、代码结构

在这里插入图片描述

并没有严格意义上的客户端,服务器端

请求由哪个服务发起的,被称为客户端,

被请求服务,称为服务器端。

4.3.2、客户端代码

4.3.2.1、main.go文件
package main

import (
	"bytes"
	"fmt"
	"github.com/vishvananda/netlink"
	"log"
	"net"
	"os"
	"strings"
	"syscall"
	"time"
	"unsafe"
)

const (
	tapName    = "tap99"
	tapDevice  = "/dev/net/tun"
	ifnameSize = 16
	localAddr  = "10.211.55.122:8285"
	remoteAddr = "10.211.55.123:8285"
	tapIP      = "10.244.2.2"
)

type ifreqFlags struct {
	IfrnName  [ifnameSize]byte
	IfruFlags uint16
}

func ioctl(fd int, request, argp uintptr) error {
	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), request, argp)
	if errno != 0 {
		fmt.Errorf("ioctl failed with '%s'\n", errno)
		return fmt.Errorf("ioctl failed with '%s'", errno)
	}
	return nil
}

func fromZeroTerm(s []byte) string {
	return string(bytes.TrimRight(s, "\000"))
}

func OpenTap(name string) (*os.File, string, error) {
	tap, err := os.OpenFile(tapDevice, os.O_RDWR|syscall.O_NONBLOCK, 0)
	if err != nil {
		fmt.Printf("OpenTap Failed! err:%v", err.Error())
		return nil, "", err
	}
	var ifr ifreqFlags
	copy(ifr.IfrnName[:len(ifr.IfrnName)-1], []byte(name+"\000"))
	ifr.IfruFlags = syscall.IFF_TAP | syscall.IFF_NO_PI

	err = ioctl(int(tap.Fd()), syscall.TUNSETIFF, uintptr(unsafe.Pointer(&ifr)))
	if err != nil {
		fmt.Printf("OpenTap Failed! err:%v\n", err.Error())
		return nil, "", err
	}

	ifName := fromZeroTerm(ifr.IfrnName[:ifnameSize])
	return tap, ifName, nil
}

func main() {
	fmt.Printf("======>Now----Client----Tap---VPN---UDP<======\n")
	tapFile, err := createTap()
	if err != nil {
		fmt.Printf("ICMP Listen Packet Failed! err:%v\n", err.Error())
		return
	}
	defer tapFile.Close()

	udpConn, err := createUDP()
	if err != nil {
		fmt.Printf("UDP conn Failed! err:%v\n", err.Error())
		return
	}
	defer udpConn.Close()

	go tapToUDP(udpConn, tapFile)

	go udpToTap(udpConn, tapFile)

	time.Sleep(time.Hour)
}

func createUDP() (*net.UDPConn, error) {
	localAddr, err := net.ResolveUDPAddr("udp", localAddr)
	if err != nil {
		log.Fatalln("failed to get udp socket:", err)
		return nil, err
	}
	conn, err := net.ListenUDP("udp", localAddr)
	if err != nil {
		log.Fatalln("failed to listen on udp socket:", err)
		return nil, err
	}

	return conn, nil

}

func tapToUDP(udpConn *net.UDPConn, tapFile *os.File) {
	packet := make([]byte, 1024*64)
	size := 0
	var err error
	for {
		if size, err = tapFile.Read(packet); err != nil {
			return
		}

		te := MACType(packet[:size])
		printMACHeader(packet[:size])

		if strings.EqualFold(fmt.Sprintf("%x", te), "0800") {
			b := packet[14:size]
			printIPv4Header(b)
			if b[9] == 1 {
				icmpPacket := b[20:]
				printICMPHeader(icmpPacket)
			}

			if b[9] == 6 {
				tcpPacket := b[20:]
				printTCPHeader(tcpPacket)
			}

			if b[9] == 17 {
				udpPacket := b[20:]
				printUDPHeader(udpPacket)
			}
		}

		if strings.EqualFold(fmt.Sprintf("%x", te), "0806") {
			b := packet[14:size]
			printARPHeader(b)
		}

		rAddr, err := net.ResolveUDPAddr("udp", remoteAddr)
		if err != nil {
			log.Fatalln("failed to get udp socket:", err)
			return
		}
		if size, err = udpConn.WriteTo(packet[:size], rAddr); err != nil {
			fmt.Println(err.Error())
			return
		}
		fmt.Printf("tapToUDP--->Write Msg To UDP Conn OK! size:%d\n", size)
	}
}

func udpToTap(udpConn *net.UDPConn, tapFile *os.File) {
	var packet = make([]byte, 1024*64)
	var size int
	var err error
	var addr net.Addr

	for {
		if size, addr, err = udpConn.ReadFrom(packet); err != nil {
			continue
		}

		size, err = tapFile.Write(packet[:size])
		if err != nil {
			continue
		}
		fmt.Printf("udpToTap--->Write Msg To /dev/net/tun OK! size:%d\tsrcIP:%v\n", size, addr)
	}
}

func createTap() (*os.File, error) {
	err := addTap()
	if err != nil {
		return nil, err
	}

	err = configTap()
	if err != nil {
		return nil, err
	}

	tapFile, _, err := OpenTap(tapName)
	if err != nil {
		return nil, err
	}

	return tapFile, nil
}

func addTap() error {
	la := netlink.LinkAttrs{
		Name:  tapName,
		Index: 8,
		MTU:   1500,
	}
	tap := netlink.Tuntap{
		LinkAttrs: la,
		Mode:      netlink.TUNTAP_MODE_TAP,
	}

	l, err := netlink.LinkByName(tapName)
	if err == nil {
		netlink.LinkSetDown(l)

		netlink.LinkDel(l)
	}

	err = netlink.LinkAdd(&tap)
	if err != nil {
		return err
	}
	return nil
}

func configTap() error {

	l, err := netlink.LinkByName(tapName)
	if err != nil {
		return err
	}

	ip, err := netlink.ParseIPNet(fmt.Sprintf("%s/%d", tapIP, 24))
	if err != nil {
		return err
	}

	addr := &netlink.Addr{IPNet: ip, Label: ""}
	if err = netlink.AddrAdd(l, addr); err != nil {
		return err
	}

	err = netlink.LinkSetUp(l)
	if err != nil {
		return err
	}

	return nil
}

在这里插入图片描述

在这里插入图片描述

具体下一小节在介绍。

目前,先把/dev/net/tun文件的并发性介绍完。

4.3.2.2、etherheader.go文件
package main

import (
	"fmt"
	"net"
)

func printMACHeader(packet []byte) {
	fmt.Println()
	fmt.Println()
	fmt.Println()
	fmt.Println("----->MAC Header<----")
	printSrcMACEther(packet)
	printDstMACEther(packet)
	printTypeEther(packet)
	fmt.Println("----->MAC Header<--end--")
}

func printDstMACEther(packet []byte) {
	fmt.Printf("Ether Header--->DstMac:%x:%x:%x:%x:%x:%x\n", packet[0], packet[1], packet[2], packet[3], packet[4], packet[5])
}

func printSrcMACEther(packet []byte) {
	fmt.Printf("Ether Header--->SrcMac:%x:%x:%x:%x:%x:%x\n", packet[6], packet[7], packet[8], packet[9], packet[10], packet[11])
}
func printTypeEther(packet []byte) {
	fmt.Printf("Ether Header--->Type:%04x\n", uint16(packet[12])<<8|uint16(packet[13]))
}

func MACDestination(macFrame []byte) net.HardwareAddr {
	return net.HardwareAddr(macFrame[:6])
}

func MACSource(macFrame []byte) net.HardwareAddr {
	return net.HardwareAddr(macFrame[6:12])
}

func MACType(macFrame []byte) []byte {
	return macFrame[12:14]
}

func MACTypeARP(macFrame []byte) []byte {
	return macFrame[2:4]
}

4.3.2.3、arpheader.go文件
package main

import (
	"fmt"
	"net"
)

func printARPHeader(packet []byte) {
	fmt.Println()
	fmt.Println()
	fmt.Println()
	fmt.Println("----->ARP Header<----")
	printARPTYPE(packet)
	printARPProTYPE(packet)
	printARPHardwareLen(packet)
	printARPPLen(packet)
	printARPOp(packet)
	printARPSrcHardwareMAC(packet)
	printARPSrcIP(packet)
	printARPDstHardwareMAC(packet)
	printARPDstIP(packet)
	fmt.Println("----->ARP Header<---END---")

}
func printARPTYPE(packet []byte) {
	fmt.Printf("ARP Header--->Type:%d\n", uint16(packet[0])<<8|uint16(packet[1]))
}

func printARPProTYPE(packet []byte) {
	fmt.Printf("ARP Header--->ProtocolType:%04x\n", uint16(packet[2])<<8|uint16(packet[3]))
}

func printARPHardwareLen(packet []byte) {
	fmt.Printf("ARP Header--->HardwareLen:%d\n", packet[4])
}

func printARPPLen(packet []byte) {
	fmt.Printf("ARP Header--->ProtocolLen:%d\n", packet[5])
}

func printARPOp(packet []byte) {
	fmt.Printf("ARP Header--->op:%d\n", uint16(packet[6])<<8|uint16(packet[7]))
}

func printARPSrcHardwareMAC(packet []byte) {
	fmt.Printf("ARP Header--->SrcHardwarMAC:%x:%x:%x:%x:%x:%x\n", packet[8], packet[9], packet[10], packet[11], packet[12], packet[13])
}

func printARPSrcIP(packet []byte) {
	fmt.Printf("ARP Header--->SrcIP:%d.%d.%d.%d\n", packet[14], packet[15], packet[16], packet[17])
}

func printARPDstHardwareMAC(packet []byte) {
	fmt.Printf("ARP Header--->DstHardwareMAC:%x:%x:%x:%x:%x:%x\n", packet[18], packet[19], packet[20], packet[21], packet[22], packet[23])
}

func printARPDstIP(packet []byte) {
	fmt.Printf("ARP Header--->DstIP:%d.%d.%d.%d\n", packet[24], packet[25], packet[26], packet[27])
}

func GetIPv4SrcARP(packet []byte) net.IP {
	return net.IPv4(packet[14], packet[15], packet[16], packet[17])
}

func GetIPv4DstARP(packet []byte) net.IP {
	return net.IPv4(packet[24], packet[25], packet[26], packet[27])
}

4.3.2.4、icmpheader.go文件
package main

import "fmt"

func printICMPHeader(packet []byte) {
	fmt.Println()
	fmt.Println()
	fmt.Println()
	fmt.Println("----->ICMP Header<----")
	printICMPType(packet)
	printICMPCode(packet)
	printICMPCheckSum(packet)
	printICMPIdentification(packet)
	printICMPSeqNum(packet)
	printICMPTimestamp(packet)
	printICMPData(packet)
	fmt.Println("----->ICMP Header<----End----")
}

func printICMPType(packet []byte) {
	fmt.Printf("ICMP Header--->Type:%d\n", packet[0])
}

func printICMPCode(packet []byte) {
	fmt.Printf("ICMP Header--->Code:%d\n", packet[1])
}

func printICMPCheckSum(packet []byte) {
	fmt.Printf("ICMP Header--->Checksum:%04x\n", uint16(packet[2])<<8|uint16(packet[3]))
}

func printICMPIdentification(packet []byte) {
	fmt.Printf("TICMP Header--->Identification(process):%d\n", uint16(packet[4])<<8|uint16(packet[5]))
}

func printICMPSeqNum(packet []byte) {
	fmt.Printf("ICMP Header--->SeqNum:%d\n", uint16(packet[6])<<8|uint16(packet[7]))
}

func printICMPTimestamp(packet []byte) {
	fmt.Printf("ICMP Header --->timestamp:%02x %02x %02x %02x %02x %02x %02x %02x\n", packet[8], packet[9], packet[10], packet[11], packet[12], packet[13], packet[14], packet[15])
}

func printICMPData(packet []byte) {
	fmt.Printf("ICMP Header--->data:%v\n", string(packet[20:]))
}


4.3.2.5、tcpheader.go文件
package main

import (
	"fmt"
)

func printTCPHeader(packet []byte) {
	fmt.Println()
	fmt.Println()
	fmt.Println()
	fmt.Printf("----->TCP Header<----len:%d\n", len(packet[:]))
	printTCPsrcPort(packet)
	printTCPdstPort(packet)
	printTCPSequenceNumber(packet)
	printTCPACKNumber(packet)
	printTCPHeaderLen(packet)
	printTCPFlagCWR(packet)
	printTCPFlagECE(packet)
	printTCPFlagUrgent(packet)
	printTCPFlagACK(packet)
	printTCPFlagPSH(packet)
	printTCPFlagRST(packet)
	printTCPFlagSYN(packet)
	printTCPFlagFIN(packet)
	printTCPWindowSize(packet)
	printTCPCheckSum(packet)
	printTCPUrgentPointer(packet)
	printTCPData(packet)
	fmt.Println("----->TCP Header<---END---")
}

func printTCPsrcPort(packet []byte) {
	fmt.Printf("TCP Header--->SrcPort:%d\n", int64(uint16(packet[0])<<8|uint16(packet[1])))
}

func printTCPdstPort(packet []byte) {
	fmt.Printf("TCP Header--->DstPort:%d\n", int64(uint16(packet[2])<<8|uint16(packet[3])))
}

func printTCPSequenceNumber(packet []byte) {
	fmt.Printf("TCP Header--->SequenceNumber:%d\n", uint32(packet[4])<<24|uint32(packet[5])<<16|uint32(packet[6])<<8|uint32(packet[7]))
}

func printTCPACKNumber(packet []byte) {
	fmt.Printf("TCP Header--->ACKNum:%d\n", uint32(packet[8])<<24|uint32(packet[9])<<16|uint32(packet[10])<<8|uint32(packet[11]))
}

func printTCPHeaderLen(packet []byte) {
	fmt.Printf("TCP Header--->HeaderLen:%d\n", packet[12]>>4*4)
}

func printTCPFlagCWR(packet []byte) {
	fmt.Printf("TCP Header--->FlagCWR:%d\n", packet[13]&0x80>>7)
}
func printTCPFlagECE(packet []byte) {
	fmt.Printf("TCP Header--->FlagEcho:%d\n", packet[13]&0x40>>6)
}

func printTCPFlagUrgent(packet []byte) {
	fmt.Printf("TCP Header--->FlagUrgent:%d\n", packet[13]&0x20>>5)
}

func printTCPFlagACK(packet []byte) {
	fmt.Printf("TCP Header--->FlagACK:%d\n", packet[13]>>4&0b0001)
	fmt.Printf("TCP Header--->FlagACK:%d\n", packet[13]>>4&0b1)
	//fmt.Printf("TCP Header--->FlagACK:%d\tpacket:%v\n", packet[13]>>4, packet[13])
	fmt.Printf("TCP Header--->FlagACK:%d\n", packet[13]&0x10>>4)
}

func printTCPFlagPSH(packet []byte) {
	fmt.Printf("TCP Header--->FlagPSH:%d\n", packet[13]&0x08>>3)
}

func printTCPFlagRST(packet []byte) {
	fmt.Printf("TCP Header--->FlagRST:%d\n", packet[13]&0x04>>2)
}

func printTCPFlagSYN(packet []byte) {
	fmt.Printf("TCP Header--->FlagSYN:%d\n", packet[13]&0x02>>1)
}

func printTCPFlagFIN(packet []byte) {
	fmt.Printf("TCP Header--->FlagFIN:%d\n", packet[13]&0x01)
}

func printTCPWindowSize(packet []byte) {
	fmt.Printf("TCP Header--->WindowSize:%d\n", uint16(packet[14])<<8|uint16(packet[15]))
}

func printTCPCheckSum(packet []byte) {
	fmt.Printf("TCP Header--->Checksum:%04x\n", uint16(packet[16])<<8|uint16(packet[17]))
}

func printTCPUrgentPointer(packet []byte) {
	fmt.Printf("TCP Header--->UrgentPointer:%d\n", uint16(packet[18])<<8|uint16(packet[19]))
}

func printTCPData(packet []byte) {
	headerLen := packet[12] >> 4 * 4
	p := packet[headerLen:]
	dataLen := len(p)
	if dataLen > 1 {
		fmt.Printf("TCP Header--->Data--->:%v\theaderLen:%v\tdataLen:%d\n", string(p[:dataLen-1]), headerLen, dataLen)
	}
}

4.3.2.6、udpheader.go文件
package main

import "fmt"

func printUDPHeader(packet []byte) {
	fmt.Println()
	fmt.Println()
	fmt.Println()
	fmt.Println("----->UDP Header<----")
	printUDPSrcPort(packet)
	printUDPDstPort(packet)
	printUDPAllLen(packet)
	printUDPChecksum(packet)
	printUDPData(packet)
	fmt.Println("----->UDP Header<---End--")
}

func printUDPSrcPort(packet []byte) {
	fmt.Printf("UDP Header--->SrcPort:%d\n", uint16(packet[0])<<8|uint16(packet[1]))
}

func printUDPDstPort(packet []byte) {
	fmt.Printf("UDP Header--->DstPort:%d\n", uint16(packet[2])<<8|uint16(packet[3]))
}

func printUDPAllLen(packet []byte) {
	fmt.Printf("UDP Header--->AllLen:%d\n", uint16(packet[4])<<8|uint16(packet[5]))
}

func printUDPChecksum(packet []byte) {
	fmt.Printf("UDP Header--->Checksum:%d\n", uint16(packet[6])<<8|uint16(packet[7]))
}

func printUDPData(packet []byte) {
	fmt.Printf("UDP Header--->data:%v\n", string(packet[8:]))
}

4.3.2.7、ipv4header.go文件
package main

import (
	"fmt"
	"net"
)

//  打印ip报文头的详情
func printIPv4Header(packet []byte) {
	fmt.Println()
	fmt.Println()
	fmt.Println()
	fmt.Println("----->IP Header<----")
	printVersionIPv4(packet)
	printHeaderLenIPv4(packet)
	printServiceTypeIPv4(packet)
	printAllLenIPv4(packet)
	printIdentificationIPv4(packet)
	printFlagsIPv4(packet)
	printFragmentOffsetIPv4(packet)
	printTTLIPv4(packet)
	printProtocolIPv4(packet)
	printChecksumIPv4(packet)
	printSrcIPv4(packet)
	printDstIPv4(packet)
	fmt.Println("----->IP Header<---End---")
}

func printVersionIPv4(packet []byte) {
	header := packet[0]
	fmt.Printf("IPv4 Header--->Version:%d\n", header>>4)
}

func printHeaderLenIPv4(packet []byte) {
	header := packet[0]
	fmt.Printf("IPv4 Header--->HeaderLen:%d byte\n", header&0x0f*4)
}

func printServiceTypeIPv4(packet []byte) {
	st := packet[1]
	fmt.Printf("IPv4 Header--->ServiceType:%v\n", st)
}

func printAllLenIPv4(packet []byte) {
	fmt.Printf("IPv4 Header--->AllLen:%d\n", uint16(packet[2])<<8|uint16(packet[3]))
}

func printIdentificationIPv4(packet []byte) {
	id := uint16(packet[4])<<8 | uint16(packet[5])
	fmt.Printf("IPv4 Header--->Identification:%x\t%d\n", id, id)
}

func printFlagsIPv4(packet []byte) {
	// 向右移动5位,相当于去掉后5位。数值降低
	fmt.Printf("IPv4 Header--->Flags:%03b\n", packet[6]>>5)
}
func printFragmentOffsetIPv4(packet []byte) {
	// 向左移动3位,相当于将前3位去掉。数值可能增加
	fmt.Printf("IPv4 Header--->FragmentOffset:%013b\n", uint16(packet[6])<<3|uint16(packet[7]))
}

func printTTLIPv4(packet []byte) {
	fmt.Printf("IPv4 Header--->TTL:%d\n", packet[8])
}

func printProtocolIPv4(packet []byte) {
	fmt.Printf("IPv4 Header--->ProtocolType:%d\n", packet[9])
}

func printChecksumIPv4(packet []byte) {
	fmt.Printf("IPv4 Header--->Checksum:%d\n", uint16(packet[10])<<8|uint16(packet[11]))
}

func printSrcIPv4(packet []byte) net.IP {
	fmt.Printf("IPv4 Header--->SrcIP:%d.%d.%d.%d\n", packet[12], packet[13], packet[14], packet[15])
	return net.IPv4(packet[12], packet[13], packet[14], packet[15])
}

func printDstIPv4(packet []byte) net.IP {
	fmt.Printf("IPv4 Header--->DstIP:%d.%d.%d.%d\n", packet[16], packet[17], packet[18], packet[19])
	return net.IPv4(packet[16], packet[17], packet[18], packet[19])
}

4.3.3、服务端代码

4.3.3.1、server.go代码
package main

import (
	"bytes"
	"fmt"
	"github.com/vishvananda/netlink"
	"log"
	"net"
	"os"
	"syscall"
	"time"
	"unsafe"
)

const (
	tapName    = "tap99"
	tapDevice  = "/dev/net/tun"
	ifnameSize = 16
	localAddr  = "10.211.55.123:8285"
	remoteAddr = "10.211.55.122:8285"
	tapIP      = "10.244.3.3"
)

type ifreqFlags struct {
	IfrnName  [ifnameSize]byte
	IfruFlags uint16
}

func ioctl(fd int, request, argp uintptr) error {
	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), request, argp)
	if errno != 0 {
		fmt.Errorf("ioctl failed with '%s'\n", errno)
		return fmt.Errorf("ioctl failed with '%s'", errno)
	}
	return nil
}

func fromZeroTerm(s []byte) string {
	return string(bytes.TrimRight(s, "\000"))
}

func OpenTap(name string) (*os.File, string, error) {
	tap, err := os.OpenFile(tapDevice, os.O_RDWR|syscall.O_NONBLOCK, 0)
	if err != nil {
		fmt.Printf("OpenTap Failed! err:%v", err.Error())
		return nil, "", err
	}
	var ifr ifreqFlags
	copy(ifr.IfrnName[:len(ifr.IfrnName)-1], []byte(name+"\000"))
	ifr.IfruFlags = syscall.IFF_TAP | syscall.IFF_NO_PI

	err = ioctl(int(tap.Fd()), syscall.TUNSETIFF, uintptr(unsafe.Pointer(&ifr)))
	if err != nil {
		fmt.Printf("OpenTap Failed! err:%v\n", err.Error())
		return nil, "", err
	}

	ifName := fromZeroTerm(ifr.IfrnName[:ifnameSize])
	return tap, ifName, nil
}

func main() {
	fmt.Printf("======>Now----Server----Tap---VPN---UDP<======\n")
	tapFile, err := createTap()
	if err != nil {
		fmt.Printf("ICMP Listen Packet Failed! err:%v\n", err.Error())
		return
	}
	defer tapFile.Close()

	udpConn, err := createUDP()
	if err != nil {
		fmt.Printf("UDP conn Failed! err:%v\n", err.Error())
		return
	}
	defer udpConn.Close()

	go tapToUDP(udpConn, tapFile)

	go udpToTap(udpConn, tapFile)

	time.Sleep(time.Hour)
}

func createUDP() (*net.UDPConn, error) {
	localAddr, err := net.ResolveUDPAddr("udp", localAddr)
	if err != nil {
		log.Fatalln("failed to get udp socket:", err)
		return nil, err
	}
	conn, err := net.ListenUDP("udp", localAddr)
	if err != nil {
		log.Fatalln("failed to listen on udp socket:", err)
		return nil, err
	}

	return conn, nil

}

func tapToUDP(udpConn *net.UDPConn, tapFile *os.File) {
	packet := make([]byte, 1024*64)
	size := 0
	var err error
	for {
		if size, err = tapFile.Read(packet); err != nil {
			return
		}

		srcMAC := MACSource(packet[:size])
		dstMAC := MACDestination(packet[:size])
		fmt.Printf("Server==>TapToUDP----srcMAC:%v\tdstMAC:%v\n", srcMAC.String(), dstMAC.String())

		rAddr, err := net.ResolveUDPAddr("udp", remoteAddr)
		if err != nil {
			log.Fatalln("failed to get udp socket:", err)
			return
		}
		if size, err = udpConn.WriteTo(packet[:size], rAddr); err != nil {
			fmt.Println(err.Error())
			return
		}
		fmt.Printf("tapToUDP--->Write Msg To UDP Conn OK! size:%d\n", size)
	}
}

func udpToTap(udpConn *net.UDPConn, tapFile *os.File) {
	var packet = make([]byte, 1024*64)
	var size int
	var err error
	var addr net.Addr

	for {
		if size, addr, err = udpConn.ReadFrom(packet); err != nil {
			continue
		}
		srcMAC := MACSource(packet[:size])
		dstMAC := MACDestination(packet[:size])
		fmt.Printf("Server==>UDPToTap----srcMAC:%v\tdstMAC:%v\n", srcMAC.String(), dstMAC.String())
		size, err = tapFile.Write(packet[:size])
		if err != nil {
			continue
		}
		fmt.Printf("udpToTap--->Write Msg To /dev/net/tun OK! size:%d\tsrcIP:%v\n", size, addr)
	}
}

func createTap() (*os.File, error) {
	err := addTap()
	if err != nil {
		return nil, err
	}

	err = configTap()
	if err != nil {
		return nil, err
	}

	tapFile, _, err := OpenTap(tapName)
	if err != nil {
		return nil, err
	}

	return tapFile, nil
}

func addTap() error {
	la := netlink.LinkAttrs{
		Name:  tapName,
		Index: 8,
		MTU:   1500,
	}
	tap := netlink.Tuntap{
		LinkAttrs: la,
		Mode:      netlink.TUNTAP_MODE_TAP,
	}

	l, err := netlink.LinkByName(tapName)
	if err == nil {
		netlink.LinkSetDown(l)

		netlink.LinkDel(l)
	}

	err = netlink.LinkAdd(&tap)
	if err != nil {
		return err
	}
	return nil
}

func configTap() error {

	l, err := netlink.LinkByName(tapName)
	if err != nil {
		return err
	}

	ip, err := netlink.ParseIPNet(fmt.Sprintf("%s/%d", tapIP, 24))
	if err != nil {
		return err
	}

	addr := &netlink.Addr{IPNet: ip, Label: ""}
	if err = netlink.AddrAdd(l, addr); err != nil {
		return err
	}

	err = netlink.LinkSetUp(l)
	if err != nil {
		return err
	}

	return nil
}

func MACDestination(macFrame []byte) net.HardwareAddr {
	return net.HardwareAddr(macFrame[:6])
}

func MACSource(macFrame []byte) net.HardwareAddr {
	return net.HardwareAddr(macFrame[6:12])
}

4.3.4、go.mod参考

module tun-test/icmp/v2

go 1.17

require github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5

require (
	github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
	golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
)

记得修改module名称,修改自己实际的。

同样,客户端,服务器端代码同样修改一下即可。

4.3.5、本地编译,上传到10.211.55.122以及10.211.55.123节点上去

Makefile

build:
	CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o tap-client ./client/*.go
	CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o tap-server ./server/main.go

scp:
	scp tap-client root@10.211.55.122:/root
	scp tap-server root@10.211.55.122:/root

all:
	make build && make scp

在本地执行

make all

在这里插入图片描述

4.4、测试用例2:使用tun设备实现ICMP协议

4.4.1、main.go文件内容,如下

package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"github.com/vishvananda/netlink"
	"golang.org/x/net/icmp"
	"golang.org/x/net/ipv4"
	"net"
	"os"
	"syscall"
	"time"
	"unsafe"
)

const (
	tunName    = "tun19"
	tunDevice  = "/dev/net/tun"
	ifnameSize = 16
	eth0       = "10.211.55.122"
	tunIP      = "10.244.2.9"
)

type ifreqFlags struct {
	IfrnName  [ifnameSize]byte
	IfruFlags uint16
}

func ioctl(fd int, request, argp uintptr) error {
	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), request, argp)
	if errno != 0 {
		fmt.Errorf("ioctl failed with '%s'\n", errno)
		return fmt.Errorf("ioctl failed with '%s'", errno)
	}
	return nil
}

func fromZeroTerm(s []byte) string {
	return string(bytes.TrimRight(s, "\000"))
}

func OpenTun(name string) (*os.File, string, error) {
	tun, err := os.OpenFile(tunDevice, os.O_RDWR|syscall.O_NONBLOCK, 0)
	if err != nil {
		fmt.Printf("OpenTun Failed! err:%v", err.Error())
		return nil, "", err
	}
	var ifr ifreqFlags
	copy(ifr.IfrnName[:len(ifr.IfrnName)-1], []byte(name+"\000"))
	ifr.IfruFlags = syscall.IFF_TUN | syscall.IFF_NO_PI

	err = ioctl(int(tun.Fd()), syscall.TUNSETIFF, uintptr(unsafe.Pointer(&ifr)))
	if err != nil {
		fmt.Printf("OpenTun Failed! err:%v\n", err.Error())
		return nil, "", err
	}

	ifName := fromZeroTerm(ifr.IfrnName[:ifnameSize])
	return tun, ifName, nil
}

func checkSum(data []byte) uint16 {
	var (
		sum    uint32
		length int = len(data)
		index  int
	)
	for length > 1 {
		sum += uint32(data[index])<<8 + uint32(data[index+1])
		index += 2
		length -= 2
	}
	if length > 0 {
		sum += uint32(data[index])
	}
	sum += sum >> 16

	return uint16(^sum)
}

func main() {
	fmt.Printf("======>Now----Test----Tun<======\n")
	tunFile, err := createTun()
	if err != nil {
		fmt.Printf("ICMP Listen Packet Failed! err:%v\n", err.Error())
		return
	}
	defer tunFile.Close()

	icmpConn, _ := icmp.ListenPacket("ip4:icmp", eth0)

	defer icmpConn.Close()

	go tunToIcmp(icmpConn, tunFile)

	go icmpToTun(icmpConn, tunFile)

	time.Sleep(time.Hour)
}

func tunToIcmp(icmpconn *icmp.PacketConn, tunFile *os.File) {
	var srcIP string
	packet := make([]byte, 1024*64)
	size := 0
	var err error
	for {
		if size, err = tunFile.Read(packet); err != nil {
			return
		}
		fmt.Printf("Msg Length: %d\n", binary.BigEndian.Uint16(packet[2:4]))
		fmt.Printf("Msg Protocol: %d (1=ICMP, 6=TCP, 17=UDP)\tsize:%d\n", packet[9], size)

		b := packet[:size]
		srcIP = GetSrcIP(b)
		dstIP := GetDstIP(b)
		fmt.Printf("Msg srcIP: %s\tdstIP:%v\n", srcIP, dstIP)

		var raddr = net.IPAddr{IP: net.ParseIP(dstIP)}

		b = b[20:size]

		if size, err = icmpconn.WriteTo(b, &raddr); err != nil {
			fmt.Println(err.Error())
			return
		}
		fmt.Printf("Write Msg To Icmp Conn OK! size:%d\n", size)
	}
}

func icmpToTun(icmpconn *icmp.PacketConn, tunFile *os.File) {
	var sb = make([]byte, 1024*64)
	var addr net.Addr
	var size int
	var err error

	for {
		if size, addr, err = icmpconn.ReadFrom(sb); err != nil {
			continue
		}

		ipHeader := createIPv4Header(net.ParseIP(addr.String()), net.ParseIP(tunIP), os.Getpid())
		iphb, err := ipHeader.Marshal()
		if err != nil {
			continue
		}
		fmt.Printf("Reply MSG Length: %d\n", binary.BigEndian.Uint16(iphb[2:4]))
		fmt.Printf("Reply MSG Protocol: %d (1=ICMP, 6=TCP, 17=UDP)\n", iphb[9])
		dstIP := GetDstIP(iphb)
		fmt.Printf("Reply src IP: %s\tdstIP:%v\n", addr, dstIP)

		var rep = make([]byte, 84)
		rep = append(iphb, sb[:size]...)

		size, err = tunFile.Write(rep)
		if err != nil {
			continue
		}
		fmt.Printf("Write Msg To /dev/net/tun OK! size:%d\ttime:%v\n", size, time.Now())
	}
}

func createIPv4Header(src, dst net.IP, id int) *ipv4.Header {

	iph := &ipv4.Header{
		Version:  ipv4.Version,
		Len:      ipv4.HeaderLen,
		TOS:      0x00,
		TotalLen: ipv4.HeaderLen + 64,
		ID:       id,
		Flags:    ipv4.DontFragment,
		FragOff:  0,
		TTL:      64,
		Protocol: 1,
		Checksum: 0,
		Src:      src,
		Dst:      dst,
	}

	h, _ := iph.Marshal()

	iph.Checksum = int(checkSum(h))

	return iph
}

func IsIPv4(packet []byte) bool {
	flag := packet[0] >> 4
	return flag == 4
}

func GetIPv4Src(packet []byte) net.IP {
	return net.IPv4(packet[12], packet[13], packet[14], packet[15])
}

func GetIPv4Dst(packet []byte) net.IP {
	return net.IPv4(packet[16], packet[17], packet[18], packet[19])
}

func GetSrcIP(packet []byte) string {
	key := ""
	if IsIPv4(packet) && len(packet) >= 20 {
		key = GetIPv4Src(packet).To4().String()
	}

	return key
}

func GetDstIP(packet []byte) string {
	key := ""
	if IsIPv4(packet) && len(packet) >= 20 {
		key = GetIPv4Dst(packet).To4().String()
	}

	return key
}

func createTun() (*os.File, error) {
	err := addTun()
	if err != nil {
		return nil, err
	}

	err = configTun()
	if err != nil {
		return nil, err
	}

	tunFile, _, err := OpenTun(tunName)
	if err != nil {
		return nil, err
	}

	return tunFile, nil
}

func addTun() error {
	la := netlink.LinkAttrs{
		Name:  tunName,
		Index: 8,
		MTU:   1500,
	}
	tun := netlink.Tuntap{
		LinkAttrs: la,
		Mode:      netlink.TUNTAP_MODE_TUN,
	}

	l, err := netlink.LinkByName(tunName)
	if err == nil {
		netlink.LinkSetDown(l)

		netlink.LinkDel(l)
	}

	err = netlink.LinkAdd(&tun)
	if err != nil {
		return err
	}
	return nil
}

func configTun() error {

	l, err := netlink.LinkByName(tunName)
	if err != nil {
		return err
	}

	ip, err := netlink.ParseIPNet(fmt.Sprintf("%s/%d", tunIP, 24))
	if err != nil {
		return err
	}

	addr := &netlink.Addr{IPNet: ip, Label: ""}
	if err = netlink.AddrAdd(l, addr); err != nil {
		return err
	}

	err = netlink.LinkSetUp(l)
	if err != nil {
		return err
	}

	return nil
}

4.4.2、编译、上传到10.211.55.122上

Makefile

build:
	CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o tun-driver main.go

scp:
	scp tun-driver root@10.211.55.122:/root

all:
	make build && make scp

本地执行

make all

即可

4.5、注意事项

修改代码时,要注意下

  • 第一个测试用例中,创建是tap类型的虚拟网络设备tap99
  • 第二个测试用例中,创建的tun类型的虚拟网络设备tun19

我们测试的关键点是,不同的虚拟网络设备绑定在同一个文件描述符/dev/net/tun上
只要这两个设备能够同时读写操作,即可。

实话实话,主要是不想再修改第二个测试用例的代码,将其改成tap类型的。
在tun、tap上已经用了很多时间了,太累了。

4.6、启动服务

4.6.1、登录到10.211.55.123节点上

4.6.1.1、启动第一个测试用例的服务器端
./tap-server

在这里插入图片描述

4.6.1.2、设置路由表,针对的是数据包去往10.211.55.122
route -n

route add -net 10.244.2.0/24 dev tap99

route -n

在这里插入图片描述

4.6.2、登录到10.211.55.122节点上

4.6.2.1、启动tap-client服务
4.6.2.1.1、启动tap-client服务
 ./tap-client 

在这里插入图片描述

4.6.2.1.2、设置通往10.211.55.123节点的路由
route -n

route add -net 10.244.3.0/24 dev tap99

route -n

在这里插入图片描述

4.6.2.2、启动第2个测试用例
./tun-driver 

在这里插入图片描述

设置路由反向检测属性

sysctl net.ipv4.conf.all.rp_filter

sysctl -w net.ipv4.conf.all.rp_filter=2

sysctl net.ipv4.conf.all.rp_filter

在这里插入图片描述

4.7、通过ping命令来测试/dev/net/tun是否支持并发读写

登录到
10.211.55.122 节点上去

4.7.1、在10.211.55.122 节点上,发起ping命令去ping10.211.55.123节点上的tap99设备

ping 10.244.3.3 -I tap99

在这里插入图片描述

抓包命令如下:

tcpdump -nn -i tap99

在这里插入图片描述

4.7.2、在10.211.55.122 节点上,发起ping命令去ping10.211.55.123节点上的对外物理网卡eth0

ping 10.211.55.123 -I tun19

在这里插入图片描述

抓包命令如下:

tcpdump -nn -i tun19

在这里插入图片描述

5、总结

在10.211.55.122节点上,
通过ping命令同时使用tun19虚拟设备以及tap99虚拟设备发起ping请求

整个请求中,数据包有去有回,无丢包现象,

而tap19,tun99同时绑定了/dev/net/tun文件描述符,

说明/dev/net/tun文件描述符支持并发读写操作。

这样的话,本次测试的目的之一就到达了。


<<零入门kubernetes网络实战>>技术专栏之文章目录


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码二哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值