We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
接触过VPN相关技术的基本都会接触过虚拟网卡,tun,tap等字眼,因为大部分vpn都或多或少使用有类似技术。本文会对tun/tap设备的基本原理进行说明,并且对其如何应用在vpn上进行了分析,最后提供一个简单的tun的vpn的实现代码。
首先需要明确一点,tun和tap是两种类型的虚拟设备,其一大区别是从tun设备读取数据,你将能够拿到三层包,从tap网卡获取数据,你将能拿到二层包。
在了解虚拟网卡之前,应该先简单了解下真实网卡是如何进行工作的。 首先,网卡介于物理网络和内核协议栈之间,接受协议栈外出的数据并将数据往物理网络发出,同时,也接受外部数据并交付给内核协议栈进行处理。(在这里先将内核协议栈当成一个整体,一个黑盒来看待。)
了解物理网卡所处的位置以及网络数据包的流动之后,再看看虚拟网卡有什么不一样的地方。 从最直观的使用来看,用户是可以直接读写虚拟网卡的,也就是说,从内核协议栈发出的数据在选定以虚拟网卡发出之后,数据将会被用户层程序直接读取,这点与物理网卡不一样,物理网卡直接就往外发。虚拟网卡告知用户程序数据可读。
在写方面,用户进程往虚拟网卡写数据会直接从网卡写出去 一图胜千言:
了解了TUN与TAB的基本原理之后,可以明确的知道,用户层通过虚拟网卡具备有读写二层,三层数据包的能力,这种读写与原始套接字还不一样,原始套套接字做的事旁路拷贝,这个是直接截取数据包到用户层,用户层自己处理。
有了这类技术底子之后,再看看vpn,很多人一提到vpn就想到翻墙,vpn并不等于翻墙,vpn的一个目的是为不同地区模拟出一个局域网环境,让A地区的员工能够像访问局域网一样访问位于总部B的服务器或者其他比如打印机,这是vpn。
一图胜千言:
ping经过内核协议栈,路由选择从虚拟网卡发出
虚拟网卡的另外一端,也就是用户进程,将这一ping包读取出来
将ping的payload通过真实网卡发出,经过一系列的传输,到达目的主机,
目的主机收到数据包之后,将其写入虚拟网卡。
Ping reply返回类似,上图的左右两端是等价的,能够收发数据包。
为了方便说明这一原理,编写一个简单的基于tun设备的vpn——gtun
gtun客户端:
package main import ( "encoding/binary" "flag" "net" "os" "os/signal" "syscall" "github.com/ICKelin/glog" "github.com/songgao/water" ) var ( psrv = flag.String("s", "120.25.214.63:9621", "srv address") pdev = flag.String("dev", "gtun", "local tun device name") ) func main() { flag.Parse() cfg := water.Config{ DeviceType: water.TUN, } cfg.Name = *pdev ifce, err := water.New(cfg) if err != nil { glog.ERROR(err) return } conn, err := ConServer(*psrv) if err != nil { glog.ERROR(err) return } go IfaceRead(ifce, conn) go IfaceWrite(ifce, conn) sig := make(chan os.Signal, 3) signal.Notify(sig, syscall.SIGINT, syscall.SIGABRT, syscall.SIGHUP) <-sig } func ConServer(srv string) (conn net.Conn, err error) { conn, err = net.Dial("tcp", srv) if err != nil { return nil, err } return conn, err } func IfaceRead(ifce *water.Interface, conn net.Conn) { packet := make([]byte, 2048) for { n, err := ifce.Read(packet) if err != nil { glog.ERROR(err) break } err = ForwardSrv(conn, packet[:n]) if err != nil { glog.ERROR(err) } } } func IfaceWrite(ifce *water.Interface, conn net.Conn) { packet := make([]byte, 2000) for { nr, err := conn.Read(packet) if err != nil { glog.ERROR(err) break } _, err = ifce.Write(packet[4:nr]) if err != nil { glog.ERROR(err) } } } func ForwardSrv(srvcon net.Conn, buff []byte) (err error) { output := make([]byte, 0) bsize := make([]byte, 4) binary.BigEndian.PutUint32(bsize, uint32(len(buff))) output = append(output, bsize...) output = append(output, buff...) left := len(output) for left > 0 { nw, er := srvcon.Write(output) if err != nil { err = er } left -= nw } return err }
gtun_srv,中间转发服务
package main import ( "io" "net" "github.com/ICKelin/glog" ) var client = make([]net.Conn, 0) func main() { listener, err := net.Listen("tcp", ":9621") if err != nil { glog.ERROR(err) return } for { conn, err := listener.Accept() if err != nil { glog.ERROR(err) break } client = append(client, conn) glog.INFO("accept gtun client") go HandleClient(conn) } } func HandleClient(conn net.Conn) { defer conn.Close() buff := make([]byte, 65536) for { nr, err := conn.Read(buff) if err != nil { if err != io.EOF { glog.ERROR(err) } break } // broadcast for _, c := range client { if c.RemoteAddr().String() != conn.RemoteAddr().String() { c.Write(buff[:nr]) } } } }
这里示例程序为了简化Demo,中间转发服务器将收到的数据包广播给所有的客户端,具体gtun实现当中会有一个协议的解码,根据目的地址来做转发。
后续将会往路由选择方面靠拢,逐步将内核协议栈这一黑盒慢慢打开。
The text was updated successfully, but these errors were encountered:
@ICKelin 讲的很通俗易懂,点个👍,但是有问题想请教一下,我在两台ubuntu服务器下分别启动服务端以及客户端的程序,并且设置好了客户端的虚拟网卡路由,然后我ping服务端的网段,流量已经成功转发到服务端,但是ping请求一直阻塞,并没有获得响应,能解答一下嘛?
Sorry, something went wrong.
@stone-98 可以抓包看看,我猜测可能是你有一条iptables命令没加上 iptables -t nat -I POSTROUTING -j MASQUERADE
iptables -t nat -I POSTROUTING -j MASQUERADE
@ICKelin 还是没有成功,但是我使用抓包查看发现请求并没有转发到服务端 客户端ip:116.62.129.179 服务端ip:167.179.89.137 我的步骤如下:
accept gtun client
gtun
sudo ip addr add 167.179.89.136/24 dev gtun
up
sudo ip link set gtun up
route add -net 167.179.89.0/24 gw 167.179.89.137 gtun
这是我大致遇到的问题,我之前的描述有误其实流量并没有转发到服务端,所以应该不是iptables的原因吧,能给我一点思路嘛?
No branches or pull requests
接触过VPN相关技术的基本都会接触过虚拟网卡,tun,tap等字眼,因为大部分vpn都或多或少使用有类似技术。本文会对tun/tap设备的基本原理进行说明,并且对其如何应用在vpn上进行了分析,最后提供一个简单的tun的vpn的实现代码。
TUN/TAP设备的基本原理
首先需要明确一点,tun和tap是两种类型的虚拟设备,其一大区别是从tun设备读取数据,你将能够拿到三层包,从tap网卡获取数据,你将能拿到二层包。
在了解虚拟网卡之前,应该先简单了解下真实网卡是如何进行工作的。
首先,网卡介于物理网络和内核协议栈之间,接受协议栈外出的数据并将数据往物理网络发出,同时,也接受外部数据并交付给内核协议栈进行处理。(在这里先将内核协议栈当成一个整体,一个黑盒来看待。)
了解物理网卡所处的位置以及网络数据包的流动之后,再看看虚拟网卡有什么不一样的地方。
从最直观的使用来看,用户是可以直接读写虚拟网卡的,也就是说,从内核协议栈发出的数据在选定以虚拟网卡发出之后,数据将会被用户层程序直接读取,这点与物理网卡不一样,物理网卡直接就往外发。虚拟网卡告知用户程序数据可读。
在写方面,用户进程往虚拟网卡写数据会直接从网卡写出去
一图胜千言:
TUN/TAP与VPN
了解了TUN与TAB的基本原理之后,可以明确的知道,用户层通过虚拟网卡具备有读写二层,三层数据包的能力,这种读写与原始套接字还不一样,原始套套接字做的事旁路拷贝,这个是直接截取数据包到用户层,用户层自己处理。
有了这类技术底子之后,再看看vpn,很多人一提到vpn就想到翻墙,vpn并不等于翻墙,vpn的一个目的是为不同地区模拟出一个局域网环境,让A地区的员工能够像访问局域网一样访问位于总部B的服务器或者其他比如打印机,这是vpn。
一图胜千言:
ping经过内核协议栈,路由选择从虚拟网卡发出
虚拟网卡的另外一端,也就是用户进程,将这一ping包读取出来
将ping的payload通过真实网卡发出,经过一系列的传输,到达目的主机,
目的主机收到数据包之后,将其写入虚拟网卡。
Ping reply返回类似,上图的左右两端是等价的,能够收发数据包。
为了方便说明这一原理,编写一个简单的基于tun设备的vpn——gtun
gtun客户端:
gtun_srv,中间转发服务
这里示例程序为了简化Demo,中间转发服务器将收到的数据包广播给所有的客户端,具体gtun实现当中会有一个协议的解码,根据目的地址来做转发。
后续将会往路由选择方面靠拢,逐步将内核协议栈这一黑盒慢慢打开。
The text was updated successfully, but these errors were encountered: