socket有一个IP_TRANSPARENT选项,其含义就是可以使一个服务器程序侦听所有的IP地址,哪怕不是本机的IP地址,这个特性在实现透明代理服务器时十分有用,而其使用也很简单:
int opt =1;
setsockopt(server_socket,SOL_IP, IP_TRANSPARENT,&opt,sizeof(opt));

0.导引:TCP绑定0.0.0.0的情况

TCP可以绑定0.0.0.0,这个都知道,那么到底用哪一个地址何时确定呢?答案是“根据连接源的地址反向做路由查找后确定的”。如果有一个地址A连接该服务器,那么在服务器收到syn后,就会查找目的地址为A的路由,进而确定源地址,然而如果不设置IP_TRANSPARENT选项,则这个被连接的地址必须在local路由表中被找到,否则一切都免谈。
        因此如果我有一个没有设置IP_TRANSPARENT选项的TCP服务器绑定了0.0.0.0这个地址,端口绑定到80,我想这个服务器截获经过此地访问56.56.56.56:80的流量,怎么办?很简单,知道了TCP源地址选择的原理之后,我们只需要设置下面的路由即可:
ip route add local 56.56.56.56 dev lo tab local
这样一来,所有访问56.56.56.56这个地址的流量在经过本机时,都会进入local_in,因为它在local表中找到了路由。但是本机没有56.56.56.56这个地址,本机的80端口服务器回复syn-ack的时候,执行反向路由查找,在local表中找到了56.56.56.56的路由,进而成功返回,最终连接成功在A和56.56.56.56:80之间建立。
        然而思考一下,以上虽然圆满完成了任务,但是如果有N多个目的地址,岂不是要设置N多地址在local路由表?有没有什么办法只设置很少的规则就能截获所有到达80端口的流量呢?有的,那就是在代理服务器的socket上设置IP_TRANSPARENT选项。

1.总体配置

如果你想操作路由查找的过程,还是要使用策略路由。针对上述的需求,有以下配置:

a).识别需要截获(代理)的流量

可选配置1:仅仅针对网卡
ip rule add iif $流量进入的网卡 tab proxy
可选配置2:针对更复杂的五元组信息
iptables -t mangle -A PREROUTING (为特定端口的流量打上mark)
ip rule add fwmark (上述mark) iif $流量进入的网卡 tab proxy

b).为策略路由表增加路由表项

ip route add local 0.0.0.0/0(或者直接写default) dev lo tab proxy
注意:增加路由表项的时候,一定要注意local这个type,这是一个路由类型,凡这个类型的路由项,一旦有流量被匹配,所有的流量统统送到本地进行处理。
    值得注意的是,上述配置中并不需要将路由表项添加到local表中,这是因为我们设置了IP_TRANSPARENT选项,具体的限制请参考Linux内核代码net/ipv4/route.c中的ip_route_output_slow函数:

