From c3af52231184ade3cf728ec60c9de9e87c2622ad Mon Sep 17 00:00:00 2001 From: pali <7141871+pali@users.noreply.github.com> Date: Mon, 4 Jan 2021 00:01:44 +0100 Subject: [PATCH] pppd: Fix setting IPv6 peer address (#212) MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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: 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: 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 --- pppd/sys-linux.c | 167 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 157 insertions(+), 10 deletions(-) diff --git a/pppd/sys-linux.c b/pppd/sys-linux.c index b7972b9..85033d9 100644 --- a/pppd/sys-linux.c +++ b/pppd/sys-linux.c @@ -121,6 +121,12 @@ #include #include +#ifdef INET6 +#include +#include +#include +#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; -- 2.39.2