]> git.ozlabs.org Git - ppp.git/commitdiff
Merge pull request #354 from pali/register-with-name
authorPaul Mackerras <paulus@ozlabs.org>
Fri, 5 Aug 2022 04:10:40 +0000 (14:10 +1000)
committerPaul Mackerras <paulus@ozlabs.org>
Fri, 5 Aug 2022 04:10:40 +0000 (14:10 +1000)
pppd: Add support for registering ppp interface via Linux rtnetlink API

1  2 
pppd/sys-linux.c

diff --combined pppd/sys-linux.c
index ff3a249e81758ec042a6cd67991e6412532f8d1b,6eed86f3f2a2b0bd6f1754375d1c6734380ce6a2..e7f851c02512394cc1ebe5b63f771eb4890846b4
@@@ -81,7 -81,6 +81,7 @@@
  #include <sys/stat.h>
  #include <sys/utsname.h>
  #include <sys/sysmacros.h>
 +#include <sys/param.h>
  
  #include <errno.h>
  #include <stddef.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  
 -#include <linux/ppp_defs.h>
 -#include <linux/if_ppp.h>
 +#include <linux/ppp-ioctl.h>
  
  #include <linux/netlink.h>
  #include <linux/rtnetlink.h>
  #include <linux/if_link.h>
 -#ifdef INET6
 -#include <linux/if_addr.h>
 -#endif
 -
  /* Attempt at retaining compile-support with older than 4.7 kernels, or kernels
   * where RTM_NEWSTATS isn't defined for whatever reason.
   */
  #define RTM_NEWSTATS 92
  #define RTM_GETSTATS 94
  #define IFLA_STATS_LINK_64 1
+ #endif
  
 +#include <linux/if_addr.h>
++
  /* glibc versions prior to 2.24 do not define SOL_NETLINK */
  #ifndef SOL_NETLINK
  #define SOL_NETLINK 270
  #ifndef NETLINK_CAP_ACK
  #define NETLINK_CAP_ACK 10
  #endif
+ /* linux kernel versions prior to 4.7 do not define/support IFLA_PPP_DEV_FD */
+ #ifndef IFLA_PPP_MAX
+ /* IFLA_PPP_DEV_FD is declared as enum when IFLA_PPP_MAX is defined */
+ #define IFLA_PPP_DEV_FD 1
  #endif
  
  #include "pppd.h"
  #include "fsm.h"
  #include "ipcp.h"
  
 -#ifdef PPP_FILTER
 +#ifdef PPP_WITH_IPV6CP
 +#include "eui64.h"
 +#endif /* PPP_WITH_IPV6CP */
 +
 +#ifdef PPP_WITH_FILTER
  #include <pcap-bpf.h>
  #include <linux/filter.h>
 -#endif /* PPP_FILTER */
 +#endif /* PPP_WITH_FILTER */
  
  #ifdef LOCKLIB
  #include <sys/locks.h>
   */
  #include "termios_linux.h"
  
 -#ifdef INET6
 +#ifdef PPP_WITH_IPV6CP
  #ifndef _LINUX_IN6_H
  /*
   *    This is in linux/include/net/ipv6.h.
@@@ -194,7 -200,7 +202,7 @@@ struct in6_ifreq 
        } while (0)
  
  static const eui64_t nulleui64;
 -#endif /* INET6 */
 +#endif /* PPP_WITH_IPV6CP */
  
  /* We can get an EIO error on an ioctl if the modem has hung up */
  #define ok_error(num) ((num)==EIO)
@@@ -206,9 -212,9 +214,9 @@@ static int ppp_fd = -1;            /* fd which i
  static int sock_fd = -1;      /* socket for doing interface ioctls */
  static int slave_fd = -1;     /* pty for old-style demand mode, slave */
  static int master_fd = -1;    /* pty for old-style demand mode, master */
 -#ifdef INET6
 +#ifdef PPP_WITH_IPV6CP
  static int sock6_fd = -1;
 -#endif /* INET6 */
 +#endif /* PPP_WITH_IPV6CP */
  
  /*
   * For the old-style kernel driver, this is the same as ppp_fd.
@@@ -351,7 -357,7 +359,7 @@@ void sys_init(void
      if (sock_fd < 0)
        fatal("Couldn't create IP socket: %m(%d)", errno);
  
 -#ifdef INET6
 +#ifdef PPP_WITH_IPV6CP
      sock6_fd = socket(AF_INET6, SOCK_DGRAM, 0);
      if (sock6_fd < 0)
        sock6_fd = -errno;      /* save errno for later */