if (oldflp->oif == 0     && (ipv4_is_multicast(oldflp->fl4_dst) ||     oldflp->fl4_dst == htonl(0xFFFFFFFF))) {     dev_out = ip_dev_find(net, oldflp->fl4_src); //强制在local表中查找     ... } if (!(oldflp->flags & FLOWI_FLAG_ANYSRC)) { //如果设置了IP_TRANSPARENT选项... ... }

2.综上

综上所述,配置完成了。我们可以看到,Linux对路由的处理是怎样进行的,如果想深入地理解它,还是需要深入了解一下iproute2工具,它提供了一个理解问题的表象。然而正如Linux其它子系统总是为发生一些让人费解的行为一样,网络子系统这类行为更多,一些是遵循了RFC或者IEEE等相关标准的建议,另一些则是Linux自身的实现技巧,对于本文的情况,有以下的主题:

a.你配置的路由nexthop并不一定会被采用

如果你的本机eth1的地址是4.4.4.1/24,你想将过路流量导入到本机应用层,一个很直观但是不可用的配置是:
ip route add 1.2.3.4 via 4.4.4.1
然而当去往1.2.3.4的流量经过本机的时候,使用route -C查看cache,发现其默认网关还是本机的默认网关,并不是你指示的4.4.4.1,如果不理解这一点,还是需要看一下代码是如何执行的。当去往1.2.3.4的流量经过时,很显然会匹配到上述配置的路由,然而内核在真正使用该路由前需要对该路由进行一些微调,最终生成的路由cache则是真正可用的路由项,起初匹配完成后会将路由cache项的rt_gateway直接初始化为目的地址,也就是1.2.3.4:

rth->rt_gateway = fl->fl4_dst;

然后在rt_set_nexthop中会判断是继续使用目的地址直接转发还是使用你在路由表中配置的那个默认网关:

if (FIB_RES_GW(*res) &&     FIB_RES_NH(*res).nh_scope == RT_SCOPE_LINK)     rt->rt_gateway = FIB_RES_GW(*res);

很显然,只有当FIB_RES_NH(*res).nh_scope == RT_SCOPE_LINK为真时,才会使用你配置的默认网关4.4.4.1。
        接下来就是让FIB_RES_NH(*res).nh_scope == RT_SCOPE_LINK为真,于是设置下列路由表项:
ip route add 1.2.3.4/32 scope global via 4.4.4.1 dev eth1 onlink
scope为global是被迫的,因为这是规定,下一跳必须比目的地更近才可以,因此其scope一定要比下一跳的scope更小。这样还是不行,因为还有一个限制,那就是你的下一跳网关的地址必须是unicast的:

if (nh->nh_flags&RTNH_F_ONLINK) {     struct net_device *dev;     if (cfg->fc_scope >= RT_SCOPE_LINK)         return -EINVAL;     if (inet_addr_type(net, nh->nh_gw) != RTN_UNICAST)         return -EINVAL;     if ((dev = __dev_get_by_index(net, nh->nh_oif)) == NULL)         return -ENODEV;     if (!(dev->flags&IFF_UP))         return -ENETDOWN;     nh->nh_dev = dev;     dev_hold(dev);     nh->nh_scope = RT_SCOPE_LINK;     return 0; }

由于inet_addr_type返回unicast需要保证地址不在local表中命中:

local_table = fib_get_table(net, RT_TABLE_LOCAL); if (local_table) {     ret = RTN_UNICAST;     if (!local_table->tb_lookup(local_table, &fl, &res)) {         if (!dev || dev == res.fi->fib_dev)             ret = res.type;         fib_res_put(&res);     } }

那么下一步则将4.4.4.1这条路由从local表中删除:
ip rou del 4.4.4.1/24 tab local
OK,这下可以了,然而却在本机发送数据到1.2.3.4的时候失败了,这是因为源地址选择时出了问题,此时4.4.4.1这个地址已经不在local中了。上述失败以telnet 1.2.3.4 6666为例,在TCP进行connect的时候,在真正进入路由模块之前,首先要调用ip_route_connect确定源地址,然而在该connect流量进入路由模块的时候,源地址已经确定了,为4.4.4.1,然而该地址已经不在local路由表中存在,可是output路由查找在确定了源地址的情况下需要源地址在local表中或者源socket设置了IP_TRANSPARENT选项,我们知道标准的telnet是没有这个选项的,因此会返回:
telnet: Unable to connect to remote host: Invalid argument
这个错误。因此需要如下:
ip route add 1.2.3.4/32 scope global via 4.4.4.1 dev eth1 onlink src 7.7.7.7
其中7.7.7.7是临时添加到eth1上的另外一个地址,显然7.7.7.7是在local表中存在的。
        看到这里,其实还有更简单的方式,前面的那段if (nh->nh_flags&RTNH_F_ONLINK)代码是fib_check_nh的一部分,该部分执行的是设置了onlink标志的逻辑,如果不设置呢?要知道我们要做的只是:
1.inet_addr_type(net, nh->nh_gw) == RTN_UNICAST
2.cfg->fc_scope < RT_SCOPE_LINK

于是看一下fib_check_nh的第二部分:

struct flowi fl = {         .nl_u = {             .ip4_u = {                 .daddr = nh->nh_gw,                 .scope = cfg->fc_scope + 1,             },         },         .oif = nh->nh_oif,     };     if (fl.fl4_scope < RT_SCOPE_LINK)         fl.fl4_scope = RT_SCOPE_LINK;     //若到达这里,如果gw是本机地址,则会在local表中命中     //因此你仍然需要将4.4.4.1从local表中删除,保证res的scope为RTN_UNICAST     if ((err = fib_lookup(net, &fl, &res)) != 0)         return err;     }     err = -EINVAL;     if (res.type != RTN_UNICAST && res.type != RTN_LOCAL)         goto out;     nh->nh_scope = res.scope;     ...

成功,不再报错,然而数据也没有到达应用层。

b.内核不会为下一跳网关再次进行路由查找

废了这么大的力气终于将流量导入4.4.4.1了,然而真的能发往本机吗?如果你试验一下,将会发现内核直接在4.4.4.1的那个网卡上arp这个4.4.4.1地址:
ARP, Request who-has 4.4.4.1 tell 4.4.4.1, length 28
内核从来不会为你的默认网关再次进行路由查找,而仅仅根据路由的scope以及下一跳网关的scope直接进行地址解析,在本例中4.4.4.1是link的,那么很显然会直接arp,因为内核相信4.4.4.1在同链路层(link)的其它地方。

c.路由工作在网络层

虽然我们都不想使用复杂的iptables,使用纯路由被看作是一种妙招,路由的blackhole/unreachable以及任意引导任意流量被看作是网络的必杀技。然而路由仅仅工作在网络层,如果你需要更高层的参数参与过滤和引导,你不得不使用其它的手段,在Linux上使用iptables工具可以完成。当然并不是说你必须使用其防火墙和NAT功能,哪怕打个mark不也很好吗?iptables是一个工具链,它可以通过mark和策略路由交互。

本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1268969

socket的IP_TRANSPARENT选项实现代理相关推荐

  1. socket的IP TRANSPARENT选项实现代理

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴!      ...

  2. 当IE浏览器设置了代理,改变不了的时候,就需要在右上角设备哪里--找到安全--Inprivate---点击--然后就可以设置了-intenet选项去掉代理了

    当IE浏览器设置了代理,改变不了的时候,就需要在右上角设备哪里--找到安全--Inprivate---点击--然后就可以设置了-intenet选项去掉代理了 当IE浏览器设置了代理,改变不了的时候,就 ...

  3. java网络编程Socket中SO_LINGER选项的用法解读

    http://blog.sina.com.cn/s/blog_6b1990eb0101171o.html 1:设置该选项: public void setSoLinger(boolean on, in ...

  4. java setsolinger_java socket 的参数选项解读(转)

    在MulticastSocket的源代码里有设置多播的方法: public void setInterface(InetAddress inf) throwsSocketException { if( ...

  5. SOCKET - 实现任意 HTTPS 站点代理, 支持篡改内容

    * 本文内容代码执行需要管理员权限 主要难点 将用户访问请求重定向到劫持服务器, 如何获取 https 传输内容并解密成明文. 修改 https 内容后重新打包并保证浏览器可以正常打开. 难点一 重定 ...

  6. tpproxy-tcp透明代理

    tcp transparent proxy Background  最近有个需求,需要在路由器设备中截获数据包,从而实现中转.按照下面的拓补,说明.我们需要在主机h2上截获h1发往h3的TCP协议包. ...

  7. TPROXY与ip_conntrack

    <socket的IP_TRANSPARENT选项实现代理>中,介绍了IP_TRANSPARENT可以使得保留源IP地址的方法,但是使用了ip_conntrack,我们知道ip_conntr ...

  8. iptables:tproxy做透明代理

    什么是透明代理 客户端向真实服务器发起连接,代理机冒充服务器与客户端建立连接,并以客户端ip与真实服务器建立连接进行代理转发.因此对于客户端与服务器来说,代理机都是透明的. 如何建立透明代理 本地so ...

  9. Linux路由应用-使用策略路由实现访问控制

    引: 一般而言,访问控制并不是路由模块完成的,而是防火墙的职责,如果你使用Linux的,这是iptables的职责.然而有时候,特别是在策略很多的情况下,使用iptables会极大降低网络性能,这是N ...

最新文章

  1. 故障模块名称kernelbase.dll_固定资产管理系统_资产分类名称(通讯导航有线电及测量仪器篇)...
  2. java stack 从1.5开始?_java数据结构与算法之栈(Stack)设计与实现
  3. 北斗导航 | 北斗系统信息处理创新技术(学术PPT分享附视频)
  4. 数据结构之查找算法:B树
  5. Java工程师的进阶之路-Kafka篇(一)
  6. Android开发笔记(四十九)异步任务处理AsyncTask
  7. Mac彻底卸载搜狗输入法
  8. 利用Web of Science创建引文跟踪、检索词跟踪
  9. Zoox 的自动驾驶汽车方法
  10. 多层陶瓷电容器用处_具有综合优异电卡性能的无铅多层陶瓷电容器研究新进展...
  11. journalctl命令
  12. Golang Go语言 安装包 下载 官方包 与 Golang 中文网
  13. 无限循环滚动代码阿里巴巴国际站店铺装修代码底图滚动黑色半透明显示效果自定义内容装修代码全屏显示
  14. linux 远程启动WebLogic
  15. android手机操作手册,数字填图(Android版)操作手册.pdf
  16. python 拦截windows弹窗广告_win10怎么阻止弹窗广告拦截功能的方法
  17. 两种将pdf转换成jpg格式的简单方法
  18. 解决微信小程序自定义tabbar跳转页面图标闪动问题
  19. json能传数字,不能传字符串
  20. 如何写好技术文档——来自Google十多年的文档经验

热门文章

  1. 关于ASP.NET导出Excel表格的个人总结归纳
  2. linux 静态库 fpic,ffmpeg使用fPIC静态库解决记录
  3. DWORD类型与16进制字符串之间的相互转换
  4. Vue打包部署到服务器-找不到静态资源404错误
  5. lisp创建PaletteSet_CAD二次开发(.NET)之PaletteSet和Palette
  6. 名帖230 张雨 行书《独游龙井方圆庵卷》
  7. 第3章 软件测试方法--基于输入域的测试方法(等价类、边界值)
  8. 利用java连wifi_wifi 连接
  9. 随机变量及其分布函数
  10. 用友 LRP计划维护视图