Linux kernel 的網路參數 rp_filter

這篇來記錄有關 Linux kernel 的 network 參數。

ip-sysctl

為逆向路徑過濾 (Reverse Path Filtering),其作用為為過濾反向不通的封包,將其丟棄。 而原理為由 NIC1 (network interface card) 進來的封包,reverse path filtering 模塊會將其封包的源地址(source ip)與目標地址(destination ip)做對調,然後再路由表中查找,如果出去的介面是 NIC1 則通過;反之不是 NIC1 則丟棄該封包。

rp_filter 的主要作用是檢查接收到的資料包的來源位址是否是通過正確的網路介面到達的。如果資料包的來源位址與路由表中的反向路徑不匹配,rp_filter 就會丟棄該資料包,從而防止偽造的 IP 資料包進入系統。

  1. 當系統接收到一個資料包時,rp_filter 會檢查該資料包的來源位址。
  2. 它根據路由表計算,若系統要將一個資料包發送到該來源位址,會使用哪個網路介面。
  3. 如果接收到的資料包實際上是通過另一個介面到達的,而不是路由表中計算出的介面,rp_filter 會認為這個資料包是偽造的,並將其丟棄。

可以從 ip-sysctl 文件中找到參數的用法。其中該參數為整數型,其值包括了 0:不做來源驗證;1:嚴謹認證模式;2:寬鬆認證模式。

rp_filter - INTEGER
    0 - No source validation.
    1 - Strict mode as defined in RFC3704 Strict Reverse Path
        Each incoming packet is tested against the FIB and if the interface
        is not the best reverse path the packet check will fail.
        By default failed packets are discarded.
    2 - Loose mode as defined in RFC3704 Loose Reverse Path
        Each incoming packet's source address is also tested against the FIB
        and if the source address is not reachable via any interface
        the packet check will fail.

Current recommended practice in RFC3704 is to enable strict mode
    to prevent IP spoofing from DDos attacks. If using asymmetric routing
    or other complicated routing, then loose mode is recommended.

The max value from conf/{all,interface}/rp_filter is used
    when doing source validation on the {interface}.

Default value is 0. Note that some distributions enable it
    in startup scripts.

