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

如下配置,将目的端口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

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

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

应用层程序,需要设置套接口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));

配置检查函数如下,对于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;

目标处理函数如下,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);

首先,检查报文是否属于连接建立完成的套接口,其次,确定本地的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;

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

    /* 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);

如果找到的套接口设置了透明选项,将套接口赋值于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;

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);

但是,如果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;

对于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;

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;
    }

内核版本 5.10

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值