]> git.ozlabs.org Git - ppp.git/commitdiff
pppd: Fix setting IPv6 peer address (#212)
authorpali <7141871+pali@users.noreply.github.com>
Sun, 3 Jan 2021 23:01:44 +0000 (00:01 +0100)
committerGitHub <noreply@github.com>
Sun, 3 Jan 2021 23:01:44 +0000 (10:01 +1100)
On Linux IPv6 peer address cannot be set via SIOCSIFDSTADDR ioctl like it
is for IPv4 peer address. Linux kernel does not support SIOCSIFDSTADDR for
AF_INET6 PPP interfaces.

The only way how to set IPv6 peer address on Linux is via kernel netlink
interface which is just a little bit complicated compared to one ioctl
call.

Linux kernel for a long time automatically adds IPv6 peer address from
interface into route table so it is not needed to explicitly set routing
for remote peer address. pppd already does not do it for kernel versions
newer than 2.1.16. So the same check is used also for IPv6 peer route
address.

Prior this patch ppp interface was configured as:

    $ ip -6 address show dev ppp0
    2: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 state UNKNOWN qlen 3
        inet6 fe80::2/128 scope link
           valid_lft forever preferred_lft forever

    $ ip -6 route show dev ppp0
    fe80::1 metric 1 pref medium
    fe80::2 proto kernel metric 256 pref medium

And after applying this patch as:

    $ ip -6 address show dev ppp0
    2: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 state UNKNOWN qlen 3
        inet6 fe80::2 peer fe80::1/128 scope link
           valid_lft forever preferred_lft forever

    $ ip -6 route show dev ppp0
    fe80::1 proto kernel metric 256 pref medium
    fe80::2 proto kernel metric 256 pref medium

As can be seen IPv6 peer address is now correctly set on the interface and
also kernel correctly fill route table for IPv6 peer address.

Please note that old ifconfig utility cannot show nor change IPv6 peer
address. Peer address is supported only for IPv4 addresses as opposite of
the local addresses where both IPv4 and IPv6 are supported. It is because
old ifconfig utility is also using ioctl interface which cannot handle it.
Therefore for any testing it is really required ip utility or other utility
with netlink interface (and not ioctl interface).

Signed-off-by: Pali Rohár <pali@kernel.org>
pppd/sys-linux.c

index b7972b908e1c972797f0c66fd4685cf9070ee45d..85033d97124fc0c49e2b274b80277aefc8e7893e 100644 (file)
 #include <linux/ppp_defs.h>
 #include <linux/if_ppp.h>
 
+#ifdef INET6
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_addr.h>
+#endif
+
 #include "pppd.h"
 #include "fsm.h"
 #include "ipcp.h"
@@ -2818,6 +2824,135 @@ int cifaddr (int unit, u_int32_t our_adr, u_int32_t his_adr)
 }
 
 #ifdef INET6