可以上網找到原始碼 fib_frontend.c,這邊來看一下實作方法。

 1/* Ignore rp_filter for packets protected by IPsec. */
 2int fib_validate_source(struct sk_buff *skb, __be32 src, __be32 dst,
 3            u8 tos, int oif, struct net_device *dev,
 4            struct in_device *idev, u32 *itag)
 5{
 6    /* 是否啟用反向封包過濾功能,這邊注意如果封包受 IPSec 保護則忽略反向封包過濾功能 */
 7    int r = secpath_exists(skb) ? 0 : IN_DEV_RPFILTER(idev);
 8    struct net *net = dev_net(dev);
 9
10    if (!r && !fib_num_tclassid_users(net) &&
11        (dev->ifindex != oif || !IN_DEV_TX_REDIRECTS(idev))) {
12        /* 本地源的封包不執行反向封包過濾功能 */
13        if (IN_DEV_ACCEPT_LOCAL(idev))
14            goto ok;
15        /* with custom local routes in place, checking local addresses
16        * only will be too optimistic, with custom rules, checking
17        * local addresses only can be too strict, e.g. due to vrf
18        */
19        if (net->ipv4.fib_has_custom_local_routes ||
20            fib4_has_custom_rules(net))
21            goto full_check;
22        if (inet_lookup_ifaddr_rcu(net, src))
23            return -EINVAL;
24
25ok:
26        *itag = 0;
27        return 0;
28    }
29
30full_check:
31    return __fib_validate_source(skb, src, dst, tos, oif, dev, r, idev, itag);
32}
...
c
 1static int __fib_validate_source(struct sk_buff *skb, __be32 src, __be32 dst,
 2                u8 tos, int oif, struct net_device *dev,
 3                int rpf, struct in_device *idev, u32 *itag)
 4{
 5    struct net *net = dev_net(dev);
 6    struct flow_keys flkeys;
 7    int ret, no_addr;
 8    struct fib_result res;
 9    struct flowi4 fl4;
10    bool dev_match;
11
12    fl4.flowi4_oif = 0;
13    fl4.flowi4_iif = l3mdev_master_ifindex_rcu(dev);
14    if (!fl4.flowi4_iif)
15        fl4.flowi4_iif = oif ? : LOOPBACK_IFINDEX;
16    /* 可以看到這邊做反轉了 */
17    fl4.daddr = src;
18    fl4.saddr = dst;
19    fl4.flowi4_tos = tos;
20    fl4.flowi4_scope = RT_SCOPE_UNIVERSE;
21    fl4.flowi4_tun_key.tun_id = 0;
22    fl4.flowi4_flags = 0;
23    fl4.flowi4_uid = sock_net_uid(net, NULL);
24    fl4.flowi4_multipath_hash = 0;
25
26    no_addr = idev->ifa_list == NULL;
27
28    fl4.flowi4_mark = IN_DEV_SRC_VMARK(idev) ? skb->mark : 0;
29    if (!fib4_rules_early_flow_dissect(net, skb, &fl4, &flkeys)) {
30        fl4.flowi4_proto = 0;
31        fl4.fl4_sport = 0;
32        fl4.fl4_dport = 0;
33    }
34
35    if (fib_lookup(net, &fl4, &res, 0))
36        goto last_resort;
37    if (res.type != RTN_UNICAST &&
38        (res.type != RTN_LOCAL || !IN_DEV_ACCEPT_LOCAL(idev)))
39        goto e_inval;
40    fib_combine_itag(itag, &res);
41
42    dev_match = fib_info_nh_uses_dev(res.fi, dev);
43    /* This is not common, loopback packets retain skb_dst so normally they
44    * would not even hit this slow path.
45    */
46    dev_match = dev_match || (res.type == RTN_LOCAL &&
47                dev == net->loopback_dev);
48    if (dev_match) {
49        ret = FIB_RES_NHC(res)->nhc_scope >= RT_SCOPE_HOST;
50        return ret;
51    }
52    if (no_addr)
53        goto last_resort;
54    if (rpf == 1)
55        goto e_rpf;
56    fl4.flowi4_oif = dev->ifindex;
57
58    ret = 0;
59    if (fib_lookup(net, &fl4, &res, FIB_LOOKUP_IGNORE_LINKSTATE) == 0) {
60        if (res.type == RTN_UNICAST)
61            ret = FIB_RES_NHC(res)->nhc_scope >= RT_SCOPE_HOST;
62    }
63    return ret;
64
65last_resort:
66    if (rpf)
67        goto e_rpf;
68    *itag = 0;
69    return 0;
70
71e_inval:
72    return -EINVAL;
73e_rpf:
74    return -EXDEV;
75}
...
c

參數:

  • struct sk_buff - socket buffer
  • __be32 src - source
  • __be32 dst - destination
  • struct net_device - 網路結構體
  • struct in_device - 網路結構體
  • saddr - start address
  • daddr - destination address

科普:

  • __bet32 - le 與 be 分別表示 little endian 和 big endian
  • u8、u32 - 表示無符號 char 字符類型

當前RFC3704文檔建議使用嚴謹認證模式。如果網路是非對稱網路或複雜的網路 rp_filter 建議配置為 2,做寬鬆認證。 而 rp_filter 配置作用通常為下列:

  1. 減少 DDoS 攻擊,注意是減少不是防止
  2. 防止 IP Spoofing
  1. 只有一個網路介面,且無特殊路由需求

    • 如果系統只有一個網路介面,且路由配置相對簡單(例如,所有流量都通過該介面進出),那麼來源 IP 位址欺騙的風險較低。
    • 在這種情況下,開啟 rp_filter 的作用有限,因為所有資料包都只能通過這唯一的網路介面進出。
  2. 只有一個網路介面,但存在潛在的安全威脅

    • 即使系統只有一個網路介面,攻擊者仍可能試圖通過偽造來源 IP 位址向系統發送惡意資料包。
    • 開啟 rp_filter 可以增加一層安全性,確保接收到的資料包符合路由規則,從而防止這類攻擊。
  3. 未來可能擴展到多網路介面

    • 如果系統未來可能會增加更多網路介面,建議養成良好的安全習慣,提前配置 rp_filter,以避免多介面時的安全漏洞。