OpenWrt导入指定流量到虚拟网卡tun上

OpenWrt指定网卡流量到虚拟网卡tun上,实际用到的策略路由方案操作如下:

  1. /etc/config/network配置文件新增内容

    config interface 'tun'
            option proto 'none'
            option ifname 'tun0'
    
  2. 防火墙增加如下:

    #新增tun 区域
    config zone
            option name 'tun'
            option input 'ACCEPT'
            option output 'ACCEPT'
            option forward 'ACCEPT'
            option network 'tun'
    
    #新增允许lan0 区域到tun区别的转发功能
    config forwarding
            option dest 'tun'
            option src 'lan0'
    
    

  3. 启动tun0程序,并读取tun0上的数据如下:

    #include <string.h>
    #include <fcntl.h>
    #include <sys/socket.h>
    #include <linux/ip.h>
    #include <linux/if_tun.h>
    #include <arpa/inet.h>
    #include <errno.h>
    #include <linux/if_ether.h>
    #include <linux/if_packet.h>
    #include <sys/un.h>
    #include <sys/stat.h>
    #include <sys/ioctl.h>
    #include <netinet/in.h>
    #include <unistd.h>
    #include <net/if.h>
    #include <uv.h>
    
    
    #include "fr_log.h"
    
    
    #if !defined(LOG_TAG)
        #define LOG_TAG     "main"
    #endif
    #include "elog.h"
    
     
    
    int tun_create(char* dev,int net_addr) 
    { 
        int           tun_fd;
        struct ifreq  ifr;
        // 打开tun设备
        tun_fd = open("/dev/net/tun", O_RDWR);
        if(0 > tun_fd){
            log_e("open tun file error!");
            return -1;
        }
        // 设置tun设备的标志位IFF_TUN | IFF_NO_PI
        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)){
            log_e("Failed to set TUN device name:%s",strerror(errno));
            close(tun_fd);
            return -1;
        }
    
        // 创建socket 原始编程
        int sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
        if(0 > ioctl(sock, SIOCGIFFLAGS, &ifr)){
            log_e("Failed to bring up socket:%s",strerror(errno));
            close(tun_fd);
            return -1;
        }
        // 设置socket 属性 up
        ifr.ifr_flags |= IFF_UP | IFF_RUNNING | IFF_PROMISC;
        if(0 > ioctl(sock, SIOCSIFFLAGS, &ifr)){
            log_e("Failed to set socket flags:%s",strerror(errno));
            close(tun_fd);
            return -1;
        }
    
        // 设置tun设备的ip地址
        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)){
            log_e("Failed to set socket address:%s",strerror(errno));
            close(tun_fd);
            return -1;
        }
        // 设置tun设备的掩码地址
        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)){
            log_e("Failed to set socket netmask:%s",strerror(errno));
            close(tun_fd);
            return -1;
        }
        // 获取tun设备的index
        if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0){
            log_e("Failed to get socket index:%s",strerror(errno));
            close (tun_fd);
            return -1;
        }
    
    
        // 根据index 绑定
        struct sockaddr_ll addr;
        memset (&addr, 0, sizeof (addr));
        addr.sll_family   = AF_PACKET;
        addr.sll_ifindex  = ifr.ifr_ifindex;
        addr.sll_protocol = htons(ETH_P_ALL);
        log_d("addr.sll_ifindex  %d" ,addr.sll_ifindex);
        if (-1 == bind(sock, (struct sockaddr *)&addr, sizeof(addr))){
            log_e("binding socket %d with if %s error: %s",sock, dev, strerror(errno));
            close (tun_fd);
            return -1;
        }
    
        // 轮询接收socket的数据
        int N_bytes;
        unsigned char msg[8000];
        in_addr ip_addr;
        ip_addr.s_addr = net_addr;
        while(true){
            N_bytes = recv (sock, msg, 8000, 0);
            if (N_bytes > 0){
                log_d("%s sock recv msg len: %3d, msg: ", inet_ntoa(ip_addr), N_bytes);
            }
        }
    } 
     
    
    int main(int argc, char *argv[]) 
    {
        fr_log_init();
    
    
        char tun_name[ IFNAMSIZ]="tun0" ; 
        tun_create(tun_name,inet_addr("10.10.10.1"));
        return 0;
    }
    

    启动该程序

  4. 添加识别规则

    #源地址为192.168.40.200的udp包 标记为 2
    iptables -t mangle -A PREROUTING  -p udp  -s 192.168.40.200 -j MARK --set-mark 2
    iptables -t mangle -A PREROUTING  -p udp  -s 192.168.40.200 -j RETURN
    
  5. 添加路由规则

    #添加自定义的路由规则,识别为标签为2的流量 使用路由表 201
    ip rule add fwmark 2 table 201
    
    #给路由表20 添加默认的路由规则,默认路由表201的 流量走 tun0 口出
    ip route add default dev tun0 table 201
    
  6. 设置好就会看到下面的结果

