零入门kubernetes网络实战-23->基于tun虚拟网络设备来构建点对点的VPN案例(golang版本、helloworld级别)

本文主要是介绍零入门kubernetes网络实战-23->基于tun虚拟网络设备来构建点对点的VPN案例(golang版本、helloworld级别),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《零入门kubernetes网络实战》视频专栏地址
https://www.ixigua.com/7193641905282875942

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


本篇文章主要是练习:

  • 通过tun设备来实现跨主机通信
  • 测试在宿主机-1上使用curl命令可以访问宿主机-2上的web服务;
    • 请求有去,有回
  • 测试在宿主机-1上使用ping命令 可以ping通 宿主机-2上的tun类型的虚拟网卡设备,
    • 如ping通flannel99
    • 请求有去,有回

本篇文章测试时提供的代码,属于helloworld级别的,基本属于最精简的。

再下一篇文章里,为大家介绍一个开源社区提供的VPN,稍微多了点功能。

1、原理介绍

1.1、原理图说明

如下图所示:
在这里插入图片描述

该图只画出了请求路线,回复路线反之即可。

该图主要分为上下两个框:

  • 上面代表的是宿主机-1,对外的物理网卡是eth0,IP是10.211.55.122
  • 下面代表的是宿主机-2,对外的物理网卡是eth0,IP是10.211.55.123

在宿主机-1中,使用tun-vpn-client创建了tun设备,如flannel99,IP是10.244.2.2
在宿主机-2中,使用tun=vpn-server创建了tun设备,如flannel99,IP是10.244.3.3

1.2、在宿主机-1上使用curl访问宿主机-2上的web服务的数据包请求路线:

  • curl请求创建的TCP类型的数据包,经过本机的网络协议栈,路由表,防火墙规则后,
  • 进入到了网络虚拟设备flannel99中,flannel99自动给TCP数据包添加一层IP报文头,20个字节;
    • 源地址是10.244.2.2
    • 目的地址是10.244.3.3
    • 协议类型是TCP
  • 网络虚拟设备flannel99新创建的数据包,会自动的转发到/dev/net/tun文件描述符里
  • tun-vpn-client会一直在读取/dev/net/tun文件描述符里的数据包,将读取的数据包通过本地的UDP连接发送到对端的UDP连接里;本地的UDP连接是通过eth0网卡发送过去的。
  • UDP类型的数据包到达对端的eth0网卡后,会经过路由表,防火墙,网络协议栈会到达本机的UDP服务里;
  • tun-vpn-server会一直从本机的UDP服务链接里接收数据包
  • tun-vpn-server将接收到的UDP数据包,写入到/dev/net/tun文件描述符里,
  • 当数据包到达/dev/net/tun文件描述符后,会自动的转发到网络虚拟设备flannel99里
  • 网络虚拟设备flannel99会自动将数据包中的IP报文头删除掉,只保留curl创建的TCP数据包
  • 当TCP数据包从flannel99设备出来后会经过路由表,防火墙规则,网络协议栈,最终web服务接收到TCP数据包,并进行反馈。

1.3、tun-vpn-client与tun-vpn-server

1.3.1、名称说明?

不要被名字误解,无所谓哪个是客户端,哪个是服务器端

请求是哪个发起的,就是客户端;

被请求方就是服务器端。

都是相对的。

1.3.2、实现原理说明

原理是一样的。

因此,我们只介绍一下tun-vpn-client的原理即可。

2、tun-vpn-client原理说明

2.1、代码如下

package mainimport ("bytes""fmt""github.com/vishvananda/netlink""log""net""os""syscall""time""unsafe"
)const (tunName    = "flannel99"tunDevice  = "/dev/net/tun"ifnameSize = 16localAddr  = "10.211.55.122:8285"remoteAddr = "10.211.55.123:8285"tunIP      = "10.244.2.2"
)type ifreqFlags struct {IfrnName  [ifnameSize]byteIfruFlags 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 ifreqFlagscopy(ifr.IfrnName[:len(ifr.IfrnName)-1], []byte(name+"\000"))ifr.IfruFlags = syscall.IFF_TUN | syscall.IFF_NO_PIerr = 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 main() {fmt.Printf("======>Now----Client----Tun---VPN---UDP<======\n")tunFile, err := createTun()if err != nil {fmt.Printf("ICMP Listen Packet Failed! err:%v\n", err.Error())return}defer tunFile.Close()udpConn, err := createUDP()if err != nil {fmt.Printf("UDP conn Failed! err:%v\n", err.Error())return}defer udpConn.Close()go tunToUDP(udpConn, tunFile)go udpToTun(udpConn, tunFile)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 tunToUDP(udpConn *net.UDPConn, tunFile *os.File) {packet := make([]byte, 1024*64)size := 0var err errorfor {if size, err = tunFile.Read(packet); err != nil {return}b := packet[:size]srcIP := GetSrcIP(b)dstIP := GetDstIP(b)fmt.Printf("tunToUDP--->Msg Protocol type: %v(1=ICMP, 6=TCP, 17=UDP)\tsrcIP:%v--->dstIP:%v", packet[9], srcIP, dstIP)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("tunToUDP--->Write Msg To UDP Conn OK! size:%d\n", size)}
}func udpToTun(udpConn *net.UDPConn, tunFile *os.File) {var packet = make([]byte, 1024*64)var size intvar err errorvar addr net.Addrfor {if size, addr, err = udpConn.ReadFrom(packet); err != nil {continue}size, err = tunFile.Write(packet[:size])if err != nil {continue}fmt.Printf("udpToTun--->Write Msg To /dev/net/tun OK! size:%d\tsrcIP:%v\n", size, addr)}
}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
}func GetSrcIP(packet []byte) string {key := ""if IsIPv4(packet) && len(packet) >= 20 {key = GetIPv4Src(packet).To4().String()}return key
}func IsIPv4(packet []byte) bool {flag := packet[0] >> 4return flag == 4
}func GetDstIP(packet []byte) string {key := ""if IsIPv4(packet) && len(packet) >= 20 {key = GetIPv4Dst(packet).To4().String()}return key
}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])
}

