注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

itoedr的it学苑

记录从IT文盲学到专家的历程

 
 
 

日志

 
 

tproxy的原理简析  

2013-07-31 02:15:48|  分类: 透明传输技术 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
   tproxy是iptables的一附加控件,在一个控件中同时实现了elnetProxy,SocksProxy,HTTPProxy几种透明代理功能。在mangle表的PREROUTING链中使用,不修改数据包包头,直接把数据传递给一个本地socket(即不对数据包进行任何nat操作)。

  在tproxy工作过程中,首先,这是tproxy向netfilter注册的一个target,该target能够在不对数据包修改的情况下,将数据包代理到本地套接字上;
  他的关键点在于通过获得数据包的目的端口和目的地址(而非本地监听套接字的bind地址),再通过nf_tproxy_get_sock_v4函数,获得监听套接字,最后把skb->sock = sk;
      #更多内容参考如下代码分析。

  tproxy优势:其的优势在于不对数据包(包头信息)进行任何改变(NAT),就可以重定向数据包。

***********************
使用例子:
/*
   iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 50080
   参数说明:
laddr 为代理服务器的本地监听套接字的本地ip,此例中省略,即取本地ip;
lport 为代理服务器的本地监听套接字的本地port,50080(也可以是别的端口,是另一个服务进程监听的端口,是本控件的数据出口);
mark_value 为mark值,依据此标记值使用ip rule命令工具为标记数据流进行路由选择;
*/


控件进一步理解:
***********************
“代理”这个术语的意思是什么?
What does the term 'proxy' mean?
--------------------------------

A proxy is a server-like program, receiving requests from clients, forwarding those requests to the real server on behalf of users,
and returning the response as it arrives.
  代理就象一个服务器程序,从客户端取得其请求,并代表用户将这些请求向前转发去用户请求的真实服务器,
然后还要代表服务器向用户端返回真实服务器就此请求给于客户端的回应数据(结果就像自己就是那个真实的服务器);

Proxies read and parse the application protocol, and reject invalid traffic. As most attacks violate the application protocol, disallowing
protocol violations usually protects against attacks.
  代理服务器读取并分析应用协议,并剔除那些不合格的报文。

What is transparent proxying?
什么是透明代理呢?
-----------------------------

To simplify management tasks of clients sitting behind proxy firewalls, the technique 'transparent proxying' was invented.
Transparent proxying means that the presence of the proxy is invisible to the user. Transparent proxying however requires kernel support.
  透明代理服务用于简单化地去管理隐蔽代理防火墙后客户端用户任务,这就是透明代理技术产生的原由。
透明代理意味代理环节对于客户端不可见。当然TPROX透明代理功能需要系统核心的支持。

We have a 'REDIRECT' target, isn't that enough?
我们有了iptables的“redirect”target工具,已经足够了吗?
----------------------------------------------

Real transparent proxying requires the following three features from the IP stack of the computer\
 it is running on:
  实际的透明代理需具有以下三方面的功能特点:

1. Redirect sessions destined to the outer network to a local process using a packet filter rule.
    需要运用ip数据包过滤器(规则)将去住外部网络的ip会话报文重定向到本地进程(由本地诸如haproxy这样的相关进程再处理)。
2. Make it possible for a process to listen to connections on a foreign address.
    尽可以地让一个进程去监听联系着外部IP地址的联接。
3. Make it possible for a process to initiate a connection with a foreign address as a source.
    尽量让一个进程去发起如同拥有一个象客户端外部原ip地址的关联。
注:就是实现ip欺骗,使用代理过程对两次数据包的传输过程,如同没有代理环节的存在(数据包入出对比)。

Item #1 is usually provided by packet filtering packages like Netfilter/IPTables, IPFilter.
(yes, this is the REDIRECT target)
项目#1通常是由类似的Netfilter/IPTables、IPFilter的包过滤包提供。
(的确,tproxy就是iptables的一个REDIRECT target)
一位高手对代码的分析如下:
********************************
代码注释/*
 * Transparent proxy support for Linux/iptables
 *
 * Copyright (c) 2006-2007 BalaBit IT Ltd.
 * Author: Balazs Scheidler, Krisztian Kovacs
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */

