在前一篇Linux虚拟网络之tun(二)Raw包转发中,我们在同一个虚机上建立了两个tun网卡,在两个网卡间借用agent_up来ping http_svr。
在实际组网环境中,其实比这个要复杂的多,而且一般也不会是在同一个虚机上的多个网卡间做这种转发(同一个虚机上,直接处理不就完了吗~~~~)。本文在前文基础上,构建一个完全隔离的网络环境,实现内部隧道的建立,转发应用层的报文。
组网模型如下:
稍微有点绕,解释一下(以下将建立和维护这套传输管道的系统简称为系统
):
- client是系统给应用程序(用户)分配的地址。应用程序的上行报文通过client接口传输,系统监听client的所有报文。每个client实际上是用一个tun/tap网卡来实现的。
- 是不是一定要有client?应用程序不能直接发给对外接口网卡吗?其实是可以的。不过有了client接口,一方面会更容易扩展,想象空间更多;一方面接口的处理可能会简单一些。
- 节点2对外接口上收到的上行报文,经过内部协议的处理后,可以直接投递给服务器。在server接口上处理内部协议,还是在节点对外网卡收到包直接处理,其实差别不大,上图的处理稍微简单一些。
- 下行报文处理跟上行正好反过来。
- 限于测试环境,没有多个测试机,所以用容器来隔离。图中的对外接口地址就是容器的地址。
来一份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
}
执行方式:
- 在主机执行 client.go;
- 在容器中执行server.go;
- 在主机执行
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操作; - 在容器中的1.1.3.1地址上建立一个http的服务器,放几个可供下载的文件,比如 test.zip
-
在主机上执行
wget --bind-address=1.1.1.1 --no-proxy http://1.1.3.1/test.zip
测试下载遗留问题:
1、如果每个模拟终端是独立的网卡,但是服务器是同一个,那么ping包没问题,下载会涉及到路由问题,无法成功