You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux IPv6默认路由设置中RTNH_F_ONLINK不生效的修复问询

修复Linux IPv6默认路由替换时RTNH_F_ONLINK属性失效问题

问题背景

需要用C代码实现与命令ip -6 route replace default via $GW6 dev $IFACE proto static metric 1024 onlink pref medium完全一致的IPv6默认路由替换效果,当前代码遇到以下问题:

  • 设置nl_request.r.rtm_flags = RTNH_F_ONLINK;时,路由无法更新
  • 移除该设置后路由可更新,但缺少onlink属性

当前代码

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/rtnetlink.h>

/* Open netlink socket */
static int open_netlink(int af_family)
{
    struct sockaddr_nl saddr;

    int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);

    if (sock < 0) {
        perror("Failed to open netlink socket");
        return -1;
    }

    memset(&saddr, 0, sizeof(saddr));

    return sock;
}

/* Helper structure for ip address data and attributes */
typedef struct {
    uint8_t family;
    uint8_t bitlen;
    unsigned char data[sizeof(struct in6_addr)];
} _inet_addr;

#define NLMSG_TAIL(nmsg) \
    ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))

/* Add new data to rtattr */
static int rtattr_add(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen)
{
    int len = RTA_LENGTH(alen);
    struct rtattr *rta;

    if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) {
        fprintf(stderr, "rtattr_add error: message exceeded bound of %d\n", maxlen);
        return -1;
    }

    rta = NLMSG_TAIL(n);
    rta->rta_type = type;
    rta->rta_len = len; 

    if (alen) {
        memcpy(RTA_DATA(rta), data, alen);
    }

    n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);

    return 0;
}

static int do_route(int sock, int cmd, int flags, _inet_addr *dst, _inet_addr *gw, int def_gw, int if_idx)
{
    struct {
        struct nlmsghdr n;
        struct rtmsg r;
        char buf[4096];
    } nl_request;

    /* Initialize request structure */
    nl_request.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
    nl_request.n.nlmsg_flags = NLM_F_REQUEST | flags;
    nl_request.n.nlmsg_type = cmd;
    nl_request.r.rtm_dst_len = dst->bitlen;
    nl_request.r.rtm_family = dst->family;
    nl_request.r.rtm_table = RT_TABLE_MAIN;
    nl_request.r.rtm_protocol = RTPROT_BOOT;
    nl_request.r.rtm_type = RTN_UNICAST;

    /* Set additional flags if NOT deleting route */
    if (cmd != RTM_DELROUTE) {
        /* Select scope, for simplicity we supports here only IPv6 and IPv4 */
        if (nl_request.r.rtm_family == AF_INET6) {
            nl_request.r.rtm_scope  = RT_SCOPE_UNIVERSE;
        nl_request.r.rtm_flags  = RTNH_F_ONLINK;
        } else {
        nl_request.r.rtm_scope = RT_SCOPE_LINK;
        }
    }

    /* Set gateway */
    if (gw->bitlen != 0) {
        rtattr_add(&nl_request.n, sizeof(nl_request), RTA_GATEWAY, &gw->data, gw->bitlen / 8);
        nl_request.r.rtm_scope = 0;
        nl_request.r.rtm_family = gw->family;

        /* RTA_PREF = ICMPV6_ROUTER_PREF_MEDIUM */
    }

    /* Set destination network */
    rtattr_add(&nl_request.n, sizeof(nl_request), /*RTA_NEWDST*/ RTA_DST, &dst->data, dst->bitlen / 8);

    if (!def_gw) {
        /* Set interface */
        rtattr_add(&nl_request.n, sizeof(nl_request), RTA_OIF, &if_idx, sizeof(int));
    }

    /* Send message to the netlink */
    return send(sock, &nl_request, sizeof(nl_request), 0);
}

/* Simple parser of the string IP address
 */
static int read_addr(const char *addr, _inet_addr *res)
{
    if (strchr(addr, ':')) {
        res->family = AF_INET6;
        res->bitlen = 128;
    } else {
        res->family = AF_INET;
        res->bitlen = 32;
    }

    return inet_pton(res->family, addr, res->data);
}