#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <net/checksum.h>
#include <net/udp.h>
#include <net/inet_sock.h>
#include <linux/inetdevice.h>
#include <linux/netfilter/x_tables.h>
#include <linux/netfilter_ipv4/ip_tables.h>

#include <net/netfilter/ipv4/nf_defrag_ipv4.h>
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
#include <net/if_inet6.h>
#include <net/addrconf.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <net/netfilter/ipv6/nf_defrag_ipv6.h>
#endif

#include <net/netfilter/nf_tproxy_core.h>
#include <linux/netfilter/xt_TPROXY.h>

static inline __be32
tproxy_laddr4(struct sk_buff *skb, __be32 user_laddr, __be32 daddr)
{
struct in_device *indev;
__be32 laddr;
        if (user_laddr)
                return user_laddr;
        laddr = 0;
        rcu_read_lock();
        indev = __in_dev_get_rcu(skb->dev);
        for_primary_ifa(indev) {
laddr = ifa->ifa_local;
                break;
        } endfor_ifa(indev);
        rcu_read_unlock();
        
        return laddr ? laddr : daddr;
}
/*
   iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 50080
   参数说明:
laddr  为代理服务器的本地监听套接字的本地ip
lport  为代理服务器的本地监听套接字的本地port,50080
mark_value 为mark值
*/



static unsigned int
tproxy_tg4(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) {
pr_debug("TPROXY: packet is too short to contain a transport header, dropping\n");
return NF_DROP;
}

