Linux虚拟网络之tun(二)Raw包转发

有一种应用场景,是建立某种传输通道,将用户的报文按需投递。建立的通道可以采用自定义的协议传输,用户的报文是IP包。以上行报文为例,其中目的地址是某个服务器,源地址是用户自己的ip。

有两种使用场景:
1、服务器是用户无法直接访问的,比如是一个内部服务器,地址不公开,所有的访问都需要被控制(比如通道建立者是可以控制的);
2、用户地址是内部地址,无法直接访问服务器,需要由代理来转。这种场景用户是受控的内部用户(比如手机入网后,无线网络分配的地址就是内部地址)。

包传递的流程大致如下:

Created with Raphaël 2.1.0 用户 用户 代理 代理 服务器 服务器 用内部协议头封装的报文,payload是IP包 去掉内部协议头后将IP包转发出去 处理结束后,应答的IP包 用内部协议头封装后的报文,payload是IP包

我们来模拟实现一个这种场景的代码,先上golang版本的,比较简洁:

package main

import (
    "net"
    "os/exec"

    "fmt"

    "github.com/mdlayher/raw"
    "github.com/songgao/water"
)

func AddIpToIntf(ifName string, ip string) error {
    if err := exec.Command("ip", "addr", "add", ip, "dev", ifName).Run(); err != nil {
        fmt.Println(err)
        return err
    }

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

func NewTUNIntf(ifName string, ip string) (ifce *water.Interface, err error) {
    intf, err := water.NewTUN(ifName)
    if err != nil {
        return nil, err
    }

    return intf, AddIpToIntf(ifName, ip)
}

func main() {
    _, err := NewTUNIntf("http_svr", "1.1.2.1/24")
    if err != nil {
        fmt.Println(err)
    }
    agentUpIntf, err := NewTUNIntf("agent_up", "1.1.1.1/24")
    if err != nil {
        fmt.Println(err)
    }

    ipaddr, err := net.ResolveIPAddr("ip4", "1.1.2.1")
    if err != nil {
        fmt.Println("net.ResolveIPAddr ", err)
    }
    conn, err := net.DialIP("ip4:255", nil, ipaddr)
    if err != nil {
        fmt.Println("net.DialIP ", err)
    }

    data := make([]byte, 1024*8)
    for {
        num, err := agentUpIntf.Read(data)
        if err != nil {
            fmt.Println("agent up recv ", err)
        }
        if num > 0 {
            fmt.Println("agent up recv data len:", num, ", data:", data[:20])
        }

        num, err = conn.Write(data[:num])
        if err != nil {
            fmt.Println("write to http conn", err)
        }
        if num > 0 {
            fmt.Println("write to http conn data len:", num, ", data:", data[:20])
        }
    }
}

1、NewTUNIntf是创建虚拟网卡的,连带生效一个ip地址;
2、net.DialIP创建一个raw类型的socket,特别要注意参数“ip4:255”,其中的255表示IPPROTO_RAW。协议类型非常重要,否则下面的报文是无法转发出去的(因为转发的IP包)。
3、从agentUpIntf收到的包,直接发给http服务器。

上面的代码有个问题没有体现:用户的报文从哪里来?
实际环境中,报文的来源有很多方式,不在这里的讨论范围。不过常见的一种,是 agent从另一个网卡接收用户数据。

好了,稍微验证一下,在shell里面运行:

ping 1.1.2.1 -I agent_up

就能看到ping包是通的。

现在来个c版本的代码:

#include <fcntl.h>
#include <sys/socket.h>
#include <linux/ip.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <iostream>
#include <errno.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <thread>

using namespace std;

int NewTUNIntf(char* dev, int net_addr)
{
    int           tun_fd;
    struct ifreq        ifr;

    tun_fd = open("/dev/net/tun", O_RDWR);
    if(0 > tun_fd)
    {
        cout<<"open tun file error!" << endl;
        return tun_fd;
    }

    memset(&ifr, 0, sizeof(ifr));
    ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
    strncpy(ifr.ifr_ifrn.ifrn_name, dev, IFNAMSIZ);
    if(0 > ioctl(tun_fd, TUNSETIFF, &ifr))
    {
        string err_str = strerror(errno);
        cout << "Failed to set TUN device name: " << err_str << endl;
        close(tun_fd);
        return tun_fd;
    }

    // Bring up the interface
    int sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if(0 > ioctl(sock, SIOCGIFFLAGS, &ifr))
    {
        string err_str = strerror(errno);
        cout << "Failed to bring up socket: " << err_str << endl;
        close(tun_fd);
        return tun_fd;
    }
    ifr.ifr_flags |= IFF_UP | IFF_RUNNING | IFF_PROMISC;
    if(0 > ioctl(sock, SIOCSIFFLAGS, &ifr))
    {
        string err_str = strerror(errno);
        cout << "Failed to set socket flags: " << err_str << endl;
        close(tun_fd);
        return tun_fd;
    }

    ifr.ifr_addr.sa_family                                 = AF_INET;
    ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr = net_addr;
    if(0 > ioctl(sock, SIOCSIFADDR, &ifr))
    {
        string err_str = strerror(errno);
        cout << "Failed to set socket address: " << err_str << endl;
        close(tun_fd);
        return tun_fd;
    }

    ifr.ifr_netmask.sa_family                                 = AF_INET;
    ((struct sockaddr_in *)&ifr.ifr_netmask)->sin_addr.s_addr = inet_addr("255.255.255.0");
    if(0 > ioctl(sock, SIOCSIFNETMASK, &ifr))
    {
        string err_str = strerror(errno);
        cout << "Failed to set socket netmask: " << err_str << endl;
        close(tun_fd);
        return tun_fd;
    }
}

void task(char* dev, int net_addr)
{
    int tun_fd = NewTUNIntf(dev, net_addr);

    int sd = -1;
    if ((sd = socket (AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)
    {
        printf("getting socket error: %s", strerror(errno));
        return;
    }

    // 这个设置不影响结果
//    const int on = 1;
//    if (setsockopt (sd, IPPROTO_IP, IP_HDRINCL, &on, sizeof (on)) < 0)
//    {
//        printf("set socket %d if %s opt IP_HDRINCL error: %s", sd, "2152", strerror(errno));
//        close (sd);
//        return;
//    }

    struct sockaddr_in sin;
    memset (&sin, 0, sizeof (struct sockaddr_in));
    sin.sin_family = AF_INET;
    if (!inet_pton(AF_INET, "172.23.2.24", &sin.sin_addr))
    {
        printf("send inet_pton failed!");
        return;
    }

    int N_bytes;
    unsigned char msg[8000];
    in_addr ip_addr;
    ip_addr.s_addr = net_addr;
    while(true)
    {
        N_bytes = read(tun_fd, msg, 8000);
        if (N_bytes > 0)
        {
            printf("%s recv msg len: %3d, msg: ", inet_ntoa(ip_addr), N_bytes);
            for (int i = 0; i < 20 && i < N_bytes; i++) printf("%2x ", msg[i]);
            cout << endl;
        }

        if (sendto (sd, msg, N_bytes, 0, (struct sockaddr *) &sin, sizeof (struct sockaddr)) < 0)
        {
            printf("sendto %s, sendLocalSocketId : %d, error: %s", "172.23.2.24", sd, strerror(errno));
            return;
        }
    }
}

void task2(char* dev, int net_addr)
{
    int tun_fd = NewTUNIntf(dev, net_addr);
    int N_bytes;
    unsigned char msg[8000];
    in_addr ip_addr;
    ip_addr.s_addr = net_addr;
    while(true)
    {
        N_bytes = read(tun_fd, msg, 8000);
        if (N_bytes > 0)
        {
            printf("%s recv msg len: %3d, msg: ", inet_ntoa(ip_addr), N_bytes);
            for (int i = 0; i < 20 && i < N_bytes; i++) printf("%2x ", msg[i]);
            cout << endl;
        }
    }
}

int main(){
    char dev[IFNAMSIZ] = "tun1";
    char dev2[IFNAMSIZ] = "tun2";

    thread t(task, dev, inet_addr("172.23.1.25"));
    thread t2(task2, dev2, inet_addr("172.23.2.24"));
    t.join();
    t2.join();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值