@@@ -377,17 -383,15 +385,17 @@@ void sys_cleanup(void
        if_is_up = 0;
        sifdown(0);
      }
 +#ifdef PPP_WITH_IPV6CP
      if (if6_is_up)
        sif6down(0);
 +#endif
  
  /*
   * Delete any routes through the device.
   */
      if (have_default_route)
        cifdefaultroute(0, 0, 0);
 -#ifdef INET6
 +#ifdef PPP_WITH_IPV6CP
      if (have_default_route6)
        cif6defaultroute(0, nulleui64, nulleui64);
  #endif
@@@ -407,7 -411,7 +415,7 @@@ sys_close(void
        close(ppp_dev_fd);
      if (sock_fd >= 0)
        close(sock_fd);
 -#ifdef INET6
 +#ifdef PPP_WITH_IPV6CP
      if (sock6_fd >= 0)
        close(sock6_fd);
  #endif
@@@ -662,6 -666,160 +670,160 @@@ void generic_disestablish_ppp(int dev_f
      }
  }
  
+ /*
+  * 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)
+ {
+     struct {
+         struct nlmsghdr nlh;
+         struct ifinfomsg ifm;
+         struct {
+             struct rtattr rta;
+             char ifname[IFNAMSIZ];
+         } ifn;
+         struct {
+             struct rtattr rta;
+             struct {
+                 struct rtattr rta;
+                 char ifkind[sizeof("ppp")];
+             } ifik;
+             struct {
+                 struct rtattr rta;
+                 struct {
+                     struct rtattr rta;
+                     union {
+                         int ppp_dev_fd;
+                     } ppp;
+                 } ifdata[1];
+             } ifid;
+         } ifli;
+     } nlreq;
+     struct {
+         struct nlmsghdr nlh;
+         struct nlmsgerr nlerr;
+     } nlresp;
+     struct sockaddr_nl nladdr;
+     struct iovec iov;
+     struct msghdr msg;
+     ssize_t nlresplen;
+     int one;
+     int fd;
+     fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+     if (fd < 0) {
+         error("make_ppp_unit_rtnetlink: socket(NETLINK_ROUTE): %m (line %d)", __LINE__);
+         return 0;
+     }
+     /* Tell kernel to not send to us payload of acknowledgment error message. */
+     one = 1;
+     setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &one, sizeof(one));
+     memset(&nladdr, 0, sizeof(nladdr));
+     nladdr.nl_family = AF_NETLINK;
+     if (bind(fd, (struct sockaddr *)&nladdr, sizeof(nladdr)) < 0) {
+         error("make_ppp_unit_rtnetlink: bind(AF_NETLINK): %m (line %d)", __LINE__);
+         close(fd);
+         return 0;
+     }
+     memset(&nlreq, 0, sizeof(nlreq));
+     nlreq.nlh.nlmsg_len = sizeof(nlreq);
+     nlreq.nlh.nlmsg_type = RTM_NEWLINK;
+     nlreq.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE;
+     nlreq.ifm.ifi_family = AF_UNSPEC;
+     nlreq.ifm.ifi_type = ARPHRD_NETROM;
+     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.ifli.rta.rta_len = sizeof(nlreq.ifli);
+     nlreq.ifli.rta.rta_type = IFLA_LINKINFO;
+     nlreq.ifli.ifik.rta.rta_len = sizeof(nlreq.ifli.ifik);
+     nlreq.ifli.ifik.rta.rta_type = IFLA_INFO_KIND;
+     strcpy(nlreq.ifli.ifik.ifkind, "ppp");
+     nlreq.ifli.ifid.rta.rta_len = sizeof(nlreq.ifli.ifid);
+     nlreq.ifli.ifid.rta.rta_type = IFLA_INFO_DATA;
+     nlreq.ifli.ifid.ifdata[0].rta.rta_len = sizeof(nlreq.ifli.ifid.ifdata[0]);
+     nlreq.ifli.ifid.ifdata[0].rta.rta_type = IFLA_PPP_DEV_FD;
+     nlreq.ifli.ifid.ifdata[0].ppp.ppp_dev_fd = ppp_dev_fd;
+     memset(&nladdr, 0, sizeof(nladdr));
+     nladdr.nl_family = AF_NETLINK;
+     memset(&iov, 0, sizeof(iov));
+     iov.iov_base = &nlreq;
+     iov.iov_len = sizeof(nlreq);
+     memset(&msg, 0, sizeof(msg));
+     msg.msg_name = &nladdr;
+     msg.msg_namelen = sizeof(nladdr);
+     msg.msg_iov = &iov;
+     msg.msg_iovlen = 1;
+     if (sendmsg(fd, &msg, 0) < 0) {
+         error("make_ppp_unit_rtnetlink: sendmsg(RTM_NEWLINK/NLM_F_CREATE): %m (line %d)", __LINE__);
+         close(fd);
+         return 0;
+     }
+     memset(&iov, 0, sizeof(iov));
+     iov.iov_base = &nlresp;
+     iov.iov_len = sizeof(nlresp);
+     memset(&msg, 0, sizeof(msg));
+     msg.msg_name = &nladdr;
+     msg.msg_namelen = sizeof(nladdr);
+     msg.msg_iov = &iov;
+     msg.msg_iovlen = 1;
+     nlresplen = recvmsg(fd, &msg, 0);
+     if (nlresplen < 0) {
+         error("make_ppp_unit_rtnetlink: recvmsg(NLM_F_ACK): %m (line %d)", __LINE__);
+         close(fd);
+         return 0;
+     }
+     close(fd);
+     if (nladdr.nl_family != AF_NETLINK) {
+         error("make_ppp_unit_rtnetlink: recvmsg(NLM_F_ACK): Not a netlink packet (line %d)", __LINE__);
+         return 0;
+     }
+     if ((size_t)nlresplen < sizeof(nlresp) || nlresp.nlh.nlmsg_len < sizeof(nlresp)) {
+         error("make_ppp_unit_rtnetlink: recvmsg(NLM_F_ACK): Acknowledgment netlink packet too short (line %d)", __LINE__);
+         return 0;
+     }
+     /* acknowledgment packet for NLM_F_ACK is NLMSG_ERROR */
+     if (nlresp.nlh.nlmsg_type != NLMSG_ERROR) {
+         error("make_ppp_unit_rtnetlink: recvmsg(NLM_F_ACK): Not an acknowledgment netlink packet (line %d)", __LINE__);
+         return 0;
+     }
+     /* error == 0 indicates success, negative value is errno code */
+     if (nlresp.nlerr.error != 0) {
+         /*
+          * Linux kernel versions prior to 4.7 do not support creating ppp
+          * interfaces via rtnetlink API and therefore error response is
+          * expected. On older kernel versions do not show this error message.
+          * When error is different than EEXIST then pppd tries to fallback to
+          * the old ioctl method.
+          */
+         errno = (nlresp.nlerr.error < 0) ? -nlresp.nlerr.error : EINVAL;
+         if (kernel_version >= KVERSION(4,7,0))
+             error("Couldn't create ppp interface %s: %m", req_ifname);
+         return 0;
+     }
+     return 1;
+ }
  /*
   * make_ppp_unit - make a new ppp unit for ppp_dev_fd.
   * Assumes new_style_driver.
@@@ -682,6 -840,33 +844,33 @@@ 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");
  
+       /*
+        * Via rtnetlink it is possible to create ppp network interface with
+        * custom ifname atomically. But it is not possible to specify custom
+        * ppp unit id.
+        *
+        * Tools like systemd, udev or NetworkManager are trying to query
+        * interface attributes based on interface name immediately when new
+        * network interface is created. And therefore immediate interface
+        * renaming is causing issues.
+        *
+        * So use rtnetlink API only when user requested custom ifname. It will
+        * 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 (ioctl(ppp_dev_fd, PPPIOCGUNIT, &ifunit))
+                   fatal("Couldn't retrieve PPP unit id: %m");
+               return 0;
+           }
+           /*
+            * If interface with requested name already exist return error
+            * otherwise fallback to old ioctl method.
+            */
+           if (errno == EEXIST)
+               return -1;
+       }
        ifunit = req_unit;
        x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit);
        if (x < 0 && req_unit >= 0 && errno == EEXIST) {
@@@ -1427,7 -1612,7 +1616,7 @@@ void ccp_flags_set (int unit, int isope
                modify_flags(ppp_dev_fd, SC_CCP_OPEN|SC_CCP_UP, x);
  }
  
 -#ifdef PPP_FILTER
 +#ifdef PPP_WITH_FILTER
  /*
   * set_filters - set the active and pass filters in the kernel driver.
   */
@@@ -1452,7 -1637,7 +1641,7 @@@ int set_filters(struct bpf_program *pas
        }
        return 1;
  }
 -#endif /* PPP_FILTER */
 +#endif /* PPP_WITH_FILTER */
  
  /********************************************************************
   *
@@@ -1477,21 -1662,20 +1666,21 @@@ get_ppp_stats_ioctl(int u, struct pppd_
      static u_int32_t iwraps = 0;
      static u_int32_t owraps = 0;
  
 -    struct ifpppstatsreq req;
 +    struct ifreq req;
 +    struct ppp_stats data;
  
      memset (&req, 0, sizeof (req));
  
 -    req.stats_ptr = (caddr_t) &req.stats;
 -    strlcpy(req.ifr__name, ifname, sizeof(req.ifr__name));
 +    req.ifr_data = (caddr_t) &data;
 +    strlcpy(req.ifr_name, ifname, sizeof(req.ifr_name));
      if (ioctl(sock_fd, SIOCGPPPSTATS, &req) < 0) {
        error("Couldn't get PPP statistics: %m");
        return 0;
      }
 -    stats->bytes_in = req.stats.p.ppp_ibytes;
 -    stats->bytes_out = req.stats.p.ppp_obytes;
 -    stats->pkts_in = req.stats.p.ppp_ipackets;
 -    stats->pkts_out = req.stats.p.ppp_opackets;
 +    stats->bytes_in = data.p.ppp_ibytes;
 +    stats->bytes_out = data.p.ppp_obytes;
 +    stats->pkts_in = data.p.ppp_ipackets;
 +    stats->pkts_out = data.p.ppp_opackets;
  
      if (stats->bytes_in < previbytes)
        ++iwraps;
@@@ -2109,7 -2293,7 +2298,7 @@@ int cifdefaultroute (int unit, u_int32_
      return 1;
  }
  
 -#ifdef INET6
 +#ifdef PPP_WITH_IPV6CP
  /*
   * /proc/net/ipv6_route parsing stuff.
   */
