• iptables目标TPROXY


    TPROXY目标帮助信息如下。

    # iptables -j TPROXY -h
    
    TPROXY target options:
      --on-port port                    Redirect connection to port, or the original port if 0
      --on-ip ip                        Optionally redirect to the given IP
      --tproxy-mark value[/mask]        Mark packets with the given value/mask
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如下配置,将目的端口80的报文设置标记1,并且送到本机监听在30080的套接口。

    # iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY \
      --tproxy-mark 0x1/0x1 --on-port 30080
    #
    # iptables -t mangle -L -n 
    Chain PREROUTING (policy ACCEPT)
    target     prot opt source               destination         
    TPROXY     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:80 
    		TPROXY redirect 0.0.0.0:30080 mark 0x1/0x1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    配置如下的IP路由策略,将标记为1的报文,送到本机回环设备lo处理,本机接收:

    # ip rule add fwmark 1 lookup 100
    # ip route add local 0.0.0.0/0 dev lo table 100
    
    • 1
    • 2

    应用层程序,需要设置套接口IP层选项IP_TRANSPARENT(SOL_IP, IP_TRANSPARENT),以接收代理报文。

    TPROXY目标

    函数xt_register_targets注册目标结构tproxy_tg_reg。

    static struct xt_target tproxy_tg_reg[] __read_mostly = {
        {
            .name       = "TPROXY",
            .family     = NFPROTO_IPV4,
            .table      = "mangle",
            .target     = tproxy_tg4_v1,
            .revision   = 1,
            .targetsize = sizeof(struct xt_tproxy_target_info_v1),
            .checkentry = tproxy_tg4_check,
            .hooks      = 1 << NF_INET_PRE_ROUTING,
            .me     = THIS_MODULE,
        },
    
    static int __init tproxy_tg_init(void)
    {
        return xt_register_targets(tproxy_tg_reg, ARRAY_SIZE(tproxy_tg_reg));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    配置检查函数如下,对于IPv4协议,启用报文重组功能,透明代理仅支持TCP和UDP协议。

    static int tproxy_tg4_check(const struct xt_tgchk_param *par)
    {
        const struct ipt_ip *i = par->entryinfo;
        int err;
    
        err = nf_defrag_ipv4_enable(par->net);
        if (err)
            return err;
    
        if ((i->proto == IPPROTO_TCP || i->proto == IPPROTO_UDP)
            && !(i->invflags & IPT_INV_PROTO))
            return 0;
    
        pr_info_ratelimited("Can be used only with -p tcp or -p udp\n");
        return -EINVAL;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    目标处理函数如下,tproxy_tg4使用的参数如上所示:(TPROXY redirect 0.0.0.0:30080 mark 0x1/0x1)

    static unsigned int
    tproxy_tg4_v1(struct sk_buff *skb, const struct xt_action_param *par)
    {
        const struct xt_tproxy_target_info_v1 *tgi = par->targinfo;
    
        return tproxy_tg4(xt_net(par), skb, tgi->laddr.ip, tgi->lport,
                  tgi->mark_mask, tgi->mark_value);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    首先,检查报文是否属于连接建立完成的套接口,其次,确定本地的IP地址和端口号,如果TPROXY配置的本地地址为零,使用接收数据包的接口上的IP地址作为本地地址。如果,配置的本地端口为零,使用报文中的目的端口。

    static unsigned int
    tproxy_tg4(struct net *net, struct sk_buff *skb, __be32 laddr, __be16 lport,
           u_int32_t mark_mask, u_int32_t mark_value)
    {
        const struct iphdr *iph = ip_hdr(skb);
        struct udphdr _hdr, *hp;
        struct sock *sk;
    
        hp = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(_hdr), &_hdr);
        if (hp == NULL)
            return NF_DROP;
    
        /* check if there's an ongoing connection on the packet
         * addresses, this happens if the redirect already happened
         * and the current packet belongs to an already established
         * connection */
        sk = nf_tproxy_get_sock_v4(net, skb, iph->protocol,
                       iph->saddr, iph->daddr,
                       hp->source, hp->dest,
                       skb->dev, NF_TPROXY_LOOKUP_ESTABLISHED);
    
        laddr = nf_tproxy_laddr4(skb, laddr, iph->daddr);
        if (!lport)
            lport = hp->dest;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    如果以上没有找到连接建立状态的套接口,查找监听状态的套接口。

        /* UDP has no TCP_TIME_WAIT state, so we never enter here */
        if (sk && sk->sk_state == TCP_TIME_WAIT)
            /* reopening a TIME_WAIT connection needs special handling */
            sk = nf_tproxy_handle_time_wait4(net, skb, laddr, lport, sk);
        else if (!sk)
            /* no, there's no established connection, check if
             * there's a listener on the redirected addr/port */
            sk = nf_tproxy_get_sock_v4(net, skb, iph->protocol,
                           iph->saddr, laddr,
                           hp->source, lport,
                           skb->dev, NF_TPROXY_LOOKUP_LISTENER);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果找到的套接口设置了透明选项,将套接口赋值于skb。否则,丢弃报文。

        /* NOTE: assign_sock consumes our sk reference */
        if (sk && nf_tproxy_sk_is_transparent(sk)) {
            /* This should be in a separate target, but we don't do multiple
               targets on the same rule yet */
            skb->mark = (skb->mark & ~mark_mask) ^ mark_value;
    
            pr_debug("redirecting: proto %hhu %pI4:%hu -> %pI4:%hu, mark: %x\n",
                 iph->protocol, &iph->daddr, ntohs(hp->dest),
                 &laddr, ntohs(lport), skb->mark);
    
            nf_tproxy_assign_sock(skb, sk);
            return NF_ACCEPT;
        }
    
        pr_debug("no socket, dropping: proto %hhu %pI4:%hu -> %pI4:%hu, mark: %x\n",
             iph->protocol, &iph->saddr, ntohs(hp->source),
             &iph->daddr, ntohs(hp->dest), skb->mark);
        return NF_DROP;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    TCP套接口

    对于TCP协议,一般情况下在函数__inet_lookup_skb中查找套接口。

    int tcp_v4_rcv(struct sk_buff *skb)
    { 
    
        th = (const struct tcphdr *)skb->data;
        iph = ip_hdr(skb);
    lookup:
        sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
                       th->dest, sdif, &refcounted);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    但是,如果skb结构中已有可用的套接口,返回此套接口(TPROXY中赋值)。

    static inline struct sock *__inet_lookup_skb(struct inet_hashinfo *hashinfo,
                    struct sk_buff *skb, int doff,
                    const __be16 sport, const __be16 dport,
                    const int sdif, bool *refcounted)
    {
        struct sock *sk = skb_steal_sock(skb, refcounted);
        const struct iphdr *iph = ip_hdr(skb);
    
        if (sk)
            return sk;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    对于TIME_WAIT状态的TCP套接口,如果接收到的报文是一个SYN报文,查找是否存在监听状态的套接口,优先使用监听套接口。

    struct sock *
    nf_tproxy_handle_time_wait4(struct net *net, struct sk_buff *skb,
                 __be32 laddr, __be16 lport, struct sock *sk)
    {                  
        const struct iphdr *iph = ip_hdr(skb);
        struct tcphdr _hdr, *hp;
        
        hp = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(_hdr), &_hdr);
        if (hp == NULL) {
            inet_twsk_put(inet_twsk(sk));
            return NULL;
        }  
        if (hp->syn && !hp->rst && !hp->ack && !hp->fin) {
            /* SYN to a TIME_WAIT socket, we'd rather redirect it
             * to a listener socket if there's one */
            struct sock *sk2;
            
            sk2 = nf_tproxy_get_sock_v4(net, skb, iph->protocol,
                            iph->saddr, laddr ? laddr : iph->daddr,
                            hp->source, lport ? lport : hp->dest,
                            skb->dev, NF_TPROXY_LOOKUP_LISTENER);
            if (sk2) {
                inet_twsk_deschedule_put(inet_twsk(sk));
                sk = sk2;
            }
        }   
        return sk;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    UDP套接口

    对于UDP协议,如果skb中套接口结构不为空,使用此套接口(TPROXY中赋值)。

    int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable,
               int proto)
    {
    
        sk = skb_steal_sock(skb, &refcounted);
        if (sk) {
            struct dst_entry *dst = skb_dst(skb);
            int ret;
            
            if (unlikely(sk->sk_rx_dst != dst))
                udp_sk_rx_dst_set(sk, dst);
            
            ret = udp_unicast_rcv_skb(sk, skb, uh);
            if (refcounted)
                sock_put(sk);
            return ret;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    内核版本 5.10

  • 相关阅读:
    添加环境变量
    为Electron-log 设置日志颜色
    springboot基于Web的社区医院管理服务系统springboot025
    uniapp音频加进度条加蓝牙ibecon设备搜索
    cf Educational Codeforces Round 133 D. Chip Move
    [NepCTF2022] 复现
    C语言——栈
    (封装)已知的一个类Student
    AIGC ChatGPT4 读取接口文件并进行可视化分析
    算法-3.无重复字符的最长子串
  • 原文地址:https://blog.csdn.net/sinat_20184565/article/details/125473975