int replace_default_gateway(const char* interface_name, const char* gateway_ip, int af_family, int* perr) {
    int sock_fd, err;
    struct ifreq ifr;
    _inet_addr to_addr;
    _inet_addr gw_addr;
    int nl_cmd      = RTM_NEWROUTE;
    int nl_flags    = NLM_F_CREATE | NLM_F_REPLACE;

    memset(&to_addr, 0, sizeof(to_addr));
    memset(&gw_addr, 0, sizeof(gw_addr));
    to_addr.family = (char) af_family;

    sock_fd = open_netlink(af_family);
    if (sock_fd < 0) {
        perr[0] = sock_fd;
        return -1;
    }
    perr[0] = read_addr(gateway_ip, &gw_addr);
    if( perr[0] != 1 ) {
        err = -2;
        goto L1;
    }
    if( gw_addr.family != af_family ) {
        err = -3;
        goto L1;
    }

    memset(&ifr, 0, sizeof(struct ifreq));
    strncpy(ifr.ifr_name, interface_name, IFNAMSIZ-1);
    // Get the ifrindex of the interface
    perr[0] = ioctl(sock_fd, SIOGIFINDEX, &ifr);
    if ( perr[0] < 0 ) {
        err = -2;
        goto L1;
    }
    perr[0] = do_route(sock_fd, nl_cmd, nl_flags, &to_addr, &gw_addr, 1, ifr.ifr_ifindex);
    err = 0;
L1:
    close(sock_fd);
    return err;
}

问题原因

RTNH_F_ONLINK是**路由下一跳(nexthop)**的属性标志,而非路由条目本身的rtmsg.rtm_flags字段。直接将其设置到rtm_flags会导致netlink消息格式错误,内核无法正确解析,从而路由更新失败;移除该设置后消息格式合法,但缺少下一跳的onlink标志。

另外原代码的netlink socket未绑定地址,可能导致消息发送不稳定。

修复方案

在open_netlink函数中添加绑定操作,确保socket能正常通信:

static int open_netlink(int af_family)
{
    struct sockaddr_nl saddr;

    int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);

    if (sock < 0) {
        perror("Failed to open netlink socket");
        return -1;
    }

    memset(&saddr, 0, sizeof(saddr));
    saddr.nl_family = AF_NETLINK;
    saddr.nl_pid = getpid();
    saddr.nl_groups = 0;

    if (bind(sock, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) {
        perror("Failed to bind netlink socket");
        close(sock);
        return -1;
    }

    return sock;
}

2. 正确传递RTNH_F_ONLINK标志

通过RTA_NH属性传递下一跳信息,将RTNH_F_ONLINK设置到struct rtnexthop的rtnh_flags字段,同时指定出口接口:
修改do_route函数中的网关处理逻辑,替换原有的RTA_GATEWAY添加代码:

/* Set gateway */
if (gw->bitlen != 0) {
    // 构造下一跳结构
    struct rtnexthop nh;
    memset(&nh, 0, sizeof(nh));
    nh.rtnh_family = gw->family;
    nh.rtnh_len = sizeof(struct rtnexthop);
    nh.rtnh_flags = RTNH_F_ONLINK;
    nh.rtnh_ifindex = if_idx;

    // 添加RTA_NH属性
    int ret = rtattr_add(&nl_request.n, sizeof(nl_request), RTA_NH, &nh, sizeof(nh));
    if (ret < 0) {
        return ret;
    }

    // 在RTA_NH属性内添加网关地址子属性
    struct rtattr *nh_rta = NLMSG_TAIL(&nl_request.n);
    nh_rta->rta_type = RTA_GATEWAY;
    nh_rta->rta_len = RTA_LENGTH(gw->bitlen / 8);
    memcpy(RTA_DATA(nh_rta), gw->data, gw->bitlen / 8);
    nl_request.n.nlmsg_len += RTA_ALIGN(nh_rta->rta_len);

    nl_request.r.rtm_scope = RT_SCOPE_UNIVERSE;
}

3. 添加metric和preference属性

为了和目标命令完全一致,添加metric 1024和pref medium的设置,在网关处理逻辑后添加:

// 设置metric 1024和pref medium
uint32_t metric = 1024;
uint32_t pref = ICMPV6_ROUTER_PREF_MEDIUM;

// 添加RTA_METRICS容器属性
int ret = rtattr_add(&nl_request.n, sizeof(nl_request), RTA_METRICS, NULL, 0);
if (ret < 0) {
    return ret;
}

// 在容器内添加metric属性
struct rtattr *metrics_rta = NLMSG_TAIL(&nl_request.n);
ret = rtattr_add(metrics_rta, sizeof(nl_request) - ((char*)metrics_rta - (char*)&nl_request), RTAX_METRIC, &metric, sizeof(metric));
if (ret < 0) {
    return ret;
}

// 在容器内添加preference属性
ret = rtattr_add(metrics_rta, sizeof(nl_request) - ((char*)metrics_rta - (char*)&nl_request), RTAX_PREF, &pref, sizeof(pref));
if (ret < 0) {
    return ret;
}

4. 移除错误的rtm_flags设置

删除do_route函数中nl_request.r.rtm_flags = RTNH_F_ONLINK;这一行代码。

验证

修复后执行代码,再运行ip -6 route show,可以看到默认路由包含onlink、metric 1024和pref medium属性,与目标命令执行效果完全一致。

内容的提问来源于stack exchange,提问作者p calvin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 18:37:04