//根据数据包的内容,向tcp已建立的队列查找skb属于的struct sock
//如果客户端与代理服务器已经建立连接,该数据包属于的sock将存在

        /* 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(dev_net(skb->dev), iph->protocol,
  iph->saddr, iph->daddr,
  hp->source, hp->dest,
  skb->dev, NFT_LOOKUP_ESTABLISHED);

/* udp has no TCP_TIME_WAIT state, so we never enter here */
if (sk && sk->sk_state == TCP_TIME_WAIT) {
struct tcphdr _hdr, *hp;

hp = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(_hdr), &_hdr);
if (hp == NULL)
return NF_DROP;
if (hp->syn && !hp->rst && !hp->ack && !hp->fin) {
struct sock *sk2;
/* Hm.. we got a SYN to a TIME_WAIT socket, let's see if
* there's a listener on the redirected port
*/
sk2 = nf_tproxy_get_sock_v4(dev_net(skb->dev), iph->protocol,
   iph->saddr, tproxy_laddr4(skb, laddr, iph->daddr),
   hp->source, lport ? lport : hp->dest,
   skb->dev, NFT_LOOKUP_LISTENER);
if (sk2) {
/* yeah, there's one, let's kill the TIME_WAIT
* socket and redirect to the listener
*/
inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row);
inet_twsk_put(inet_twsk(sk));
sk = sk2;
}
}
} else if (!sk) {
//如果不存在sk,说明是客户端向代理服务器发出tcp syn的数据包
//此时,通过该函数,将该数据包的skb与代理服务器的本地监听套接字建立联系,即skb->sock = sock
//tproxy_laddr4(skb, laddr, iph->daddr) 根据tproxy target的传入参数struct xt_tproxy_target_info_v0,找到当前代理服务器端已经建立的监听socket

/* no there's no established connection, check if
* there's a listener on the redirected addr/port */
sk = nf_tproxy_get_sock_v4(dev_net(skb->dev), iph->protocol,
  iph->saddr, tproxy_laddr4(skb, laddr, iph->daddr),
  hp->source, lport ? lport : hp->dest,
  skb->dev, NFT_LOOKUP_LISTENER);
}
/* NOTE: assign_sock consumes our sk reference */
if (sk && nf_tproxy_assign_sock(skb, sk)) {
//运行至此,说明客户端已经与服务器端建立了三次握手,即sk存在;
//则通过nf_tproxy_assign_sock函数,将当前数据包的skb与代理服务器的监听socket建立联系,即skb->sock = sk
//最后,将数据包打上比较,待策略路由转发到loobackshang

/* 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("TPROXY: redirecting: proto %u %08x:%u -> %08x:%u, mark: %x\n",
iph->protocol, ntohl(iph->daddr), ntohs(hp->dest),
ntohl(laddr), ntohs(lport), skb->mark);
return NF_ACCEPT;
}

pr_debug("TPROXY: no socket, dropping: proto %u %08x:%u -> %08x:%u, mark: %x\n",
iph->protocol, ntohl(iph->daddr), ntohs(hp->dest),
ntohl(laddr), ntohs(lport), skb->mark);
return NF_DROP;
}

static unsigned int
tproxy_tg4_v0(struct sk_buff *skb, const struct xt_target_param *par)
{
const struct xt_tproxy_target_info_v0 *tgi = par->targinfo;

return tproxy_tg4(skb, tgi->laddr, tgi->lport, tgi->mark_mask, tgi->mark_value);
}


//tproxy具体的target函数
static unsigned int
tproxy_tg4_v1(struct sk_buff *skb, const struct xt_target_param *par)
{
const struct xt_tproxy_target_info_v1 *tgi = par->targinfo;//获得参数

return tproxy_tg4(skb, tgi->laddr.ip, tgi->lport, tgi->mark_mask, tgi->mark_value);//根据传入的参数,将所有流量代理到本地监听套接字上
}

#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)

static inline const struct in6_addr *
tproxy_laddr6(struct sk_buff *skb, const struct in6_addr *user_laddr, const struct in6_addr *daddr)
{
struct inet6_dev *indev;
struct inet6_ifaddr *ifa;
struct in6_addr *laddr;
        if (!ipv6_addr_any(user_laddr))
                return user_laddr;
        laddr = NULL;
        rcu_read_lock();
        indev = __in6_dev_get(skb->dev);
        if (indev && (ifa = indev->addr_list)) {
laddr = &ifa->addr;
}
        rcu_read_unlock();
        
        return laddr ? laddr : daddr;
}


static unsigned int
tproxy_tg6_v1(struct sk_buff *skb, const struct xt_target_param *par)
{
const struct ipv6hdr *iph = ipv6_hdr(skb);
const struct xt_tproxy_target_info_v1 *tgi = par->targinfo;
struct udphdr _hdr, *hp;
struct sock *sk;
        int thoff;
        int tproto;

        tproto = ipv6_find_hdr(skb, &thoff, -1, NULL);
        if (tproto < 0) {
pr_debug("TPROXY: Unable to find transport header in IPv6 packet, dropping\n");
return NF_DROP;
        }

hp = skb_header_pointer(skb, thoff, sizeof(_hdr), &_hdr);
if (hp == NULL) {
pr_debug("TPROXY: Unable to grab transport header contents in IPv6 packet, dropping\n");
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_v6(dev_net(skb->dev), tproto,
  &iph->saddr, &iph->daddr,
  hp->source, hp->dest,
  par->in, NFT_LOOKUP_ESTABLISHED);

/* udp has no TCP_TIME_WAIT state, so we never enter here */
if (sk && sk->sk_state == TCP_TIME_WAIT) {
struct tcphdr _hdr, *hp;

hp = skb_header_pointer(skb, thoff, sizeof(_hdr), &_hdr);
if (hp == NULL) {
pr_debug("TPROXY: Unable to grab TCP transport header contents in IPv6 packet, dropping\n");
return NF_DROP;
}

if (hp->syn && !hp->rst && !hp->ack && !hp->fin) {
struct sock *sk2;

/* Hm.. we got a SYN to a TIME_WAIT socket, let's see if
* there's a listener on the redirected port
*/
sk2 = nf_tproxy_get_sock_v6(dev_net(skb->dev), tproto,
   &iph->saddr, tproxy_laddr6(skb, &tgi->laddr.in6, &iph->daddr),
   hp->source, tgi->lport ? tgi->lport : hp->dest,
   par->in, NFT_LOOKUP_LISTENER);
if (sk2) {
/* yeah, there's one, let's kill the TIME_WAIT
* socket and redirect to the listener
*/
inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row);
inet_twsk_put(inet_twsk(sk));
sk = sk2;
}
}
} 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_v6(dev_net(skb->dev), tproto,
  &iph->saddr, tproxy_laddr6(skb, &tgi->laddr.in6, &iph->daddr),
  hp->source, tgi->lport ? tgi->lport : hp->dest,
  par->in, NFT_LOOKUP_LISTENER);
}
/* NOTE: assign_sock consumes our sk reference */
if (sk && nf_tproxy_assign_sock(skb, sk)) {
/* This should be in a separate target, but we don't do multiple
  targets on the same rule yet */
skb->mark = (skb->mark & ~tgi->mark_mask) ^ tgi->mark_value;

pr_debug("TPROXY: redirecting: proto %u %pI6:%u -> %pI6:%u, mark: %x\n",
tproto, &iph->saddr, ntohs(hp->dest),
&tgi->laddr.in6, ntohs(tgi->lport), skb->mark);
return NF_ACCEPT;
}