+static int append_peer_ipv6_address(unsigned int iface, struct in6_addr *local_addr, struct in6_addr *remote_addr)
+{
+    struct msghdr msg;
+    struct sockaddr_nl sa;
+    struct iovec iov;
+    struct nlmsghdr *nlmsg;
+    struct ifaddrmsg *ifa;
+    struct rtattr *local_rta;
+    struct rtattr *remote_rta;
+    char buf[NLMSG_LENGTH(sizeof(*ifa) + RTA_LENGTH(sizeof(*local_addr)) + RTA_LENGTH(sizeof(*remote_addr)))];
+    ssize_t nlmsg_len;
+    struct nlmsgerr *errmsg;
+    int one;
+    int fd;
+
+    fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+    if (fd < 0)
+        return 0;
+
+    /* do not ask for error message content */
+    one = 1;
+    setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &one, sizeof(one));
+
+    memset(&sa, 0, sizeof(sa));
+    sa.nl_family = AF_NETLINK;
+    sa.nl_pid = 0;
+    sa.nl_groups = 0;
+
+    if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
+        close(fd);
+        return 0;
+    }
+
+    memset(buf, 0, sizeof(buf));
+
+    nlmsg = (struct nlmsghdr *)buf;
+    nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(*ifa) + RTA_LENGTH(sizeof(*local_addr)) + RTA_LENGTH(sizeof(*remote_addr)));
+    nlmsg->nlmsg_type = RTM_NEWADDR;
+    nlmsg->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE;
+    nlmsg->nlmsg_seq = 1;
+    nlmsg->nlmsg_pid = 0;
+
+    ifa = NLMSG_DATA(nlmsg);
+    ifa->ifa_family = AF_INET6;
+    ifa->ifa_prefixlen = 128;
+    ifa->ifa_flags = 0;
+    ifa->ifa_scope = RT_SCOPE_UNIVERSE;
+    ifa->ifa_index = iface;
+
+    local_rta = IFA_RTA(ifa);
+    local_rta->rta_len = RTA_LENGTH(sizeof(*local_addr));
+    local_rta->rta_type = IFA_LOCAL;
+    memcpy(RTA_DATA(local_rta), local_addr, sizeof(*local_addr));
+
+    remote_rta = (struct rtattr *)((char *)local_rta + local_rta->rta_len);
+    remote_rta->rta_len = RTA_LENGTH(sizeof(*remote_addr));
+    remote_rta->rta_type = IFA_ADDRESS;
+    memcpy(RTA_DATA(remote_rta), remote_addr, sizeof(*remote_addr));
+
+    memset(&sa, 0, sizeof(sa));
+    sa.nl_family = AF_NETLINK;
+    sa.nl_pid = 0;
+    sa.nl_groups = 0;
+
+    memset(&iov, 0, sizeof(iov));
+    iov.iov_base = nlmsg;
+    iov.iov_len = nlmsg->nlmsg_len;
+
+    memset(&msg, 0, sizeof(msg));
+    msg.msg_name = &sa;
+    msg.msg_namelen = sizeof(sa);
+    msg.msg_iov = &iov;
+    msg.msg_iovlen = 1;
+    msg.msg_control = NULL;
+    msg.msg_controllen = 0;
+    msg.msg_flags = 0;
+
+    if (sendmsg(fd, &msg, 0) < 0) {
+        close(fd);
+        return 0;
+    }
+
+    memset(&iov, 0, sizeof(iov));
+    iov.iov_base = buf;
+    iov.iov_len = sizeof(buf);
+
+    memset(&msg, 0, sizeof(msg));
+    msg.msg_name = NULL;
+    msg.msg_namelen = 0;
+    msg.msg_iov = &iov;
+    msg.msg_iovlen = 1;
+    msg.msg_control = NULL;
+    msg.msg_controllen = 0;
+    msg.msg_flags = 0;
+
+    nlmsg_len = recvmsg(fd, &msg, 0);
+    close(fd);
+
+    if (nlmsg_len < 0)
+        return 0;
+
+    if ((size_t)nlmsg_len < sizeof(*nlmsg)) {
+        errno = EINVAL;
+        return 0;
+    }
+
+    nlmsg = (struct nlmsghdr *)buf;
+
+    /* acknowledgment packet for NLM_F_ACK is NLMSG_ERROR */
+    if (nlmsg->nlmsg_type != NLMSG_ERROR) {
+        errno = EINVAL;
+        return 0;
+    }
+
+    if ((size_t)nlmsg_len < NLMSG_LENGTH(sizeof(*errmsg))) {
+        errno = EINVAL;
+        return 0;
+    }
+
+    errmsg = NLMSG_DATA(nlmsg);
+
+    /* error == 0 indicates success */
+    if (errmsg->error == 0)
+        return 1;
+
+    errno = -errmsg->error;
+    return 0;
+}
+
 /********************************************************************
  *
  * sif6addr - Config the interface with an IPv6 link-local address
@@ -2827,6 +2962,7 @@ int sif6addr (int unit, eui64_t our_eui64, eui64_t his_eui64)
     struct in6_ifreq ifr6;
     struct ifreq ifr;
     struct in6_rtmsg rt6;
+    struct in6_addr remote_addr;
 
     if (sock6_fd < 0) {
        errno = -sock6_fd;
@@ -2851,17 +2987,28 @@ int sif6addr (int unit, eui64_t our_eui64, eui64_t his_eui64)
        return 0;
     }
 
-    /* Route to remote host */
-    memset(&rt6, 0, sizeof(rt6));
-    IN6_LLADDR_FROM_EUI64(rt6.rtmsg_dst, his_eui64);
-    rt6.rtmsg_flags = RTF_UP;
-    rt6.rtmsg_dst_len = 128;
-    rt6.rtmsg_ifindex = ifr.ifr_ifindex;
-    rt6.rtmsg_metric = 1;
+    if (kernel_version >= KVERSION(2,1,16)) {
+        /* Set remote peer address (and route for it) */
+        IN6_LLADDR_FROM_EUI64(remote_addr, his_eui64);
+        if (!append_peer_ipv6_address(ifr.ifr_ifindex, &ifr6.ifr6_addr, &remote_addr)) {
+            error("sif6addr: setting remote peer address failed: %m");
+            return 0;
+        }
+    }
 
-    if (ioctl(sock6_fd, SIOCADDRT, &rt6) < 0) {
-       error("sif6addr: ioctl(SIOCADDRT): %m (line %d)", __LINE__);
-       return 0;
+    if (kernel_version < KVERSION(2,1,16)) {
+        /* Route to remote host */
+        memset(&rt6, 0, sizeof(rt6));
+        IN6_LLADDR_FROM_EUI64(rt6.rtmsg_dst, his_eui64);
+        rt6.rtmsg_flags = RTF_UP;
+        rt6.rtmsg_dst_len = 128;
+        rt6.rtmsg_ifindex = ifr.ifr_ifindex;
+        rt6.rtmsg_metric = 1;
+
+        if (ioctl(sock6_fd, SIOCADDRT, &rt6) < 0) {
+            error("sif6addr: ioctl(SIOCADDRT): %m (line %d)", __LINE__);
+            return 0;
+        }
     }
 
     return 1;