高级进阶

  1. 重新定义路由表编号

    上面第5步提到,导入fwmark 标记为2的 流量到路由表201 ,我们可以给这个编号201起个名字如下:

    图中说明:给路由表201起的名字为 tuntable,

    注意:第一次可以使用echo "201 tuntable" >> /etc/iproute2/rt_tables命令直接增加该条目。

    那么上面的第5步就可以改为下面命令:

    #添加自定义的路由规则,识别为标签为2的流量 使用路由表 tuntable
    ip rule add fwmark 2 table tuntable
    
    #给路由表201 即 tuntable 添加默认的路由规则,默认路由表tuntable的 流量走 tun0 口出
    ip route add default dev tun0 table tuntable
    
  2. 重新定义iptable链

    上面第4步提到直接修改mangle表的PREROUTING链,新增MARK的规则,这里可以自定义一条链,在我们自定义的链上操作:

    iptables -t mangle -N mymark  #在mangle上 新建链  mymark
    iptables -t mangle -A PREROUTING -j mymark  # 先把mangle表上的PREROUTING链上流量导入到mymark链上
    
    #对刚才自定义的mymark链,进行流量识别并,打标为2
    iptables -t mangle -A mymark -p udp  -s 192.168.40.200 -j MARK --set-mark 2
    

    紧接着我们可以使用自定义的路由规则把这个打标的流量导入到tun上

    #添加自定义的路由规则,识别为标签为2的流量 使用路由表 tuntable
    ip rule add fwmark 2 table tuntable
    
    #给路由表201 即tuntable 添加默认的路由规则,默认路由表tuntable的 流量走 tun0 口出
    ip route add default dev tun0 table tuntable
    
  3. 常用的调试手段

    • 查看表mangle上的流量

      iptables -t mangle -L -n -v
      
    • 查看路由表里面的路由规则

      ip route show table 201 #或者
      ip route show table tuntable
      
    • 查看路由rule

      ip rule list
      
    • 删除静态路由

      route del -net 192.168.40.0/24
      
    • 删除路由规则

      #ip rule show  先用该命令查看 然后执行下面的命令, num就是第几号的意思
      ip rule delete num
      
    • 删除防火墙链的规则

      iptables -t mangle -D PREROUTING 1  #1 代表第一条规则
      
    • 刷新路由表

      ip route flush cache
      
    • 修改内核参数

      sysctl -w net.ipv4.conf.eth0.rp_filter=0
      sysctl -w net.ipv4.all.arp_ignore=1
      sysctl -w net.ipv4.conf.all.arp_announce=2
      sysctl -w net.ipv4.ip_forward=1
      

shell 创建tun的方式

注意:该方式创建的tun 无法使用上面方式 把流量导入到该tun上,不知为啥。这里列出只是作下记录笔记

ip tuntap add dev tun0 mod tun
ip link set tun0 up
ip addr add 10.10.10.1/24 dev tun0


#删除
ip tuntap del dev tun0 mod tun