2.2、主流程说明

在这里插入图片描述

2.3、tunToUDP函数原理说明

在这里插入图片描述

2.4、udpToTun函数原理说明

在这里插入图片描述

tun-vpn-server的原理跟tun-vpn-client一样,就不再介绍了。

2.5、本地编译,上传到测试服务器10.211.55.122

Makefile文件内容

build:CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o tun-vpn-client main.goscp:scp tun-vpn-client root@10.211.55.122:/rootall:make build && make scp

执行

make all

即可

3、tun-vpn-server原理说明

3.1、代码如下

package mainimport ("bytes""fmt""github.com/vishvananda/netlink""log""net""os""syscall""time""unsafe"
)const (tunName    = "flannel99"tunDevice  = "/dev/net/tun"ifnameSize = 16localAddr  = "10.211.55.123:8285"remoteAddr = "10.211.55.122:8285"tunIP      = "10.244.3.3"
)type ifreqFlags struct {IfrnName  [ifnameSize]byteIfruFlags 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 ifreqFlagscopy(ifr.IfrnName[:len(ifr.IfrnName)-1], []byte(name+"\000"))ifr.IfruFlags = syscall.IFF_TUN | syscall.IFF_NO_PIerr = 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 main() {fmt.Printf("======>Now----Server----Tun---VPN---UDP<======\n")tunFile, err := createTun()if err != nil {fmt.Printf("ICMP Listen Packet Failed! err:%v\n", err.Error())return}defer tunFile.Close()udpConn, err := createUDP()if err != nil {fmt.Printf("UDP conn Failed! err:%v\n", err.Error())return}defer udpConn.Close()go tunToUDP(udpConn, tunFile)go udpToTun(udpConn, tunFile)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 tunToUDP(udpConn *net.UDPConn, tunFile *os.File) {packet := make([]byte, 1024*64)size := 0var err errorfor {if size, err = tunFile.Read(packet); err != nil {return}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("tunToUDP--->Write Msg To UDP Conn OK! size:%d\n", size)}
}func udpToTun(udpConn *net.UDPConn, tunFile *os.File) {var packet = make([]byte, 1024*64)var size intvar err errorvar addr net.Addrfor {if size, addr, err = udpConn.ReadFrom(packet); err != nil {continue}size, err = tunFile.Write(packet[:size])if err != nil {continue}fmt.Printf("udpToTun--->Write Msg To /dev/net/tun OK! size:%d\tsrcIP:%v\n", size, addr)}
}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
}

3.2、本地编译,上传到测试服务器10.211.55.123上

Makefile内容

build:CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o tun-vpn-server main.goscp:scp tun-vpn-server root@10.211.55.123:/rootall:make build && make scp

执行

make all

即可

4、web服务说明

简单提供了一个web服务

4.1、代码如下

package mainimport ("encoding/json""fmt""net/http"
)type Stu struct {Age intMsg string
}func sayHello(w http.ResponseWriter, r *http.Request) {stu := Stu{Age: 12, Msg: "hello world\nTun VPN\n"}stuJson, e := json.Marshal(&stu)if e != nil {panic(e)}w.Write(stuJson)fmt.Printf("Reply MSG:%v\n", string(stuJson))
}func main() {http.HandleFunc("/", sayHello)fmt.Printf("App URL: http://10.244.3.3:9090\n")err := http.ListenAndServe("10.244.3.3:9090", nil)if err != nil {fmt.Printf("http server failed, err:%v\n", err)return}
}

在这里插入图片描述

4.2、本地编译,上传到测试服务器10.211.55.123上

Makefile内容

build:CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app main.goscp:scp app root@10.211.55.123:/rootall:make build && make scp

执行

make all

即可

5、对端的路由设置

5.1、是否需要设置路由呢?

如果宿主机-1和宿主机-2上的flannel99都是属于同一个网段,比方:

  • 宿主机-1上的flannel99的IP是10.244.2.2
  • 宿主机-2上的flannel99的IP是10.244.2.3
    的话,是不需要单独设置路由的

而当前我们的测试用例中,两个宿主机上的flannel99的网络是不同的,

因此,需要设置路由。

5.2、在宿主机-1上(10.211.55.122)

查看当前的路由表、并且设置数据包访问宿主机-2上flannel99网络的路由

route -nroute add -net 10.244.3.0/24 dev flannel99route -n

在这里插入图片描述

5.3、在宿主机-2上(10.211.55.123)

查看当前的路由表、并且设置数据包访问宿主机-1上flannel99网络的路由

route -nroute add -net 10.244.2.0/24 dev flannel99route -n

在这里插入图片描述

可能会存在一种现象,新增的路由规则,经常被删除。原因不在深挖了。手工重新添加即可。生产中,可以对路由规则进行定时检测的。

6、测试

6.1、登录到10.211.55.122服务器上,启动tun-vpn-client服务

./tun-vpn-client

在这里插入图片描述

6.2、登录到10.211.55.123服务器上

6.2.1、启动tun-vpn-server服务

./tun-vpn-server 

在这里插入图片描述

6.2.2、启动web服务

./app

在这里插入图片描述

6.3、重新登录10.211.55.122服务器上

6.3.1、curl命令测试 6.3.1.1、curl命令测试

curl 10.244.3.3:9090 

在这里插入图片描述

tun-vpn-client与tun-vpn-server服务打印日志,

在启动服务的时候,已经截图了,

不再重复截图了。

6.3.1.2、抓包,wireshark分析
6.3.1.2.1、针对flannel99网卡抓包分析
tcpdump -nn -i flannel99 -w curl.pcap

抓取命令
在这里插入图片描述

tcp的三次握手
在这里插入图片描述
(注意下:图片中 “flannel99自动给tcp报文添加的IP报文头”,这种说法是有待研究的。仅供参考)

curl请求
在这里插入图片描述

web服务app的回复
在这里插入图片描述

四次挥手
在这里插入图片描述

6.3.1.2.2、针对eth0网卡抓包分析
tcpdump -nn udp -i eth0 -w curl.pcap

在这里插入图片描述

在这里插入图片描述

6.3.2、ping命令测试,测试是否可以ping通10.211.55.123节点上的flannel99网卡

ping 10.244.3.3ping 10.244.3.3 -I flannel99sysctl net.ipv4.conf.all.rp_filter

在这里插入图片描述

ping命令就不在注抓取数据包了。

大家可以自行抓取。

7、总结

  • 本篇文章我们利用tun类型的虚拟网络设备实现了点对点的VPN组网,
  • 并进行了curl命令,ping命令的测试;
  • 支持TCP,ICMP协议的数据包传输。
  • 观察原理图的话,你会发现
  • 数据包在用户空间和内核空间之间来回的切换,这种方式性能肯定存在一定的问题。
  • 未来会有其他网络形式进行代替。

如果本篇文章的内容,你已经了解了的话,

那么,恭喜你,flannel中的UDP模式,你已经掌握核心了。


点击 下面 返回 专栏目录

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

这篇关于零入门kubernetes网络实战-23->基于tun虚拟网络设备来构建点对点的VPN案例(golang版本、helloworld级别)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/274777

相关文章

golang获取当前时间、时间戳和时间字符串及它们之间的相互转换方法

《golang获取当前时间、时间戳和时间字符串及它们之间的相互转换方法》:本文主要介绍golang获取当前时间、时间戳和时间字符串及它们之间的相互转换,本文通过实例代码给大家介绍的非常详细,感兴趣... 目录1、获取当前时间2、获取当前时间戳3、获取当前时间的字符串格式4、它们之间的相互转化上篇文章给大家介

浅谈配置MMCV环境,解决报错,版本不匹配问题

《浅谈配置MMCV环境,解决报错,版本不匹配问题》:本文主要介绍浅谈配置MMCV环境,解决报错,版本不匹配问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录配置MMCV环境,解决报错,版本不匹配错误示例正确示例总结配置MMCV环境,解决报错,版本不匹配在col

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

MyBatis 动态 SQL 优化之标签的实战与技巧(常见用法)

《MyBatis动态SQL优化之标签的实战与技巧(常见用法)》本文通过详细的示例和实际应用场景,介绍了如何有效利用这些标签来优化MyBatis配置,提升开发效率,确保SQL的高效执行和安全性,感... 目录动态SQL详解一、动态SQL的核心概念1.1 什么是动态SQL?1.2 动态SQL的优点1.3 动态S

Pandas使用SQLite3实战

《Pandas使用SQLite3实战》本文主要介绍了Pandas使用SQLite3实战,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1 环境准备2 从 SQLite3VlfrWQzgt 读取数据到 DataFrame基础用法:读

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa