tproxy_Linux使用TPROXY进行UDP的透明代理

1.为什么使用TPROXY才能代理UDP

在进行TCP的代理时,只要在NET表上无脑进行REDIRECT就好了。例如使用ss-redir,你只要把tcp的流量redirect到ss-redir监听的端口上就OK了。但是当你使用这种方法的时候,就会不正常,因为对于UDP进行redirect之后,原始的目的地址和端口就找不到了。

这是为什么呢?

ss-redir的原理很简单:使用iptables对PREROUTING与OUTPUT的TCP/UDP流量进行REDIRECT(REDIRECT是DNAT的特例),ss—redir在捕获网络流量后,通过一些技术手段获取REDIRECT之前的目的地址(dst)与端口(port),连同网络流量一起转发至远程服务器。

针对TCP连接,的确是因为Linux Kernel连接跟踪机制的实现才使获取数据包原本的dst和port成为可能,但这种连接跟踪机制并非只存在于TCP连接中,UDP连接同样存在,conntrack -p udp便能看到UDP的连接跟踪记录。内核中有关TCP与UDP的NAT源码/net/netfilter/nf_nat_proto_tcp.c和/net/netfilter/nf_nat_proto_udp.c几乎一模一样,都是根据NAT的类型做SNAT或DNAT。

那这究竟是怎么一回事?为什么对于UDP连接就失效了呢?

回过头来看看ss-redir有关获取TCP原本的dst和port的源码,核心函数是getdestaddr:

static int

getdestaddr(int fd, struct sockaddr_storage *destaddr)

{

socklen_t socklen = sizeof(*destaddr);

int error = 0;

error = getsockopt(fd, SOL_IPV6, IP6T_SO_ORIGINAL_DST, destaddr, &socklen);

if (error) { // Didn't find a proper way to detect IP version.

error = getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, destaddr, &socklen);

if (error) {

return -1;

}

}

return 0;

}

在内核源码中搜了下有关SO_ORIGINAL_DST的东西,看到了getorigdst:

static int

getorigdst(struct sock *sk, int optval, void __user *user, int *len)

{

const struct inet_sock *inet = inet_sk(sk);

const struct nf_conntrack_tuple_hash *h;

struct nf_conntrack_tuple tuple;

memset(&tuple, 0, sizeof(tuple));

lock_sock(sk);

tuple.src.u3.ip = inet->inet_rcv_saddr;

tuple.src.u.tcp.port = inet->inet_sport;

tuple.dst.u3.ip = inet->inet_daddr;

tuple.dst.u.tcp.port = inet->inet_dport;

tuple.src.l3num = PF_INET;

tuple.dst.protonum = sk->sk_protocol;

release_sock(sk);

/* We only do TCP and SCTP at the moment: is there a better way? */

if (tuple.dst.protonum != IPPROTO_TCP &&

tuple.dst.protonum != IPPROTO_SCTP) {

pr_debug("SO_ORIGINAL_DST: Not a TCP/SCTP socket\n");

return -ENOPROTOOPT;

}

We only do TCP and SCTP at the moment。Oh,shit!只针对TCP与SCTP才能这么做,并非技术上不可行,只是人为地阻止罢了。

2.TPROXY

为了在redirect UDP后还能够获取原本的dst和port,ss-redir采用了TPROXY。Linux系统有关TPROXY的设置是以下三条命令:

ip rule add fwmark 0x2333/0x2333 pref 100 table 100

ip route add local default dev lo table 100

iptables -t mangle -A PREROUTING -p udp -j TPROXY --tproxy-mark 0x2333/0x2333 --on-ip 127.0.0.1 --on-port 1080

大意就是在mangle表的PREROUTING中为每个UDP数据包打上0x2333/0x2333标志,之后在路由选择中将具有0x2333/0x2333标志的数据包投递到本地环回设备上的1080端口;对监听0.0.0.0地址的1080端口的socket启用IP_TRANSPARENT标志,使IPv4路由能够将非本机的数据报投递到传输层,传递给监听1080端口的ss-redir。IP_RECVORIGDSTADDR与IPV6_RECVORIGDSTADDR则表示获取送达数据包的dst与port。

可问题来了:要知道mangle表并不会修改数据包,那么TPROXY是如何做到在不修改数据包的前提下将非本机dst的数据包投递到换回设备上的1080端口呢?

这个问题在内核中时如何实现的,还待研究,但是确定是TPROXY做了某些工作。

TPROXY主要功能:

重定向一部分经过路由选择的流量到本地路由进程(类似NAT中的REDIRECT)

在非本地IP上起监听。监听后就可以转发了(神奇吧)

TPROXY要解决的两个重要的问题

1.套接字如何监听到非本地IP地址。

先用setsockopt函数为套接字设置IP_TRANSPARENT标识,再去监听0.0.0.0地址这样的方式来实现监听任意IP。

2.如何获取的原始目标的端口 。

先调用setsockopt (s, IPPROTO_IP, IP_RECVORIGDSTADDR, &n, sizeof(int))函数为套接字设置IP_RECVORIGDSTADDR标识,然后通过recvmsg函数从tproxy那边接受发过来的msghdr结构体信息,并循环遍历cmsghdr成员最终获取到原始目标的地址和端口,也就是说tproxy会向msghdr(附属数据结构)填入原始目标ip和端口信息,再通过sendmsg函数发送给代理应用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值