天天看點

綁定非本機位址與透明代理

核心代碼

核心中對綁定非本地位址的相關判斷代碼,位于net/ipv4/af_inet.c中:

int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
	chk_addr_ret = inet_addr_type(net, addr->sin_addr.s_addr);
	
    if (!net->ipv4_sysctl_ip_nonlocal_bind &&
        !(inet->freebind || inet->transparent) &&
        addr->sin_addr.s_addr != htonl(INADDR_ANY) &&
        chk_addr_ret != RTN_LOCAL &&
        chk_addr_ret != RTN_MULTICAST &&
        chk_addr_ret != RTN_BROADCAST)
        goto out;
}
           

由代碼可見,需要滿足以下三個條件中的一個,才能綁定成功:

1)此socket所屬的net namespace設定了全局的ipv4_sysctl_ip_nonlocal_bind;

2)此socket設定了IP_FREEBIND選項;

3)此socket設定了IP_TRANSPARENT選項;

條件1可通過proc檔案設定,條件2和3可通過setsockopt設定:

echo 1 > /proc/sys/net/ipv4/ip_nonlocal_bind

int    on = 1;

setsockopt(sock, SOL_IP, IP_FREEBIND, (char *)&on, sizeof(on))

setsockopt(sock, SOL_IP, IP_TRANSPARENT, (char *)&on, sizeof(on))

三者的差別

ip_nonlocal_bind和freebind功能相同,綁定位址的時候不要求本地接口已經獲得該位址,但是在随後收發封包時還是需要該位址,隻是可以提前進行位址綁定。二者差別僅在于作用範圍不同。

IP_TRANSPARENT不僅是允許綁定非本地位址,更重要的是與netfilter一起實作透明代理功能。

透明代理

實作透明代理的前提是,1)本機能夠接收目的位址非本機的資料包,2)和發送源位址非本地位址的資料包。

a) 接收

利用transparent選項,本機已經建立了socket監聽用戶端到伺服器的連接配接,需要iptables在IP層把外出流量導入到本機。

iptables -t mangle -N DIVERT
iptables -t mangle -A PREROUTING -p tcp -m socket --transparent -j DIVERT
iptables -t mangle -A DIVERT -j MARK --set-mark 1
iptables -t mangle -A DIVERT -j ACCEPT
           

在PREROUTING hook點上,提前檢查是否有本機socket在監聽此連接配接,transparent選項忽略未設定此選項的socket。另外一個有用的選項--nowildcard,可用于将此連接配接關聯到監聽在INADDR_ANY的socket上。如果找到socket,設定mark等于1,路由到本機。

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

b)發送

主要涉及到查找出口路由函數,其中要對源位址做檢查,非本機位址導緻查找失敗,代碼檔案net/ipv4/route.c:

struct rtable *__ip_route_output_key(struct net *net, struct flowi4 *fl4)
{
    if (fl4->saddr) {
		...
        if (!(fl4->flowi4_flags & FLOWI_FLAG_ANYSRC)) {
            /* It is equivalent to inet_addr_type(saddr) == RTN_LOCAL */
            if (!__ip_dev_find(net, fl4->saddr, false))
                goto out;
        }
    }
}
           

針對此問題,IP_TRANSPARENT選項關閉了源位址檢查include/net/inet_sock.h:

static inline __u8 inet_sk_flowi_flags(const struct sock *sk)
{
    __u8 flags = 0;

    if (inet_sk(sk)->transparent || inet_sk(sk)->hdrincl)
        flags |= FLOWI_FLAG_ANYSRC;
    return flags;
}
           

c)重定向

利用TPROXY目标,将目的端口為80的tcp連接配接重定向到本機監聽在192.168.1.1:50080上的sock,不改變資料包的内容,即透明模式。

iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-ip 192.168.1.1 --on-port 50080
           

另外一種改變資料包目的位址的重定向為nat方式,如下:

iptables -t nat -N MY_HTTP				                #在nat表上建立名為MY_HTTP自定義鍊 
iptables -t nat -p tcp -A MY_HTTP -j REDIRECT --to-ports 50080	        #将進入MY_HTTP鍊的資料包端口重定向到50080上 
iptables -t nat -N MY_NAT				                #在nat表上建立名為MY_NAT自定義鍊 
iptables -t nat -A PREROUTING -p tcp -j MY_NAT			        #将MY_NAT加入到PREROUTING鍊後 
iptables -t nat -A MY_NAT -p tcp -m multiport --dports 80 -j MY_HTTP	#在端口為80時執行MY_HTTP鍊 
           

核心版本

Linux-3.10.0