IPv6 Prohibit路由

如下命令添加Prohibit路由项,禁止路由到3301::/64网络的流量。

# ip -6 route add prohibit 3301::/64
#
# ip -6 route
::1 dev lo proto kernel metric 256 pref medium
prohibit 3301::/64 dev lo metric 1024 pref medium

Prohibit路由添加

IP命令下发到内核的Prohibit路由项,指定类型rtm_type为RTN_PROHIBIT,内核中将其记录为标志位RTF_REJECT。

static int rtm_to_fib6_config(struct sk_buff *skb, struct nlmsghdr *nlh,
                  struct fib6_config *cfg, struct netlink_ext_ack *extack)
{
    struct rtmsg *rtm;
    struct nlattr *tb[RTA_MAX+1];

    err = nlmsg_parse_deprecated(nlh, sizeof(*rtm), tb, RTA_MAX, rtm_ipv6_policy, extack);
    if (err < 0)
        goto errout;

    err = -EINVAL;
    rtm = nlmsg_data(nlh);
    *cfg = (struct fib6_config){
        .fc_table = rtm->rtm_table,
        .fc_dst_len = rtm->rtm_dst_len,
        .fc_src_len = rtm->rtm_src_len,
        .fc_flags = RTF_UP,
        .fc_protocol = rtm->rtm_protocol,
        .fc_type = rtm->rtm_type,
        .fc_nlinfo.portid = NETLINK_CB(skb).portid,
        .fc_nlinfo.nlh = nlh,
        .fc_nlinfo.nl_net = sock_net(skb->sk),
    };