@@@ -2299,7 -2483,7 +2488,7 @@@ int cif6defaultroute (int unit, eui64_
  
      return 1;
  }
 -#endif /* INET6 */
 +#endif /* PPP_WITH_IPV6CP */
  
  /********************************************************************
   *
@@@ -2925,15 -3109,15 +3114,15 @@@ int sifdown (int u
      if (if_is_up && --if_is_up > 0)
        return 1;
  
 -#ifdef INET6
 +#ifdef PPP_WITH_IPV6CP
      if (if6_is_up)
        return 1;
 -#endif /* INET6 */
 +#endif /* PPP_WITH_IPV6CP */
  
      return setifstate(u, 0);
  }
  
 -#ifdef INET6
 +#ifdef PPP_WITH_IPV6CP
  /********************************************************************
   *
   * sif6up - Config the interface up for IPv6
@@@ -2964,7 -3148,7 +3153,7 @@@ int sif6down (int u
  
      return setifstate(u, 0);
  }
 -#endif /* INET6 */
 +#endif /* PPP_WITH_IPV6CP */
  
  /********************************************************************
   *
@@@ -3152,7 -3336,7 +3341,7 @@@ int cifaddr (int unit, u_int32_t our_ad
      return 1;
  }
  
 -#ifdef INET6
 +#ifdef PPP_WITH_IPV6CP
  /********************************************************************
   *
   * sif6addr_rtnetlink - Config the interface with both IPv6 link-local addresses via rtnetlink
@@@ -3413,7 -3597,7 +3602,7 @@@ int cif6addr (int unit, eui64_t our_eui
      }
      return 1;
  }
 -#endif /* INET6 */
 +#endif /* PPP_WITH_IPV6CP */
  
  /*
   * get_pty - get a pty master/slave pair and chown the slave side