]> git.ozlabs.org Git - ppp.git/commitdiff
pppd: add VRF (Virtual Routing and Forwarding) binding support on Linux (#566) master
authorMartin Olivier <martin.olivier@live.fr>
Fri, 3 Oct 2025 09:16:54 +0000 (11:16 +0200)
committerGitHub <noreply@github.com>
Fri, 3 Oct 2025 09:16:54 +0000 (19:16 +1000)
Add 'vrf' option to bind a PPP interface to a specific VRF, so that
routes are installed in the VRF's routing table rather than the main
routing table.

This allows PPP sessions to operate in separate routing domains,
providing isolation of traffic and routes.

When this option is used, pppd sets the 'VRF' environment variable for
ip-{up,down} scripts.

Signed-off-by: Martin Olivier <martin.olivier@live.fr>
pppd/main.c
pppd/options.c
pppd/pppd-private.h
pppd/pppd.8
pppd/sys-linux.c

index 65dac0e2d92f6ee329662cfaa49b2ad99773d481..b5c0c0e7ffe63a9d28caef66156e095928c19543 100644 (file)
@@ -846,6 +846,10 @@ set_ifunit(int iskey)
        slprintf(ifname, sizeof(ifname), "%s%d", PPP_DRV_NAME, ifunit);
     info("Using interface %s", ifname);
     ppp_script_setenv("IFNAME", ifname, iskey);
+#ifdef __linux__
+    if (req_vrf[0] != '\0')
+        ppp_script_setenv("VRF", req_vrf, iskey);
+#endif
     slprintf(ifkey, sizeof(ifkey), "%d", ifunit);
     ppp_script_setenv("UNIT", ifkey, iskey);
     if (iskey) {
index dc993dbb5ea81e404e936cbeb43441d807ffc7f3..61954de96287e8bc2c90c33c6596d7cd2047b277 100644 (file)
@@ -128,6 +128,9 @@ char        path_ipup[MAXPATHLEN];  /* pathname of ip-up script */
 char   path_ipdown[MAXPATHLEN];/* pathname of ip-down script */
 char   path_ippreup[MAXPATHLEN]; /* pathname of ip-pre-up script */
 char   req_ifname[IFNAMSIZ];   /* requested interface name */
+#ifdef __linux__
+char   req_vrf[IFNAMSIZ];      /* VRF name to bind with PPP interface */
+#endif
 bool   multilink = 0;          /* Enable multilink operation */
 char   *bundle_name = NULL;    /* bundle name for multilink */
 bool   dump_options;           /* print out option values */
@@ -314,6 +317,12 @@ struct option general_options[] = {
       "Set PPP interface name",
       OPT_PRIO | OPT_PRIV | OPT_STATIC, NULL, IFNAMSIZ },
 
+#ifdef __linux__
+    { "vrf", o_string, req_vrf,
+      "Bind PPP interface to the specified VRF and install routes in its routing table",
+      OPT_PRIO | OPT_PRIV | OPT_STATIC, NULL, IFNAMSIZ },
+#endif
+
     { "dump", o_bool, &dump_options,
       "Print out option values after parsing all options", 1 },
     { "dryrun", o_bool, &dryrun,
index b3d5a652451edcfcb1fe6046f79ded26a5eaceee..5f841824e1e1fab93ebaf50765310f3b6c9c6229 100644 (file)
@@ -203,6 +203,9 @@ extern char path_ipup[];    /* pathname of ip-up script */
 extern char    path_ipdown[];  /* pathname of ip-down script */
 extern char    path_ippreup[]; /* pathname of ip-pre-up script */
 extern char    req_ifname[]; /* interface name to use (IFNAMSIZ) */
+#ifdef __linux__
+extern char    req_vrf[];      /* VRF name to bind with PPP interface */
+#endif
 extern bool    multilink;      /* enable multilink operation (options.c) */
 extern bool    noendpoint;     /* don't send or accept endpt. discrim. */
 extern char    *bundle_name;   /* bundle name for multilink */
index f8f85ddc9c123ba64017953839edb804d9659dcc..5e8fd20d9d534a1d00a8c3ac0a7282574fc329e6 100644 (file)
@@ -1165,6 +1165,12 @@ Set the ppp interface name for outbound connections.  If the interface name is
 already in use, or if the name cannot be used for any other reason, pppd will
 terminate.
 .TP
+.B vrf \fIname
+Bind the ppp interface to the existing VRF (Virtual Routing and Forwarding)
+instance \fIname\fR on Linux.  Routes installed by pppd will go into the
+routing table of VRF \fIname\fR instead of the default table.  This option
+is only available on Linux systems that support VRF.
+.TP
 .B unset \fIname
 Remove a variable from the environment variable for scripts that are
 invoked by pppd.  When specified by a privileged source, the variable
@@ -1759,6 +1765,10 @@ The name of the serial tty device being used.
 .B IFNAME
 The name of the network interface being used.
 .TP
+.B VRF
+The name of the VRF to which the ppp interface is bound.  This is only set if
+the ppp interface has been bound to a VRF using the \fIvrf\fR option.
+.TP
 .B IPLOCAL
 The IP address for the local end of the link.  This is only set when
 IPCP has come up.
index 51fbe29d2a11368ca9ef866ca7238825fc83df27..9933fd59a62fe46e6275a915b92ee543039098c2 100644 (file)
@@ -215,6 +215,8 @@ int ppp_dev_fd = -1;                /* fd for /dev/ppp (new style driver) */
 
 static int chindex;            /* channel index (new style driver) */
 
+static unsigned routing_table_id = RT_TABLE_MAIN;
+
 static int has_proxy_arp       = 0;
 static int driver_version      = 0;
 static int driver_modification = 0;
@@ -790,12 +792,93 @@ void ppp_generic_disestablish(int dev_fd)
     }
 }
 
+/********************************************************************
+ *
+ * get_vrf_table_id - get the routing table id of a VRF from its ifindex.
+ * 
+ * Returns 0 (unspec table id) on failure.
+ */
+static unsigned get_vrf_table_id(unsigned vrf_ifindex)
+{
+    struct {
+        struct nlmsghdr nlh;
+        struct ifinfomsg ifm;
+    } nlreq;
+    struct {
+        struct ifinfomsg ifm;
+        char buf[4096];
+    } nlresp;
+    struct rtattr *vrf_table = NULL;
+    struct rtattr *info_data = NULL;
+    struct rtattr *linkinfo = NULL;
+    struct rtattr *kind = NULL;
+    struct rtattr *rta;
+    size_t nlresp_size;
+    int vrf_len;
+    int li_len;
+    int resp;
+    int len;
+
+    memset(&nlreq, 0, sizeof(nlreq));
+    nlreq.nlh.nlmsg_len = sizeof(nlreq);
+    nlreq.nlh.nlmsg_type = RTM_GETLINK;
+    nlreq.nlh.nlmsg_flags = NLM_F_REQUEST;
+    nlreq.ifm.ifi_family = AF_UNSPEC;
+    nlreq.ifm.ifi_index = vrf_ifindex;
+
+    nlresp_size = sizeof(nlresp);
+    resp = rtnetlink_msg("RTM_GETLINK/NLM_F_REQUEST", NULL, &nlreq, sizeof(nlreq), &nlresp, &nlresp_size, RTM_NEWLINK);
+    if (resp) {
+        errno = (resp < 0) ? -resp : EINVAL;
+        error("Couldn't collect vrf info: %m");
+        return 0;
+    }
+
+    len = nlresp_size - sizeof(nlresp.ifm);
+
+    /* Walk on top-level attributes */
+    for (rta = IFLA_RTA(&nlresp.ifm); RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
+        if (rta->rta_type == IFLA_LINKINFO) {
+            linkinfo = rta;
+            break;
+        }
+    }
+    if (!linkinfo)
+        return 0;
+
+    /* Walk in IFLA_LINKINFO */
+    li_len = RTA_PAYLOAD(linkinfo);
+    for (rta = (struct rtattr *)RTA_DATA(linkinfo); RTA_OK(rta, li_len); rta = RTA_NEXT(rta, li_len)) {
+        if (rta->rta_type == IFLA_INFO_KIND)
+            kind = rta;
+        else if (rta->rta_type == IFLA_INFO_DATA)
+            info_data = rta;
+    }
+    if (!kind || strcmp((char *)RTA_DATA(kind), "vrf") != 0)
+        return 0;
+    if (!info_data)
+        return 0;
+
+    /* Walk in IFLA_INFO_DATA */
+    vrf_len = RTA_PAYLOAD(info_data);
+    for (rta = (struct rtattr *)RTA_DATA(info_data); RTA_OK(rta, vrf_len); rta = RTA_NEXT(rta, vrf_len)) {
+        if (rta->rta_type == IFLA_VRF_TABLE) {
+            vrf_table = rta;
+            break;
+        }
+    }
+    if (!vrf_table)
+        return 0;
+
+    return *(unsigned *)RTA_DATA(vrf_table);
+}
+
 /*
  * make_ppp_unit_rtnetlink - register a new ppp network interface for ppp_dev_fd
  * with specified req_ifname via rtnetlink. Interface name req_ifname must not
  * be empty. Custom ppp unit id req_unit is ignored and kernel choose some free.
  */
-static int make_ppp_unit_rtnetlink(void)
+static int make_ppp_unit_rtnetlink(unsigned vrf_ifindex)
 {
     struct {
         struct nlmsghdr nlh;
@@ -804,6 +887,10 @@ static int make_ppp_unit_rtnetlink(void)
             struct rtattr rta;
             char ifname[IFNAMSIZ];
         } ifn;
+        struct {
+            struct rtattr rta;
+            unsigned ifindex;
+        } ifp;
         struct {
             struct rtattr rta;
             struct {
@@ -832,6 +919,9 @@ static int make_ppp_unit_rtnetlink(void)
     nlreq.ifn.rta.rta_len = sizeof(nlreq.ifn);
     nlreq.ifn.rta.rta_type = IFLA_IFNAME;
     strlcpy(nlreq.ifn.ifname, req_ifname, sizeof(nlreq.ifn.ifname));
+    nlreq.ifp.rta.rta_len = sizeof(nlreq.ifp);
+    nlreq.ifp.rta.rta_type = IFLA_MASTER;
+    nlreq.ifp.ifindex = vrf_ifindex;
     nlreq.ifli.rta.rta_len = sizeof(nlreq.ifli);
     nlreq.ifli.rta.rta_type = IFLA_LINKINFO;
     nlreq.ifli.ifik.rta.rta_len = sizeof(nlreq.ifli.ifik);
@@ -874,6 +964,7 @@ static int make_ppp_unit_rtnetlink(void)
  */
 static int make_ppp_unit(void)
 {
+       unsigned vrf_ifindex = 0;
        int x, flags;
 
        if (ppp_dev_fd >= 0) {
@@ -888,6 +979,20 @@ static int make_ppp_unit(void)
            || fcntl(ppp_dev_fd, F_SETFL, flags | O_NONBLOCK) == -1)
                warn("Couldn't set /dev/ppp to nonblock: %m");
 
+       if (req_vrf[0] != '\0') {
+               vrf_ifindex = if_nametoindex(req_vrf);
+               if (vrf_ifindex == 0) {
+                       error("Requested vrf %s does not exist", req_vrf);
+                       return -1;
+               }
+
+               routing_table_id = get_vrf_table_id(vrf_ifindex);
+               if (routing_table_id == 0) {
+                       error("Couldn't get the routing table id of vrf %s", req_vrf);
+                       return -1;
+               }
+       }
+
        /*
         * Via rtnetlink it is possible to create ppp network interface with
         * custom ifname atomically. But it is not possible to specify custom
@@ -902,7 +1007,7 @@ static int make_ppp_unit(void)
         * avoid system issues with interface renaming.
         */
        if (req_unit == -1 && req_ifname[0] != '\0' && kernel_version >= KVERSION(2,1,16)) {
-           if (make_ppp_unit_rtnetlink()) {
+           if (make_ppp_unit_rtnetlink(vrf_ifindex)) {
                if (ioctl(ppp_dev_fd, PPPIOCGUNIT, &ifunit))
                    fatal("Couldn't retrieve PPP unit id: %m");
                return 0;
@@ -930,6 +1035,42 @@ static int make_ppp_unit(void)
        if (x < 0)
                error("Couldn't create new ppp unit: %m");
 
+       if (x == 0 && req_vrf[0] != '\0') {
+               struct {
+                       struct nlmsghdr nlh;
+                       struct ifinfomsg ifm;
+                       struct {
+                               struct rtattr rta;
+                               unsigned ifindex;
+                       } ifp;
+               } nlreq;
+               char ppp_iface[IFNAMSIZ];
+               int resp;
+
+               memset(&nlreq, 0, sizeof(nlreq));
+
+               slprintf(ppp_iface, sizeof(ppp_iface), "%s%d", PPP_DRV_NAME, ifunit);
+
+               nlreq.nlh.nlmsg_len = sizeof(nlreq);
+               nlreq.nlh.nlmsg_type = RTM_SETLINK;
+               nlreq.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+
+               nlreq.ifm.ifi_family = AF_UNSPEC;
+               nlreq.ifm.ifi_index = if_nametoindex(ppp_iface);
+
+               nlreq.ifp.rta.rta_type = IFLA_MASTER;
+               nlreq.ifp.rta.rta_len = sizeof(nlreq.ifp);
+               nlreq.ifp.ifindex = vrf_ifindex;
+
+               resp = rtnetlink_msg("RTM_SETLINK/NLM_F_REQUEST", NULL, &nlreq, sizeof(nlreq), NULL, NULL, 0);
+               if (resp) {
+                       x = -1;
+                       error("Couldn't move interface %s in vrf %s", ppp_iface, req_vrf);
+               } else {
+                       info("Moved interface %s in vrf %s", ppp_iface, req_vrf);
+               }
+       }
+
        if (x == 0 && req_ifname[0] != '\0') {
                struct ifreq ifr;
                char t[IFNAMSIZ];
@@ -2104,6 +2245,10 @@ int _route_netlink(const char* op_fam, int operation, int family, unsigned metri
            struct rtattr rta;
            unsigned val;
        } metric;
+       struct {
+           struct rtattr rta;
+           unsigned id;
+       } table;
        struct {
            struct rtattr rta;
            unsigned char ipdata[16]; /* IPv6 MAX */
@@ -2121,7 +2266,7 @@ int _route_netlink(const char* op_fam, int operation, int family, unsigned metri
        nlreq.nlh.nlmsg_flags |= NLM_F_APPEND;
 
     nlreq.rtmsg.rtm_family = family;
-    nlreq.rtmsg.rtm_table = RT_TABLE_MAIN;
+    nlreq.rtmsg.rtm_table = RT_TABLE_UNSPEC;
     nlreq.rtmsg.rtm_protocol = RTPROT_BOOT;
     nlreq.rtmsg.rtm_scope = RT_SCOPE_LINK;
     nlreq.rtmsg.rtm_type = RTN_UNICAST;
@@ -2134,6 +2279,10 @@ int _route_netlink(const char* op_fam, int operation, int family, unsigned metri
     nlreq.metric.rta.rta_type = RTA_PRIORITY;
     nlreq.metric.val = metric;
 
+    nlreq.table.rta.rta_len = sizeof(nlreq.table);
+    nlreq.table.rta.rta_type = RTA_TABLE;
+    nlreq.table.id = routing_table_id;
+
     if (prefix) {
        char nbytes;
        switch (family) {