/* * Copyright (c)2019 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * * Change Date: 2026-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. */ /****/ #include "../node/Constants.hpp" #include // #define ZT_NETLINK_TRACE #ifdef __LINUX__ #include "LinuxNetLink.hpp" #include #include #ifndef IFNAMSIZ #define IFNAMSIZ 16 #endif const int ZT_RTE_METRIC = 5000; namespace ZeroTier { struct nl_route_req { struct nlmsghdr nl; struct rtmsg rt; char buf[8192]; }; struct nl_if_req { struct nlmsghdr nl; struct ifinfomsg ifa; char buf[8192]; }; struct nl_adr_req { struct nlmsghdr nl; struct ifaddrmsg ifa; char buf[8192]; }; LinuxNetLink::LinuxNetLink() : _t(), _running(false), _seq(0), _interfaces(), _if_m(), _fd(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)), _la({ 0 }) { // set socket timeout to 1 sec so we're not permablocking recv() calls _setSocketTimeout(_fd, 1); _la.nl_family = AF_NETLINK; _la.nl_pid = 0; // getpid()+1; _la.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE | RTMGRP_NOTIFY; if (bind(_fd, (struct sockaddr*)&_la, sizeof(_la))) { fprintf(stderr, "Error connecting to RTNETLINK: %s\n", strerror(errno)); ::exit(1); } _requestIPv4Routes(); _requestIPv6Routes(); _requestInterfaceList(); _running = true; _t = Thread::start(this); } LinuxNetLink::~LinuxNetLink() { _running = false; Thread::join(_t); ::close(_fd); } void LinuxNetLink::_setSocketTimeout(int fd, int seconds) { struct timeval tv; tv.tv_sec = seconds; tv.tv_usec = 0; if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)) != 0) { #ifdef ZT_NETLINK_TRACE fprintf(stderr, "setsockopt failed: %s\n", strerror(errno)); #endif } } #define ZT_NL_BUF_SIZE 16384 int LinuxNetLink::_doRecv(int fd) { char* buf = nullptr; if (posix_memalign((void**)&buf, 16, ZT_NL_BUF_SIZE) != 0) { fprintf(stderr, "malloc failed!\n"); ::exit(1); } if (! buf) { fprintf(stderr, "malloc failed!\n"); ::exit(1); } char* p = NULL; struct nlmsghdr* nlp; int nll = 0; int rtn = 0; p = buf; for (;;) { rtn = recv(fd, p, ZT_NL_BUF_SIZE - nll, 0); if (rtn > 0) { nlp = (struct nlmsghdr*)p; if (nlp->nlmsg_type == NLMSG_ERROR && (nlp->nlmsg_flags & NLM_F_ACK) != NLM_F_ACK) { struct nlmsgerr* err = (struct nlmsgerr*)NLMSG_DATA(nlp); if (err->error != 0) { #ifdef ZT_NETLINK_TRACE fprintf(stderr, "rtnetlink error: %s\n", strerror(-(err->error))); #endif } p = buf; nll = 0; break; } if (nlp->nlmsg_type == NLMSG_NOOP) { break; } if ((nlp->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI || (nlp->nlmsg_type == NLMSG_DONE)) { if (nlp->nlmsg_type == NLMSG_DONE) { _processMessage(nlp, nll); p = buf; nll = 0; break; } p += rtn; nll += rtn; } if (nlp->nlmsg_type == NLMSG_OVERRUN) { #ifdef ZT_NETLINK_TRACE fprintf(stderr, "NLMSG_OVERRUN: Data lost\n"); #endif p = buf; nll = 0; break; } nll += rtn; _processMessage(nlp, nll); p = buf; nll = 0; break; } else { break; } } free(buf); return rtn; } void LinuxNetLink::threadMain() throw() { int rtn = 0; while (_running) { rtn = _doRecv(_fd); if (rtn <= 0) { Thread::sleep(250); continue; } } } void LinuxNetLink::_processMessage(struct nlmsghdr* nlp, int nll) { for (; NLMSG_OK(nlp, nll); nlp = NLMSG_NEXT(nlp, nll)) { switch (nlp->nlmsg_type) { case RTM_NEWLINK: _linkAdded(nlp); break; case RTM_DELLINK: _linkDeleted(nlp); break; case RTM_NEWADDR: _ipAddressAdded(nlp); break; case RTM_DELADDR: _ipAddressDeleted(nlp); break; case RTM_NEWROUTE: _routeAdded(nlp); break; case RTM_DELROUTE: _routeDeleted(nlp); break; default: break; } } } void LinuxNetLink::_ipAddressAdded(struct nlmsghdr* nlp) { #ifdef ZT_NETLINK_TRACE struct ifaddrmsg* ifap = (struct ifaddrmsg*)NLMSG_DATA(nlp); struct rtattr* rtap = (struct rtattr*)IFA_RTA(ifap); int ifal = IFA_PAYLOAD(nlp); char addr[40] = { 0 }; char local[40] = { 0 }; char label[40] = { 0 }; char bcast[40] = { 0 }; for (; RTA_OK(rtap, ifal); rtap = RTA_NEXT(rtap, ifal)) { switch (rtap->rta_type) { case IFA_ADDRESS: inet_ntop(ifap->ifa_family, RTA_DATA(rtap), addr, 40); break; case IFA_LOCAL: inet_ntop(ifap->ifa_family, RTA_DATA(rtap), local, 40); break; case IFA_LABEL: memcpy(label, RTA_DATA(rtap), 40); break; case IFA_BROADCAST: inet_ntop(ifap->ifa_family, RTA_DATA(rtap), bcast, 40); break; } } fprintf(stderr, "Added IP Address %s local: %s label: %s broadcast: %s\n", addr, local, label, bcast); #endif } void LinuxNetLink::_ipAddressDeleted(struct nlmsghdr* nlp) { #ifdef ZT_NETLINK_TRACE struct ifaddrmsg* ifap = (struct ifaddrmsg*)NLMSG_DATA(nlp); struct rtattr* rtap = (struct rtattr*)IFA_RTA(ifap); int ifal = IFA_PAYLOAD(nlp); char addr[40] = { 0 }; char local[40] = { 0 }; char label[40] = { 0 }; char bcast[40] = { 0 }; for (; RTA_OK(rtap, ifal); rtap = RTA_NEXT(rtap, ifal)) { switch (rtap->rta_type) { case IFA_ADDRESS: inet_ntop(ifap->ifa_family, RTA_DATA(rtap), addr, 40); break; case IFA_LOCAL: inet_ntop(ifap->ifa_family, RTA_DATA(rtap), local, 40); break; case IFA_LABEL: memcpy(label, RTA_DATA(rtap), 40); break; case IFA_BROADCAST: inet_ntop(ifap->ifa_family, RTA_DATA(rtap), bcast, 40); break; } } fprintf(stderr, "Removed IP Address %s local: %s label: %s broadcast: %s\n", addr, local, label, bcast); #endif } void LinuxNetLink::_routeAdded(struct nlmsghdr* nlp) { char dsts[40] = { 0 }; char gws[40] = { 0 }; char srcs[40] = { 0 }; char ifs[16] = { 0 }; char ms[24] = { 0 }; struct rtmsg* rtp = (struct rtmsg*)NLMSG_DATA(nlp); struct rtattr* rtap = (struct rtattr*)RTM_RTA(rtp); int rtl = RTM_PAYLOAD(nlp); Route r; bool wecare = false; for (; RTA_OK(rtap, rtl); rtap = RTA_NEXT(rtap, rtl)) { switch (rtap->rta_type) { case RTA_DST: switch (rtp->rtm_family) { case AF_INET: inet_ntop(rtp->rtm_family, RTA_DATA(rtap), dsts, 24); r.target.set(RTA_DATA(rtap), 4, 0); wecare = true; break; case AF_INET6: inet_ntop(rtp->rtm_family, RTA_DATA(rtap), dsts, 24); r.target.set(RTA_DATA(rtap), 16, 0); wecare = true; break; } break; case RTA_SRC: switch (rtp->rtm_family) { case AF_INET: inet_ntop(rtp->rtm_family, RTA_DATA(rtap), srcs, 24); r.src.set(RTA_DATA(rtap), 4, 0); wecare = true; break; case AF_INET6: inet_ntop(rtp->rtm_family, RTA_DATA(rtap), srcs, 24); r.src.set(RTA_DATA(rtap), 16, 0); wecare = true; break; } break; case RTA_GATEWAY: switch (rtp->rtm_family) { case AF_INET: inet_ntop(rtp->rtm_family, RTA_DATA(rtap), gws, 24); r.via.set(RTA_DATA(rtap), 4, 0); wecare = true; break; case AF_INET6: inet_ntop(rtp->rtm_family, RTA_DATA(rtap), gws, 24); r.via.set(RTA_DATA(rtap), 16, 0); wecare = true; break; } break; case RTA_OIF: switch (rtp->rtm_family) { case AF_INET: r.ifidx = *((int*)RTA_DATA(rtap)); wecare = true; break; case AF_INET6: r.ifidx = *((int*)RTA_DATA(rtap)); wecare = true; break; } sprintf(ifs, "%d", *((int*)RTA_DATA(rtap))); break; } } if (wecare) { Mutex::Lock rl(_routes_m); _routes[r.target].insert(r); } #ifdef ZT_NETLINK_TRACE sprintf(ms, "%d", rtp->rtm_dst_len); fprintf(stderr, "Route Added: dst %s/%s gw %s src %s if %s\n", dsts, ms, gws, srcs, ifs); #endif } void LinuxNetLink::_routeDeleted(struct nlmsghdr* nlp) { char dsts[40] = { 0 }; char gws[40] = { 0 }; char srcs[40] = { 0 }; char ifs[16] = { 0 }; char ms[24] = { 0 }; struct rtmsg* rtp = (struct rtmsg*)NLMSG_DATA(nlp); struct rtattr* rtap = (struct rtattr*)RTM_RTA(rtp); int rtl = RTM_PAYLOAD(nlp); Route r; bool wecare = false; for (; RTA_OK(rtap, rtl); rtap = RTA_NEXT(rtap, rtl)) { switch (rtap->rta_type) { case RTA_DST: switch (rtp->rtm_family) { case AF_INET: inet_ntop(rtp->rtm_family, RTA_DATA(rtap), dsts, 24); r.target.set(RTA_DATA(rtap), 4, 0); wecare = true; break; case AF_INET6: inet_ntop(rtp->rtm_family, RTA_DATA(rtap), dsts, 24); r.target.set(RTA_DATA(rtap), 16, 0); wecare = true; break; } break; case RTA_SRC: switch (rtp->rtm_family) { case AF_INET: inet_ntop(rtp->rtm_family, RTA_DATA(rtap), srcs, 24); r.src.set(RTA_DATA(rtap), 4, 0); wecare = true; break; case AF_INET6: inet_ntop(rtp->rtm_family, RTA_DATA(rtap), srcs, 24); r.src.set(RTA_DATA(rtap), 16, 0); wecare = true; break; } break; case RTA_GATEWAY: switch (rtp->rtm_family) { case AF_INET: inet_ntop(rtp->rtm_family, RTA_DATA(rtap), gws, 24); r.via.set(RTA_DATA(rtap), 4, 0); wecare = true; break; case AF_INET6: inet_ntop(rtp->rtm_family, RTA_DATA(rtap), gws, 24); r.via.set(RTA_DATA(rtap), 16, 0); wecare = true; break; } break; case RTA_OIF: switch (rtp->rtm_family) { case AF_INET: r.ifidx = *((int*)RTA_DATA(rtap)); wecare = true; break; case AF_INET6: r.ifidx = *((int*)RTA_DATA(rtap)); wecare = true; break; } sprintf(ifs, "%d", *((int*)RTA_DATA(rtap))); break; } } if (wecare) { Mutex::Lock rl(_routes_m); _routes[r.target].erase(r); } #ifdef ZT_NETLINK_TRACE sprintf(ms, "%d", rtp->rtm_dst_len); fprintf(stderr, "Route Deleted: dst %s/%s gw %s src %s if %s\n", dsts, ms, gws, srcs, ifs); #endif } void LinuxNetLink::_linkAdded(struct nlmsghdr* nlp) { unsigned char mac_bin[6] = { 0 }; unsigned int mtu = 0; char ifname[IFNAMSIZ] = { 0 }; struct ifinfomsg* ifip = (struct ifinfomsg*)NLMSG_DATA(nlp); struct rtattr* rtap = (struct rtattr*)IFLA_RTA(ifip); int ifil = RTM_PAYLOAD(nlp); const char* ptr = (const char*)0; for (; RTA_OK(rtap, ifil); rtap = RTA_NEXT(rtap, ifil)) { switch (rtap->rta_type) { case IFLA_ADDRESS: ptr = (const char*)RTA_DATA(rtap); memcpy(mac_bin, ptr, 6); break; case IFLA_IFNAME: ptr = (const char*)RTA_DATA(rtap); memcpy(ifname, ptr, strlen(ptr)); break; case IFLA_MTU: memcpy(&mtu, RTA_DATA(rtap), sizeof(unsigned int)); break; } } { Mutex::Lock l(_if_m); struct iface_entry& entry = _interfaces[ifip->ifi_index]; entry.index = ifip->ifi_index; memcpy(entry.ifacename, ifname, sizeof(ifname)); snprintf( entry.mac, sizeof(entry.mac), "%.02x:%.02x:%.02x:%.02x:%.02x:%.02x", (unsigned int)mac_bin[0], (unsigned int)mac_bin[1], (unsigned int)mac_bin[2], (unsigned int)mac_bin[3], (unsigned int)mac_bin[4], (unsigned int)mac_bin[5]); memcpy(entry.mac_bin, mac_bin, 6); entry.mtu = mtu; } } void LinuxNetLink::_linkDeleted(struct nlmsghdr* nlp) { unsigned int mtu = 0; char ifname[40] = { 0 }; struct ifinfomsg* ifip = (struct ifinfomsg*)NLMSG_DATA(nlp); struct rtattr* rtap = (struct rtattr*)IFLA_RTA(ifip); int ifil = RTM_PAYLOAD(nlp); const char* ptr = (const char*)0; for (; RTA_OK(rtap, ifil); rtap = RTA_NEXT(rtap, ifil)) { switch (rtap->rta_type) { case IFLA_IFNAME: ptr = (const char*)RTA_DATA(rtap); memcpy(ifname, ptr, strlen(ptr)); break; case IFLA_MTU: memcpy(&mtu, RTA_DATA(rtap), sizeof(unsigned int)); break; } } { Mutex::Lock l(_if_m); if (_interfaces.contains(ifip->ifi_index)) { _interfaces.erase(ifip->ifi_index); } } } void LinuxNetLink::_requestIPv4Routes() { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; } _setSocketTimeout(fd); struct sockaddr_nl la; la.nl_family = AF_NETLINK; la.nl_pid = 0; // getpid(); la.nl_groups = RTMGRP_IPV4_ROUTE; if (bind(fd, (struct sockaddr*)&la, sizeof(la))) { fprintf(stderr, "Error binding RTNETLINK (_requestIPv4Routes #1): %s\n", strerror(errno)); close(fd); return; } struct nl_route_req req; bzero(&req, sizeof(req)); req.nl.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.nl.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; req.nl.nlmsg_type = RTM_GETROUTE; req.nl.nlmsg_pid = 0; req.nl.nlmsg_seq = ++_seq; req.rt.rtm_family = AF_INET; req.rt.rtm_table = RT_TABLE_MAIN; struct sockaddr_nl pa; bzero(&pa, sizeof(pa)); pa.nl_family = AF_NETLINK; struct msghdr msg; bzero(&msg, sizeof(msg)); msg.msg_name = (void*)&pa; msg.msg_namelen = sizeof(pa); struct iovec iov; bzero(&iov, sizeof(iov)); iov.iov_base = (void*)&req.nl; iov.iov_len = req.nl.nlmsg_len; msg.msg_iov = &iov; msg.msg_iovlen = 1; sendmsg(fd, &msg, 0); _doRecv(fd); close(fd); } void LinuxNetLink::_requestIPv6Routes() { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; } _setSocketTimeout(fd); struct sockaddr_nl la; la.nl_family = AF_NETLINK; la.nl_pid = 0; // getpid(); la.nl_groups = RTMGRP_IPV6_ROUTE; if (bind(fd, (struct sockaddr*)&la, sizeof(struct sockaddr_nl))) { fprintf(stderr, "Error binding RTNETLINK (_requestIPv6Routes #1): %s\n", strerror(errno)); close(fd); return; } struct nl_route_req req; bzero(&req, sizeof(req)); req.nl.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.nl.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; req.nl.nlmsg_type = RTM_GETROUTE; req.nl.nlmsg_pid = 0; req.nl.nlmsg_seq = ++_seq; req.rt.rtm_family = AF_INET6; req.rt.rtm_table = RT_TABLE_MAIN; struct sockaddr_nl pa; bzero(&pa, sizeof(pa)); pa.nl_family = AF_NETLINK; struct msghdr msg; bzero(&msg, sizeof(msg)); msg.msg_name = (void*)&pa; msg.msg_namelen = sizeof(pa); struct iovec iov; bzero(&iov, sizeof(iov)); iov.iov_base = (void*)&req.nl; iov.iov_len = req.nl.nlmsg_len; msg.msg_iov = &iov; msg.msg_iovlen = 1; sendmsg(fd, &msg, 0); _doRecv(fd); close(fd); } void LinuxNetLink::_requestInterfaceList() { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; } _setSocketTimeout(fd); struct sockaddr_nl la; la.nl_family = AF_NETLINK; la.nl_pid = 0; // getpid(); la.nl_groups = RTMGRP_LINK; if (bind(fd, (struct sockaddr*)&la, sizeof(struct sockaddr_nl))) { fprintf(stderr, "Error binding RTNETLINK (_requestInterfaceList #1): %s\n", strerror(errno)); close(fd); return; } struct nl_if_req req; bzero(&req, sizeof(req)); req.nl.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); req.nl.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; req.nl.nlmsg_type = RTM_GETLINK; req.nl.nlmsg_pid = 0; req.nl.nlmsg_seq = ++_seq; req.ifa.ifi_family = AF_UNSPEC; struct sockaddr_nl pa; bzero(&pa, sizeof(pa)); pa.nl_family = AF_NETLINK; struct msghdr msg; bzero(&msg, sizeof(msg)); msg.msg_name = (void*)&pa; msg.msg_namelen = sizeof(pa); struct iovec iov; bzero(&iov, sizeof(iov)); iov.iov_base = (void*)&req.nl; iov.iov_len = req.nl.nlmsg_len; msg.msg_iov = &iov; msg.msg_iovlen = 1; sendmsg(fd, &msg, 0); _doRecv(fd); close(fd); } void LinuxNetLink::addRoute(const InetAddress& target, const InetAddress& via, const InetAddress& src, const char* ifaceName) { if (! target) return; int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; } _setSocketTimeout(fd); struct sockaddr_nl la; bzero(&la, sizeof(la)); la.nl_family = AF_NETLINK; la.nl_pid = 0; // getpid(); if (bind(fd, (struct sockaddr*)&la, sizeof(struct sockaddr_nl))) { fprintf(stderr, "Error binding RTNETLINK (addRoute #1): %s\n", strerror(errno)); close(fd); return; } #ifdef ZT_NETLINK_TRACE char tmp[64]; char tmp2[64]; char tmp3[64]; fprintf(stderr, "Adding Route. target: %s via: %s src: %s iface: %s\n", target.toString(tmp), via.toString(tmp2), src.toString(tmp3), ifaceName); #endif int rtl = sizeof(struct rtmsg); struct nl_route_req req; bzero(&req, sizeof(req)); struct rtattr* rtap = (struct rtattr*)req.buf; rtap->rta_type = RTA_DST; if (target.isV4()) { rtap->rta_len = RTA_LENGTH(sizeof(struct in_addr)); memcpy(RTA_DATA(rtap), &((struct sockaddr_in*)&target)->sin_addr, sizeof(struct in_addr)); } else { rtap->rta_len = RTA_LENGTH(sizeof(struct in6_addr)); memcpy(RTA_DATA(rtap), &((struct sockaddr_in6*)&target)->sin6_addr, sizeof(struct in6_addr)); } rtl += rtap->rta_len; if (via) { /* * Setting a metric keeps zerotier routes from taking priority over physical * At best the computer would use zerotier through the router instead of the LAN. * At worst it stops working at all. * * default via 192.168.82.1 dev eth0 proto dhcp src 192.168.82.169 metric 202 * 10.147.17.0/24 dev zt5u4uptmb proto kernel scope link src 10.147.17.94 * 192.168.82.0/24 dev eth0 proto dhcp scope link src 192.168.82.169 metric 202 * 192.168.82.0/24 via 10.147.17.1 dev zt5u4uptmb proto static metric 5000 * */ rtap = (struct rtattr*)(((char*)rtap) + rtap->rta_len); rtap->rta_type = RTA_PRIORITY; rtap->rta_len = RTA_LENGTH(sizeof(ZT_RTE_METRIC)); memcpy(RTA_DATA(rtap), &ZT_RTE_METRIC, sizeof(ZT_RTE_METRIC)); rtl += rtap->rta_len; rtap = (struct rtattr*)(((char*)rtap) + rtap->rta_len); rtap->rta_type = RTA_GATEWAY; if (via.isV4()) { rtap->rta_len = RTA_LENGTH(sizeof(struct in_addr)); memcpy(RTA_DATA(rtap), &((struct sockaddr_in*)&via)->sin_addr, sizeof(struct in_addr)); } else { rtap->rta_len = RTA_LENGTH(sizeof(struct in6_addr)); memcpy(RTA_DATA(rtap), &((struct sockaddr_in6*)&via)->sin6_addr, sizeof(struct in6_addr)); } rtl += rtap->rta_len; } else if (src) { rtap = (struct rtattr*)(((char*)rtap) + rtap->rta_len); rtap->rta_type = RTA_SRC; if (src.isV4()) { rtap->rta_len = RTA_LENGTH(sizeof(struct in_addr)); memcpy(RTA_DATA(rtap), &((struct sockaddr_in*)&src)->sin_addr, sizeof(struct in_addr)); } else { rtap->rta_len = RTA_LENGTH(sizeof(struct in6_addr)); memcpy(RTA_DATA(rtap), &((struct sockaddr_in6*)&src)->sin6_addr, sizeof(struct in6_addr)); } req.rt.rtm_src_len = src.netmaskBits(); } if (ifaceName != NULL) { int interface_index = _indexForInterface(ifaceName); if (interface_index != -1) { rtap = (struct rtattr*)(((char*)rtap) + rtap->rta_len); rtap->rta_type = RTA_OIF; rtap->rta_len = RTA_LENGTH(sizeof(int)); memcpy(RTA_DATA(rtap), &interface_index, sizeof(int)); rtl += rtap->rta_len; } } req.nl.nlmsg_len = NLMSG_LENGTH(rtl); req.nl.nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE | NLM_F_ACK; req.nl.nlmsg_type = RTM_NEWROUTE; req.nl.nlmsg_pid = 0; req.nl.nlmsg_seq = ++_seq; req.rt.rtm_family = target.ss_family; req.rt.rtm_table = RT_TABLE_MAIN; req.rt.rtm_protocol = RTPROT_STATIC; req.rt.rtm_scope = RT_SCOPE_UNIVERSE; req.rt.rtm_type = RTN_UNICAST; req.rt.rtm_dst_len = target.netmaskBits(); req.rt.rtm_flags = 0; struct sockaddr_nl pa; bzero(&pa, sizeof(pa)); pa.nl_family = AF_NETLINK; struct msghdr msg; bzero(&msg, sizeof(msg)); msg.msg_name = (void*)&pa; msg.msg_namelen = sizeof(pa); struct iovec iov; bzero(&iov, sizeof(iov)); iov.iov_base = (void*)&req.nl; iov.iov_len = req.nl.nlmsg_len; msg.msg_iov = &iov; msg.msg_iovlen = 1; sendmsg(fd, &msg, 0); _doRecv(fd); close(fd); } void LinuxNetLink::delRoute(const InetAddress& target, const InetAddress& via, const InetAddress& src, const char* ifaceName) { if (! target) return; int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; } _setSocketTimeout(fd); struct sockaddr_nl la; la.nl_family = AF_NETLINK; la.nl_pid = 0; // getpid(); if (bind(fd, (struct sockaddr*)&la, sizeof(struct sockaddr_nl))) { fprintf(stderr, "Error binding RTNETLINK (delRoute #1): %s\n", strerror(errno)); close(fd); return; } #ifdef ZT_NETLINK_TRACE char tmp[64]; char tmp2[64]; char tmp3[64]; fprintf(stderr, "Removing Route. target: %s via: %s src: %s iface: %s\n", target.toString(tmp), via.toString(tmp2), src.toString(tmp3), ifaceName); #endif int rtl = sizeof(struct rtmsg); struct nl_route_req req; bzero(&req, sizeof(req)); struct rtattr* rtap = (struct rtattr*)req.buf; rtap->rta_type = RTA_DST; if (target.isV4()) { rtap->rta_len = RTA_LENGTH(sizeof(struct in_addr)); memcpy(RTA_DATA(rtap), &((struct sockaddr_in*)&target)->sin_addr, sizeof(struct in_addr)); } else { rtap->rta_len = RTA_LENGTH(sizeof(struct in6_addr)); memcpy(RTA_DATA(rtap), &((struct sockaddr_in6*)&target)->sin6_addr, sizeof(struct in6_addr)); } rtl += rtap->rta_len; if (via) { rtap = (struct rtattr*)(((char*)rtap) + rtap->rta_len); rtap->rta_type = RTA_GATEWAY; if (via.isV4()) { rtap->rta_len = RTA_LENGTH(sizeof(struct in_addr)); memcpy(RTA_DATA(rtap), &((struct sockaddr_in*)&via)->sin_addr, sizeof(struct in_addr)); } else { rtap->rta_len = RTA_LENGTH(sizeof(struct in6_addr)); memcpy(RTA_DATA(rtap), &((struct sockaddr_in6*)&via)->sin6_addr, sizeof(struct in6_addr)); } rtl += rtap->rta_len; } else if (src) { rtap = (struct rtattr*)(((char*)rtap) + rtap->rta_len); rtap->rta_type = RTA_SRC; if (src.isV4()) { rtap->rta_len = RTA_LENGTH(sizeof(struct in_addr)); memcpy(RTA_DATA(rtap), &((struct sockaddr_in*)&src)->sin_addr, sizeof(struct in_addr)); } else { rtap->rta_len = RTA_LENGTH(sizeof(struct in6_addr)); memcpy(RTA_DATA(rtap), &((struct sockaddr_in6*)&src)->sin6_addr, sizeof(struct in6_addr)); } req.rt.rtm_src_len = src.netmaskBits(); } if (ifaceName != NULL) { int interface_index = _indexForInterface(ifaceName); if (interface_index != -1) { rtap = (struct rtattr*)(((char*)rtap) + rtap->rta_len); rtap->rta_type = RTA_OIF; rtap->rta_len = RTA_LENGTH(sizeof(int)); memcpy(RTA_DATA(rtap), &interface_index, sizeof(int)); rtl += rtap->rta_len; } } req.nl.nlmsg_len = NLMSG_LENGTH(rtl); req.nl.nlmsg_flags = NLM_F_REQUEST; req.nl.nlmsg_type = RTM_DELROUTE; req.nl.nlmsg_pid = 0; req.nl.nlmsg_seq = ++_seq; req.rt.rtm_family = target.ss_family; req.rt.rtm_table = RT_TABLE_MAIN; req.rt.rtm_protocol = RTPROT_STATIC; req.rt.rtm_scope = RT_SCOPE_UNIVERSE; req.rt.rtm_type = RTN_UNICAST; req.rt.rtm_dst_len = target.netmaskBits(); req.rt.rtm_flags = 0; struct sockaddr_nl pa; bzero(&pa, sizeof(pa)); pa.nl_family = AF_NETLINK; struct msghdr msg; bzero(&msg, sizeof(msg)); msg.msg_name = (void*)&pa; msg.msg_namelen = sizeof(pa); struct iovec iov; bzero(&iov, sizeof(iov)); iov.iov_base = (void*)&req.nl; iov.iov_len = req.nl.nlmsg_len; msg.msg_iov = &iov; msg.msg_iovlen = 1; sendmsg(fd, &msg, 0); _doRecv(fd); close(fd); } void LinuxNetLink::addAddress(const InetAddress& addr, const char* iface) { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; } _setSocketTimeout(fd); struct sockaddr_nl la; memset(&la, 0, sizeof(la)); la.nl_family = AF_NETLINK; la.nl_pid = 0; // getpid(); if (addr.isV4()) { la.nl_groups = RTMGRP_IPV4_IFADDR; } else { la.nl_groups = RTMGRP_IPV6_IFADDR; } if (bind(fd, (struct sockaddr*)&la, sizeof(struct sockaddr_nl))) { fprintf(stderr, "Error binding RTNETLINK (addAddress #1): %s\n", strerror(errno)); close(fd); return; } #ifdef ZT_NETLINK_TRACE char tmp[128]; fprintf(stderr, "Adding IP address %s to interface %s\n", addr.toString(tmp), iface); #endif int interface_index = _indexForInterface(iface); for (int reps = 0; interface_index == -1 && reps < 10; ++reps) { Thread::sleep(100); interface_index = _indexForInterface(iface); } if (interface_index == -1) { fprintf(stderr, "Unable to find index for interface %s\n", iface); close(fd); return; } int rtl = sizeof(struct ifaddrmsg); struct nl_adr_req req; bzero(&req, sizeof(struct nl_adr_req)); struct rtattr* rtap = (struct rtattr*)req.buf; ; if (addr.isV4()) { struct sockaddr_in* addr_v4 = (struct sockaddr_in*)&addr; rtap->rta_type = IFA_ADDRESS; rtap->rta_len = RTA_LENGTH(sizeof(struct in_addr)); memcpy(RTA_DATA(rtap), &addr_v4->sin_addr, sizeof(struct in_addr)); rtl += rtap->rta_len; rtap = (struct rtattr*)(((char*)rtap) + rtap->rta_len); rtap->rta_type = IFA_LOCAL; rtap->rta_len = RTA_LENGTH(sizeof(struct in_addr)); memcpy(RTA_DATA(rtap), &addr_v4->sin_addr, sizeof(struct in_addr)); rtl += rtap->rta_len; InetAddress broadcast = addr.broadcast(); if (broadcast) { rtap = (struct rtattr*)(((char*)rtap) + rtap->rta_len); struct sockaddr_in* bcast = (struct sockaddr_in*)&broadcast; rtap->rta_type = IFA_BROADCAST; rtap->rta_len = RTA_LENGTH(sizeof(struct in_addr)); memcpy(RTA_DATA(rtap), &bcast->sin_addr, sizeof(struct in_addr)); rtl += rtap->rta_len; } } else { // V6 rtap->rta_type = IFA_ADDRESS; struct sockaddr_in6* addr_v6 = (struct sockaddr_in6*)&addr; rtap->rta_len = RTA_LENGTH(sizeof(struct in6_addr)); memcpy(RTA_DATA(rtap), &addr_v6->sin6_addr, sizeof(struct in6_addr)); rtl += rtap->rta_len; } if (iface) { rtap = (struct rtattr*)(((char*)rtap) + rtap->rta_len); rtap->rta_type = IFA_LABEL; rtap->rta_len = RTA_LENGTH(strlen(iface)); memcpy(RTA_DATA(rtap), iface, strlen(iface)); rtl += rtap->rta_len; } req.nl.nlmsg_len = NLMSG_LENGTH(rtl); req.nl.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL; req.nl.nlmsg_type = RTM_NEWADDR; req.nl.nlmsg_pid = 0; req.nl.nlmsg_seq = ++_seq; req.ifa.ifa_family = addr.ss_family; req.ifa.ifa_prefixlen = addr.port(); req.ifa.ifa_flags = IFA_F_PERMANENT; req.ifa.ifa_scope = 0; req.ifa.ifa_index = interface_index; struct sockaddr_nl pa; bzero(&pa, sizeof(sockaddr_nl)); pa.nl_family = AF_NETLINK; struct msghdr msg; bzero(&msg, sizeof(msg)); msg.msg_name = (void*)&pa; msg.msg_namelen = sizeof(pa); struct iovec iov; iov.iov_base = (void*)&req.nl; iov.iov_len = req.nl.nlmsg_len; msg.msg_iov = &iov; msg.msg_iovlen = 1; sendmsg(fd, &msg, 0); _doRecv(fd); close(fd); } void LinuxNetLink::removeAddress(const InetAddress& addr, const char* iface) { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (fd == -1) { fprintf(stderr, "Error opening RTNETLINK socket: %s\n", strerror(errno)); return; } _setSocketTimeout(fd); struct sockaddr_nl la; la.nl_family = AF_NETLINK; la.nl_pid = 0; // getpid(); if (addr.isV4()) { la.nl_groups = RTMGRP_IPV4_IFADDR; } else { la.nl_groups = RTMGRP_IPV6_IFADDR; } if (bind(fd, (struct sockaddr*)&la, sizeof(struct sockaddr_nl))) { fprintf(stderr, "Error binding RTNETLINK (removeAddress #1): %s\n", strerror(errno)); close(fd); return; } #ifdef ZT_NETLINK_TRACE char tmp[128]; fprintf(stderr, "Removing IP address %s from interface %s\n", addr.toString(tmp), iface); #endif int interface_index = _indexForInterface(iface); if (interface_index == -1) { fprintf(stderr, "Unable to find index for interface %s\n", iface); close(fd); return; } int rtl = sizeof(struct ifaddrmsg); struct nl_adr_req req; bzero(&req, sizeof(struct nl_adr_req)); struct rtattr* rtap = (struct rtattr*)req.buf; if (addr.isV4()) { struct sockaddr_in* addr_v4 = (struct sockaddr_in*)&addr; rtap->rta_type = IFA_ADDRESS; rtap->rta_len = RTA_LENGTH(sizeof(struct in_addr)); memcpy(RTA_DATA(rtap), &addr_v4->sin_addr, sizeof(struct in_addr)); rtl += rtap->rta_len; rtap = (struct rtattr*)(((char*)rtap) + rtap->rta_len); rtap->rta_type = IFA_LOCAL; rtap->rta_len = RTA_LENGTH(sizeof(struct in_addr)); memcpy(RTA_DATA(rtap), &addr_v4->sin_addr, sizeof(struct in_addr)); rtl += rtap->rta_len; InetAddress broadcast = addr.broadcast(); if (broadcast) { rtap = (struct rtattr*)(((char*)rtap) + rtap->rta_len); struct sockaddr_in* bcast = (struct sockaddr_in*)&broadcast; rtap->rta_type = IFA_BROADCAST; rtap->rta_len = RTA_LENGTH(sizeof(struct in_addr)); memcpy(RTA_DATA(rtap), &bcast->sin_addr, sizeof(struct in_addr)); rtl += rtap->rta_len; } } else { // V6 rtap->rta_type = IFA_ADDRESS; struct sockaddr_in6* addr_v6 = (struct sockaddr_in6*)&addr; rtap->rta_len = RTA_LENGTH(sizeof(struct in6_addr)); memcpy(RTA_DATA(rtap), &addr_v6->sin6_addr, sizeof(struct in6_addr)); rtl += rtap->rta_len; } if (iface) { rtap = (struct rtattr*)(((char*)rtap) + rtap->rta_len); rtap->rta_type = IFA_LABEL; rtap->rta_len = RTA_LENGTH(strlen(iface)); memcpy(RTA_DATA(rtap), iface, strlen(iface)); rtl += rtap->rta_len; } req.nl.nlmsg_len = NLMSG_LENGTH(rtl); req.nl.nlmsg_flags = NLM_F_REQUEST; req.nl.nlmsg_type = RTM_DELADDR; req.nl.nlmsg_pid = 0; req.nl.nlmsg_seq = ++_seq; req.ifa.ifa_family = addr.ss_family; req.ifa.ifa_prefixlen = addr.port(); req.ifa.ifa_flags = IFA_F_PERMANENT; req.ifa.ifa_scope = 0; req.ifa.ifa_index = interface_index; struct sockaddr_nl pa; bzero(&pa, sizeof(sockaddr_nl)); pa.nl_family = AF_NETLINK; struct msghdr msg; bzero(&msg, sizeof(msg)); msg.msg_name = (void*)&pa; msg.msg_namelen = sizeof(pa); struct iovec iov; iov.iov_base = (void*)&req.nl; iov.iov_len = req.nl.nlmsg_len; msg.msg_iov = &iov; msg.msg_iovlen = 1; sendmsg(fd, &msg, 0); _doRecv(fd); close(fd); } bool LinuxNetLink::routeIsSet(const InetAddress& target, const InetAddress& via, const InetAddress& src, const char* ifname) { Mutex::Lock rl(_routes_m); const std::set& rs = _routes[target]; for (std::set::const_iterator ri(rs.begin()); ri != rs.end(); ++ri) { if ((ri->via == via) && (ri->src == src)) { if (ifname) { Mutex::Lock ifl(_if_m); const iface_entry* ife = _interfaces.get(ri->ifidx); if ((ife) && (! strncmp(ife->ifacename, ifname, IFNAMSIZ))) return true; } else { return true; } } } return false; } int LinuxNetLink::_indexForInterface(const char* iface) { Mutex::Lock l(_if_m); int interface_index = -1; Hashtable::Iterator iter(_interfaces); int* k = NULL; iface_entry* v = NULL; while (iter.next(k, v)) { if (strcmp(iface, v->ifacename) == 0) { interface_index = v->index; break; } } return interface_index; } } // namespace ZeroTier #endif