Linux虚拟网络之tun(三)隔离网络下的Raw转发

时间:2022-04-30 22:28:07

在前一篇Linux虚拟网络之tun(二)Raw包转发中,我们在同一个虚机上建立了两个tun网卡,在两个网卡间借用agent_up来ping http_svr。

在实际组网环境中,其实比这个要复杂的多,而且一般也不会是在同一个虚机上的多个网卡间做这种转发(同一个虚机上,直接处理不就完了吗~~~~)。本文在前文基础上,构建一个完全隔离的网络环境,实现内部隧道的建立,转发应用层的报文。

组网模型如下:

Created with Raphaël 2.1.0 应用程序 应用程序 client 1.1.1.x client 1.1.1.x 节点1接口 172.17.0.1 节点1接口 172.17.0.1 节点2接口 172.17.0.2 节点2接口 172.17.0.2 server 1.1.3.1 server 1.1.3.1 应用服务器 应用服务器 应用程序数据 内部协议处理,净荷 是应用程序IP包 网络传输 网络传输 内部协议处理,净荷 是应用程序IP包 应用程序IP报文 应用程序数据 应用程序应答数据 内部协议处理,净荷 是应用程序IP包 网络传输 网络传输 内部协议处理,净荷 是应用程序IP包 应用程序IP报文 应用程序数据

稍微有点绕,解释一下(以下将建立和维护这套传输管道的系统简称为系统):

  1. client是系统给应用程序(用户)分配的地址。应用程序的上行报文通过client接口传输,系统监听client的所有报文。每个client实际上是用一个tun/tap网卡来实现的。
  2. 是不是一定要有client?应用程序不能直接发给对外接口网卡吗?其实是可以的。不过有了client接口,一方面会更容易扩展,想象空间更多;一方面接口的处理可能会简单一些。
  3. 节点2对外接口上收到的上行报文,经过内部协议的处理后,可以直接投递给服务器。在server接口上处理内部协议,还是在节点对外网卡收到包直接处理,其实差别不大,上图的处理稍微简单一些。
  4. 下行报文处理跟上行正好反过来。
  5. 限于测试环境,没有多个测试机,所以用容器来隔离。图中的对外接口地址就是容器的地址。

来一份golang的代码:

  • common.go 实现一些公用函数
package common

import (
"fmt"
"net"
)

type CommonRecever interface {
Read(p []byte) (n int, err error)
Close() error
}

func RunRecever(recever CommonRecever, name string, action func([]byte)) {
go func() {
buff := make([]byte, 8*1024)
for {
num, err := recever.Read(buff)
if err != nil {
fmt.Printf("%s read err: %v \n", name, err)
recever.Close()
return
}
action(buff[:num])
}
}()
}

func UdpSender(rip string) (*net.UDPConn, error) {
ripaddr, err := net.ResolveUDPAddr("udp4", rip)
if err != nil {
fmt.Println("net.ResolveUDPAddr ", err)
return nil, err
}
conn, err := net.DialUDP("udp4", nil, ripaddr)
if err != nil {
fmt.Println("net.DialIP ", err)
return nil, err
}
return conn, nil
}

func UdpRecver(lip string) (*net.UDPConn, error) {
lipaddr, err := net.ResolveUDPAddr("udp4", lip)
if err != nil {
fmt.Println("net.ResolveUDPAddr ", err)
return nil, err
}

conn, err := net.ListenUDP("udp4", lipaddr)
if err != nil {
fmt.Println("net.ListenUDP ", err)
return nil, err
}

return conn, nil
}
  • client.go 客户端代码
package main

import (
"fmt"
"os"
"os/exec"
"os/signal"
"syscall"
"common"
"github.com/songgao/water"
)

func main() {
client, err := water.NewTUN("client")
if err != nil {
fmt.Println(err)
}
defer client.Close()

// 模拟终端地址1
if err = exec.Command("ip", "addr", "add", "1.1.1.1/32", "dev", "client:0", "peer", "1.1.3.1").Run(); err != nil {
fmt.Println(err)
}

// 模拟终端地址2
if err = exec.Command("ip", "addr", "add", "1.1.1.2/32", "dev", "client:1", "peer", "1.1.3.1").Run(); err != nil {
fmt.Println(err)
}

if err := exec.Command("ip", "link", "set", "dev", "client", "up").Run(); err != nil {
fmt.Println(err)
}

dockerSender, err := common.UdpSender("172.17.0.2:20170")
if err != nil {
fmt.Println("docker udp sender ", err)
}
defer dockerSender.Close()

dockerRecver, err := common.UdpRecver("172.17.0.1:20170")
if err != nil {
fmt.Println("docker udp recver ", err)
}
defer dockerRecver.Close()

common.RunRecever(client, "client", func(msg []byte) {
dockerSender.Write(msg)
})

common.RunRecever(dockerRecver, "docker", func(msg []byte) {
client.Write(msg)
})

quitChan := make(chan os.Signal)
signal.Notify(quitChan,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGHUP,
)
<-quitChan
}
  • server.go 服务端代码
package main

import (
"fmt"
"os"
"os/exec"
"os/signal"
"syscall"

"github.com/songgao/water"

"common"
)

func main() {
fmt.Println("start server!!!")
// server interface
serverTun, err := water.NewTUN("server")
if err != nil {
fmt.Println(err)
}
defer serverTun.Close()

if err = exec.Command("ip", "addr", "add", "1.1.3.1/32", "dev", "server:0").Run(); err != nil {
fmt.Println(err)
}

if err := exec.Command("ip", "link", "set", "dev", "server", "up").Run(); err != nil {
fmt.Println(err)
}

if err := exec.Command("ip", "route", "add", "1.1.1.0/24", "dev", "server").Run(); err != nil {
fmt.Println(err)
}

dockerSender, err := common.UdpSender("172.17.0.1:20170")
if err != nil {
fmt.Println("docker udp sender ", err)
}

dockerRecver, err := common.UdpRecver("172.17.0.2:20170")
if err != nil {
fmt.Println("docker udp recver ", err)
}

fmt.Println("server start handle msg!!!")

common.RunRecever(dockerRecver, "docker", func(msg []byte) {
serverTun.Write(msg)
})

common.RunRecever(serverTun, "server", func(msg []byte) {
dockerSender.Write(msg)
})

quitChan := make(chan os.Signal)
signal.Notify(quitChan,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGHUP,
)
<-quitChan
}

执行方式:

  1. 在主机执行 client.go;
  2. 在容器中执行server.go;
  3. 在主机执行 ping 1.1.3.1 -I 1.1.1.1 就会执行从 1.1.1.1 到 1.1.3.1 的ping操作; 执行 ping 1.1.3.1 -I 1.1.1.2 就会执行从 1.1.1.2 到 1.1.3.1 的ping操作;
  4. 在容器中的1.1.3.1地址上建立一个http的服务器,放几个可供下载的文件,比如 test.zip
  5. 在主机上执行 wget --bind-address=1.1.1.1 --no-proxy http://1.1.3.1/test.zip 测试下载

    遗留问题:
    1、如果每个模拟终端是独立的网卡,但是服务器是同一个,那么ping包没问题,下载会涉及到路由问题,无法成功