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),以接收代理报文。
函数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协议,一般情况下在函数__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协议,如果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