pr_debug("TPROXY: no socket, dropping: proto %u %pI6:%u -> %pI6:%u, mark: %x\n",
tproto, &iph->saddr, ntohs(hp->dest),
&tgi->laddr.in6, ntohs(tgi->lport), skb->mark);
return NF_DROP;
}
#endif


static bool tproxy_tg4_check(const struct xt_tgchk_param *par)
{
const struct ipt_ip *i = par->entryinfo;

if ((i->proto == IPPROTO_TCP || i->proto == IPPROTO_UDP)
   && !(i->invflags & IPT_INV_PROTO))
return true;
pr_info("xt_TPROXY: Can be used only in combination with "
"either -p tcp or -p udp\n");
return false;
}

static bool tproxy_tg6_check(const struct xt_tgchk_param *par)
{
const struct ip6t_ip6 *i = par->entryinfo;

if ((i->proto == IPPROTO_TCP || i->proto == IPPROTO_UDP)
   && !(i->flags & IP6T_INV_PROTO))
return true;
pr_info("xt_TPROXY: Can be used only in combination with "
"either -p tcp or -p udp\n");
return false;
}

static struct xt_target tproxy_tg_reg[] __read_mostly = {
{
.name = "TPROXY",
.family = NFPROTO_IPV4,
.table = "mangle",
.target = tproxy_tg4_v0,//tproxy的target执行钩子
.revision       = 0,
.targetsize = sizeof(struct xt_tproxy_target_info_v0),//struct xt_tproxy_target_info_v0 为向target钩子函数传入的参数
.checkentry = tproxy_tg4_check,
.hooks = 1 << NF_INET_PRE_ROUTING,//钩子点
.me = THIS_MODULE,
},
{
.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,
},
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
{
.name = "TPROXY",
.family = NFPROTO_IPV6,
.table = "mangle",
.target = tproxy_tg6_v1,
.revision       = 1,
.targetsize = sizeof(struct xt_tproxy_target_info_v1),
.checkentry = tproxy_tg6_check,
.hooks = 1 << NF_INET_PRE_ROUTING,
.me = THIS_MODULE,
},
#endif

};

//tproxy target初始化,向iptables中target注册
static int __init tproxy_tg_init(void)
{
//tproxy是需要先对数据包进行重组的
nf_defrag_ipv4_enable();
nf_defrag_ipv6_enable();
return xt_register_targets(tproxy_tg_reg, ARRAY_SIZE(tproxy_tg_reg));
}

static void __exit tproxy_tg_exit(void)
{
xt_unregister_targets(tproxy_tg_reg, ARRAY_SIZE(tproxy_tg_reg));
}

module_init(tproxy_tg_init);
module_exit(tproxy_tg_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Krisztian Kovacs");
MODULE_DESCRIPTION("Netfilter transparent proxy (TPROXY) target module.");
MODULE_ALIAS("ipt_TPROXY");
MODULE_ALIAS("ip6t_TPROXY");
  评论这张
 
阅读(2592)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017