    if (rtm->rtm_type == RTN_UNREACHABLE ||
        rtm->rtm_type == RTN_BLACKHOLE ||
        rtm->rtm_type == RTN_PROHIBIT ||
        rtm->rtm_type == RTN_THROW)
        cfg->fc_flags |= RTF_REJECT;

函数ip6_route_info_create根据以上解析IP命令参数得到的配置结构fib6_config来生成路由信息fib6_info。函数fib6_is_reject判断是否配置了RTF_REJECT标志,如果为真,将路由信息结构fib6_info中的标志成员设置两个标志RTF_REJECT和RTF_NONEXTHOP。

static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
                          gfp_t gfp_flags, struct netlink_ext_ack *extack)
{
    struct net *net = cfg->fc_nlinfo.nl_net;
    struct fib6_info *rt = NULL;
    struct nexthop *nh = NULL;
    struct fib6_nh *fib6_nh;

    if (nh) {
        ...
    } else {
        err = fib6_nh_init(net, rt->fib6_nh, cfg, gfp_flags, extack);
        if (err)
            goto out;

        fib6_nh = rt->fib6_nh;

        /* We cannot add true routes via loopback here, they would
         * result in kernel looping; promote them to reject routes
         */
        addr_type = ipv6_addr_type(&cfg->fc_dst);
        if (fib6_is_reject(cfg->fc_flags, rt->fib6_nh->fib_nh_dev, addr_type))
            rt->fib6_flags = RTF_REJECT | RTF_NONEXTHOP;
    }

在函数fib6_nh_init中,将Prohibit路由的设备设置为回环接口设备(lo)。

int fib6_nh_init(struct net *net, struct fib6_nh *fib6_nh,
         struct fib6_config *cfg, gfp_t gfp_flags, struct netlink_ext_ack *extack)
{
    struct net_device *dev = NULL;
    struct inet6_dev *idev = NULL;

    /* We cannot add true routes via loopback here,
     * they would result in kernel looping; promote them to reject routes
     */
    addr_type = ipv6_addr_type(&cfg->fc_dst);
    if (fib6_is_reject(cfg->fc_flags, dev, addr_type)) {
        /* hold loopback dev/idev if we haven't done so. */
        if (dev != net->loopback_dev) {
            if (dev) {
                dev_put(dev);
                in6_dev_put(idev);
            }
            dev = net->loopback_dev;
            dev_hold(dev);
            idev = in6_dev_get(dev);
            if (!idev) {
                err = -ENODEV;
                goto out;
            }
        }
        goto pcpu_alloc;
    }
    if (cfg->fc_flags & RTF_GATEWAY) {
        err = ip6_validate_gw(net, cfg, &dev, &idev, extack);
        if (err)
            goto out;

        fib6_nh->fib_nh_gw6 = cfg->fc_gateway;
        fib6_nh->fib_nh_gw_family = AF_INET6;

对于非Prohibit的正常路由,如果配置了网关RTF_GATEWAY,由函数ip6_validate_gw验证网关合法性。注意,对于prohibit路由,以上函数中fib6_is_reject返回真,不会进入网关检查这一步。

static int ip6_validate_gw(struct net *net, struct fib6_config *cfg,
               struct net_device **_dev, struct inet6_dev **idev,
               struct netlink_ext_ack *extack)
{
        if (cfg->fc_flags & RTNH_F_ONLINK)
            err = ip6_route_check_nh_onlink(net, cfg, dev, extack);
        else
            err = ip6_route_check_nh(net, cfg, _dev, idev);

        rcu_read_unlock();

        if (err)
            goto out;

例如以上的调用函数ip6_route_check_nh,如果到达网关地址的路由设置了拒绝标志RTF_REJECT,返回主机不可达错误(EHOSTUNREACH)。对于函数ip6_route_check_nh_onlink,其执行同样的检测,但是返回的错误值为EINVAL。

static int ip6_route_check_nh(struct net *net, struct fib6_config *cfg, struct net_device **_dev, struct inet6_dev **idev)
{
    const struct in6_addr *gw_addr = &cfg->fc_gateway;
    struct net_device *dev = _dev ? *_dev : NULL;

    if (cfg->fc_table) {
        err = ip6_nh_lookup_table(net, cfg, gw_addr, cfg->fc_table, flags, &res);
        /* gw_addr can not require a gateway or resolve to a reject
         * route. If a device is given, it must match the result.
         */
        if (err || res.fib6_flags & RTF_REJECT ||
            res.nh->fib_nh_gw_family || (dev && dev != res.nh->fib_nh_dev))
            err = -EHOSTUNREACH;
    }

路由查询

如果查询到的路由项设置了RTF_REJECT标志,跳转到函数ip6_create_rt_rcu中创建路由缓存。

INDIRECT_CALLABLE_SCOPE struct rt6_info *ip6_pol_route_lookup(struct net *net, ...)
{
    struct fib6_result res = {};
    struct fib6_node *fn;
    struct rt6_info *rt;

    fn = fib6_node_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr);
restart:
    res.f6i = rcu_dereference(fn->leaf);
    if (!res.f6i)
        res.f6i = net->ipv6.fib6_null_entry;
    else
        rt6_device_match(net, &res, &fl6->saddr, fl6->flowi6_oif, flags);

    if (res.f6i == net->ipv6.fib6_null_entry) {
        ...
        goto out;
    } else if (res.fib6_flags & RTF_REJECT) {
        goto do_create;
    }
    fib6_select_path(net, &res, fl6, fl6->flowi6_oif, fl6->flowi6_oif != 0, skb, flags);

    /* Search through exception table */
    rt = rt6_find_cached_rt(&res, &fl6->daddr, &fl6->saddr);
    if (rt) {
        if (ip6_hold_safe(net, &rt))
            dst_use_noref(&rt->dst, jiffies);
    } else {
do_create:
        rt = ip6_create_rt_rcu(&res);
    }

Prohibit路由缓存

Prohibit路由缓存的output和input函数分别赋值为ip6_pkt_prohibit_out和ip6_pkt_prohibit。这两个函数都将回复类型为ICMPV6_DEST_UNREACH,code码为ICMPV6_ADM_PROHIBITED的ICMPv6报文,并且将接收到的数据报文丢弃。

static void ip6_rt_init_dst_reject(struct rt6_info *rt, u8 fib6_type)
{
    rt->dst.error = ip6_rt_type_to_error(fib6_type);

    switch (fib6_type) {
    case RTN_PROHIBIT:
        rt->dst.output = ip6_pkt_prohibit_out;
        rt->dst.input = ip6_pkt_prohibit;
        break;

static void ip6_rt_init_dst(struct rt6_info *rt, const struct fib6_result *res)
{
    struct fib6_info *f6i = res->f6i;

    if (res->fib6_flags & RTF_REJECT) {
        ip6_rt_init_dst_reject(rt, res->fib6_type);
        return;
    }

路由重定向检查

在接收到重定向报文后,将报文源IP地址,作为网关进行路由查找,如果找的路由项设置了RTF_REJECT标志,表明本机并没有合适的路由到此网关,不应接收到此重定向报文,返回ip6_null_entry路由缓存项。由于ip6_null_entry设置了RTF_REJECT标志,在之后函数rt6_do_redirect中将判断到此错误,并打印错误信息。

INDIRECT_CALLABLE_SCOPE struct rt6_info *__ip6_route_redirect(struct net *net,
                         struct fib6_table *table, struct flowi6 *fl6,
                         const struct sk_buff *skb, int flags)
{
    struct ip6rd_flowi *rdfl = (struct ip6rd_flowi *)fl6;
    struct rt6_info *ret = NULL;
    struct fib6_nh_rd_arg arg = {
        .res = &res,
        .fl6 = fl6,
        .gw  = &rdfl->gateway,
        .ret = &ret
    };

    /* Get the "current" route for this destination and
     * check if the redirect has come from appropriate router.
     *
     * RFC 4861 specifies that redirects should only be
     * accepted if they come from the nexthop to the target.
     * Due to the way the routes are chosen, this notion
     * is a bit fuzzy and one might need to check all possible routes.
     */
    rcu_read_lock();
    fn = fib6_node_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr);
restart:
    for_each_fib6_node_rt_rcu(fn) {
        res.f6i = rt;
        if (fib6_check_expired(rt))
            continue;
        if (rt->fib6_flags & RTF_REJECT)
            break;
        ...
    }
    if (!rt)
        rt = net->ipv6.fib6_null_entry;
    else if (rt->fib6_flags & RTF_REJECT) {
        ret = net->ipv6.ip6_null_entry;
        goto out;

对于接收到的ICMP路由重定向报文,如果本机到此报文的源地址的路由设置了RTF_REJECT标志,认为其不是一个合法的重定向报文,打印错误信息。

static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_buff *skb)
{
    struct netevent_redirect netevent;
    struct rt6_info *rt, *nrt = NULL;


    rt = (struct rt6_info *) dst;
    if (rt->rt6i_flags & RTF_REJECT) {
        net_dbg_ratelimited("rt6_redirect: source isn't a valid nexthop for redirect target\n");
        return;
    }

IPv6路由PROC文件

ipv6_route文件内容的倒数第二个字段00200200表示路由项的标志位,其中设置了RTF_REJECT。对于与以上设置的Prohibit路由。

#define RTF_REJECT  0x0200      /* Reject route         */


$ cat /proc/net/ipv6_route 
33010000000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000001 00000000 00200200       lo

内核版本 5.10

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要禁止修改IP,可以通过调用Linux系统提供的 netlink socket API,具体来说,可以通过使用 libnl 库来简化 netlink socket 的使用。以下是一个示例代码片段,演示如何使用 libnl 库来禁止修改IP: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <netlink/netlink.h> #include <netlink/route/link.h> #include <netlink/route/link/addr.h> int main(int argc, char **argv) { struct nl_sock *sk; struct nl_cache *cache; struct rtnl_link *link; struct nl_addr *addr; int err; // 初始化 netlink socket sk = nl_socket_alloc(); if (!sk) { fprintf(stderr, "Failed to allocate netlink socket.\n"); return EXIT_FAILURE; } // 连接到 kernel netlink socket if (err = nl_connect(sk, NETLINK_ROUTE)) { fprintf(stderr, "Failed to connect to netlink: %s\n", nl_geterror(err)); nl_socket_free(sk); return EXIT_FAILURE; } // 获取网络接口信息 if (err = rtnl_link_alloc_cache(sk, AF_UNSPEC, &cache)) { fprintf(stderr, "Failed to allocate link cache: %s\n", nl_geterror(err)); nl_socket_free(sk); return EXIT_FAILURE; } // 找到名为 eth0 的网络接口 link = rtnl_link_get_by_name(cache, "eth0"); if (!link) { fprintf(stderr, "Failed to find interface eth0.\n"); nl_socket_free(sk); nl_cache_free(cache); return EXIT_FAILURE; } // 为该网络接口添加一个 IP 地址 addr = nl_addr_build(AF_INET, "192.168.0.1", 24); if (!addr) { fprintf(stderr, "Failed to build address.\n"); nl_socket_free(sk); nl_cache_free(cache); return EXIT_FAILURE; } // 禁止修改 IP 地址 err = rtnl_link_addr_set_policy(link, NL_ACT_FORBID, NL_ACT_REPLACE); if (err) { fprintf(stderr, "Failed to set IP address policy: %s\n", nl_geterror(err)); nl_socket_free(sk); nl_cache_free(cache); return EXIT_FAILURE; } // 添加 IP 地址到网络接口 err = rtnl_link_addr_add(link, addr, NLM_F_CREATE); if (err) { fprintf(stderr, "Failed to add IP address to link: %s\n", nl_geterror(err)); nl_socket_free(sk); nl_cache_free(cache); return EXIT_FAILURE; } // 释放资源 nl_addr_put(addr); rtnl_link_put(link); nl_cache_free(cache); nl_socket_free(sk); return EXIT_SUCCESS; } ``` 上述代码示例中,通过调用 `rtnl_link_addr_set_policy()` 函数来设置网络接口的 IP 地址修改策略,`NL_ACT_FORBID` 表示禁止修改,`NL_ACT_REPLACE` 表示在禁止修改策略下,如果有新的 IP 地址需要添加,则原有的 IP 地址会被替换。调用 `rtnl_link_addr_add()` 函数来向指定的网络接口添加 IP 地址。 需要注意的是,以上代码示例仅供参考,实际应用中需要根据具体的需求进行修改和适配。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值