+/********************************************************************
+ *
+ * sif6addr_rtnetlink - Config the interface with both IPv6 link-local addresses via rtnetlink
+ */
+static int sif6addr_rtnetlink(unsigned int iface, eui64_t our_eui64, eui64_t his_eui64)
+{
+ struct {
+ struct nlmsghdr nlh;
+ struct ifaddrmsg ifa;
+ struct {
+ struct rtattr rta;
+ struct in6_addr addr;
+ } addrs[2];
+ } 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("sif6addr_rtnetlink: socket(NETLINK_ROUTE): %m (line %d)", __LINE__);
+ return 0;
+ }
+
+ /*
+ * Tell kernel to not send to us payload of acknowledgment error message.
+ * NETLINK_CAP_ACK option is supported since Linux kernel version 4.3 and
+ * older kernel versions always send full payload in acknowledgment netlink
+ * message. We ignore payload of this message as we need only error code,
+ * to check if our set remote peer address request succeeded or failed.
+ * So ignore return value from the following setsockopt() call as setting
+ * option NETLINK_CAP_ACK means for us just a kernel hint / optimization.
+ */
+ 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("sif6addr_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_NEWADDR;
+ nlreq.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE;
+ nlreq.ifa.ifa_family = AF_INET6;
+ nlreq.ifa.ifa_prefixlen = 128;
+ nlreq.ifa.ifa_flags = IFA_F_NODAD | IFA_F_PERMANENT;
+ nlreq.ifa.ifa_scope = RT_SCOPE_LINK;
+ nlreq.ifa.ifa_index = iface;
+ nlreq.addrs[0].rta.rta_len = sizeof(nlreq.addrs[0]);
+ nlreq.addrs[0].rta.rta_type = IFA_LOCAL;
+ IN6_LLADDR_FROM_EUI64(nlreq.addrs[0].addr, our_eui64);
+ nlreq.addrs[1].rta.rta_len = sizeof(nlreq.addrs[1]);
+ nlreq.addrs[1].rta.rta_type = IFA_ADDRESS;
+ IN6_LLADDR_FROM_EUI64(nlreq.addrs[1].addr, his_eui64);
+
+ 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("sif6addr_rtnetlink: sendmsg(RTM_NEWADDR/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("sif6addr_rtnetlink: recvmsg(NLM_F_ACK): %m (line %d)", __LINE__);
+ close(fd);
+ return 0;
+ }
+
+ close(fd);
+
+ if (nladdr.nl_family != AF_NETLINK) {
+ error("sif6addr_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("sif6addr_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("sif6addr_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 3.11 do not support setting IPv6 peer
+ * addresses and error response is expected. On older kernel versions
+ * do not show this error message. On error pppd tries to fallback to
+ * the old IOCTL method.
+ */
+ if (kernel_version >= KVERSION(3,11,0))
+ error("sif6addr_rtnetlink: %s (line %d)", strerror(-nlresp.nlerr.error), __LINE__);
+ return 0;
+ }
+
+ return 1;
+}
+