/* vim: set expandtab ts=4 sw=4: */ /* * You may redistribute this program and/or modify it under the terms of * the GNU General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "util/platform/netdev/NetPlatform.h" #include "util/platform/Sockaddr.h" #include "memory/Allocator.h" #include "exception/Except.h" #include "wire/Message.h" #include "util/AddrTools.h" #include "util/Assert.h" #include "util/CString.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if ! defined(android) #include #endif #include #include #include // Way to identify our routes as opposed to statically created or otherwise... #define RTPROT_CJDNS 52 /** * This hack exists because linux/in.h and linux/in6.h define * the same structures, leading to redefinition errors. * For the second operand, we're grateful to android/bionic, platform level 21. */ struct Cjdns_in6_ifreq { struct in6_addr ifr6_addr; uint32_t ifr6_prefixlen; int ifr6_ifindex; }; /** * Get a socket and ifRequest for a given interface by name. * * @param interfaceName the name of the interface, eg: tun0 * @param af either AF_INET or AF_INET6 * @param eg an exception handler in case something goes wrong. * this will send a -1 for all errors. * @param ifRequestOut an ifreq which will be populated with the interface index of the interface. * @return a socket for interacting with this interface. */ static Er_DEFUN(int socketForIfName(const char* interfaceName, int af, struct Allocator* alloc, struct ifreq* ifRequestOut)) { int s; if ((s = socket(af, SOCK_DGRAM, 0)) < 0) { Er_raise(alloc, "socket() [%s]", strerror(errno)); } memset(ifRequestOut, 0, sizeof(struct ifreq)); CString_safeStrncpy(ifRequestOut->ifr_name, interfaceName, IFNAMSIZ); if (ioctl(s, SIOCGIFINDEX, ifRequestOut) < 0) { int err = errno; close(s); Er_raise(alloc, "ioctl(SIOCGIFINDEX) [%s]", strerror(err)); } Er_ret(s); } /** don't use if_nametoindex() because it accesses the filesystem. */ static Er_DEFUN(int ifIndexForName(const char* interfaceName, struct Allocator* alloc)) { struct ifreq ifRequest; int s = Er(socketForIfName(interfaceName, AF_INET, alloc, &ifRequest)); close(s); Er_ret(ifRequest.ifr_ifindex); } static Er_DEFUN(void checkInterfaceUp(int socket, struct ifreq* ifRequest, struct Log* logger, struct Allocator* alloc)) { if (ioctl(socket, SIOCGIFFLAGS, ifRequest) < 0) { int err = errno; close(socket); Er_raise(alloc, "ioctl(SIOCGIFFLAGS) [%s]", strerror(err)); } if (ifRequest->ifr_flags & IFF_UP & IFF_RUNNING) { // already up. Er_ret(); } Log_info(logger, "Bringing up interface [%s]", ifRequest->ifr_name); ifRequest->ifr_flags |= IFF_UP | IFF_RUNNING; if (ioctl(socket, SIOCSIFFLAGS, ifRequest) < 0) { int err = errno; close(socket); Er_raise(alloc, "ioctl(SIOCSIFFLAGS) [%s]", strerror(err)); } Er_ret(); } Er_DEFUN(void NetPlatform_addAddress(const char* interfaceName, const uint8_t* address, int prefixLen, int addrFam, struct Log* logger, struct Allocator* tempAlloc)) { struct ifreq ifRequest; int s = Er(socketForIfName(interfaceName, addrFam, tempAlloc, &ifRequest)); int ifIndex = ifRequest.ifr_ifindex; // checkInterfaceUp() clobbers the ifindex. Er(checkInterfaceUp(s, &ifRequest, logger, tempAlloc)); if (addrFam == Sockaddr_AF_INET6) { struct Cjdns_in6_ifreq ifr6 = { .ifr6_ifindex = ifIndex, .ifr6_prefixlen = prefixLen }; memcpy(&ifr6.ifr6_addr, address, 16); if (ioctl(s, SIOCSIFADDR, &ifr6) < 0) { int err = errno; close(s); if (err == EPERM) { Er_raise(tempAlloc, "ioctl permission denied, Are you root and is ipv6 enabled?"); } else { Er_raise(tempAlloc, "ioctl(SIOCSIFADDR) failed: [%s]", strerror(err)); } } } else if (addrFam == Sockaddr_AF_INET) { struct sockaddr_in sin = { .sin_family = AF_INET, .sin_port = 0 }; memcpy(&sin.sin_addr.s_addr, address, 4); memcpy(&ifRequest.ifr_addr, &sin, sizeof(struct sockaddr)); if (ioctl(s, SIOCSIFADDR, &ifRequest) < 0) { int err = errno; close(s); Er_raise(tempAlloc, "ioctl(SIOCSIFADDR) failed: [%s]", strerror(err)); } uint32_t x = (uint32_t)~0 << (32 - prefixLen); x = Endian_hostToBigEndian32(x); memcpy(&sin.sin_addr, &x, 4); memcpy(&ifRequest.ifr_addr, &sin, sizeof(struct sockaddr_in)); if (ioctl(s, SIOCSIFNETMASK, &ifRequest) < 0) { int err = errno; close(s); Er_raise(tempAlloc, "ioctl(SIOCSIFNETMASK) failed: [%s]", strerror(err)); } } else { Assert_true(0); } close(s); Er_ret(); } Er_DEFUN(void NetPlatform_setMTU(const char* interfaceName, uint32_t mtu, struct Log* logger, struct Allocator* alloc)) { struct ifreq ifRequest; int s = Er(socketForIfName(interfaceName, AF_INET6, alloc, &ifRequest)); Log_info(logger, "Setting MTU for device [%s] to [%u] bytes.", interfaceName, mtu); ifRequest.ifr_mtu = mtu; if (ioctl(s, SIOCSIFMTU, &ifRequest) < 0) { int err = errno; close(s); Er_raise(alloc, "ioctl(SIOCSIFMTU) [%s]", strerror(err)); } close(s); Er_ret(); } struct IfIndexAttr { struct rtattr rta; int ifIndex; }; Assert_compileTime(sizeof(struct IfIndexAttr) == 8); struct RouteRequest { struct nlmsghdr hdr; struct rtmsg route; }; Assert_compileTime(sizeof(struct nlmsghdr) == 16); Assert_compileTime(sizeof(struct rtmsg) == 12); Assert_compileTime(sizeof(struct RouteRequest) == 28); struct RouteInfo; struct RouteInfo { struct RouteInfo* next; int protocol; int prefix; uint8_t dstAddr[16]; int ifIndex; int af; }; #define BUFF_SZ 16384 static Er_DEFUN(bool getMoreMessages(struct RouteInfo** rio, int sock, int ifIndex, struct Allocator* alloc)) { bool retVal = false; struct Allocator* tempAlloc = Allocator_child(alloc); struct Message* msg = Message_new(BUFF_SZ, 0, tempAlloc); ssize_t sz = recv(sock, msg->bytes, BUFF_SZ, MSG_TRUNC); if (sz < (ssize_t)sizeof(struct nlmsghdr)) { Er_raise(tempAlloc, "recv() -> %s", strerror(errno)); } else if (sz > BUFF_SZ) { Er_raise(tempAlloc, "recv() -> buffer too small"); } msg->length = sz; //printf("%s\n", Hex_print(msg->bytes, msg->length, tempAlloc)); while (msg->length) { struct RouteInfo ri = { .protocol = 0 }; int initMsgLen = msg->length; struct nlmsghdr hdr; Er(Message_epop(msg, &hdr, sizeof(struct nlmsghdr))); //printf("\nHEADER %04x %04x\n", hdr.nlmsg_type, hdr.nlmsg_flags); if (hdr.nlmsg_flags & NLM_F_MULTI) { retVal = true; } if (hdr.nlmsg_type == NLMSG_DONE) { Allocator_free(tempAlloc); Er_ret(false); } struct rtmsg rtm; Er(Message_epop(msg, &rtm, sizeof(struct rtmsg))); ri.prefix = rtm.rtm_dst_len; ri.af = rtm.rtm_family; ri.protocol = rtm.rtm_protocol; for (;;) { int remainingLen = hdr.nlmsg_len - (initMsgLen - msg->length); if (remainingLen <= (int)sizeof(struct rtattr)) { break; } struct rtattr attrHead; //printf(">%s %d\n", Hex_print(msg->bytes, msg->length, tempAlloc), remainingLen); Er(Message_epop(msg, &attrHead, sizeof(struct rtattr))); switch (attrHead.rta_type) { case RTA_OIF: { if (attrHead.rta_len != 8) { Er_raise(alloc, "unexpected rta_len for ifIndex"); } Er(Message_epop(msg, &ri.ifIndex, 4)); break; } case RTA_DST: { if (rtm.rtm_family == AF_INET6) { if (attrHead.rta_len != 20) { Er_raise(alloc, "unexpected rta_len for RTA_DST (ipv6)"); } Er(Message_epop(msg, ri.dstAddr, 16)); } else if (rtm.rtm_family == AF_INET) { if (attrHead.rta_len != 8) { Er_raise(alloc, "unexpected rta_len for RTA_DST (ipv4)"); } Er(Message_epop(msg, ri.dstAddr, 4)); } else { Er_raise(alloc, "unexpected af %d", rtm.rtm_family); } break; } default: { int effectiveLen = RTA_ALIGN(attrHead.rta_len); //printf("unrecognized %d (length %d)\n", attrHead.rta_type, effectiveLen); Er(Message_epop(msg, NULL, effectiveLen - sizeof(struct rtattr))); break; } } } if (rtm.rtm_table != RT_TABLE_MAIN) { continue; } if (rtm.rtm_type != RTN_UNICAST) { continue; } if (ri.ifIndex != ifIndex) { continue; } if (ri.protocol != RTPROT_CJDNS) { continue; } struct RouteInfo* outRi = Allocator_clone(alloc, &ri); outRi->next = *rio; *rio = outRi; } Allocator_free(tempAlloc); Er_ret(retVal); } static Er_DEFUN(struct RouteInfo* getRoutes(int sock, int ifIndex, struct Allocator* alloc)) { struct RouteRequest req = { .hdr = { .nlmsg_len = sizeof(struct RouteRequest), .nlmsg_type = RTM_GETROUTE, .nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT | NLM_F_MULTI }, .route = { .rtm_family = AF_UNSPEC } }; ssize_t sz = send(sock, &req, req.hdr.nlmsg_len, 0); if (sz < 0) { Er_raise(alloc, "send() -> %s", strerror(errno)); } struct RouteInfo* ri = NULL; while (Er(getMoreMessages(&ri, sock, ifIndex, alloc))) ; Er_ret(ri); } static void bitShave(uint8_t* address, int prefix, int af) { int top; if (af == AF_INET) { top = 4; } else if (af == AF_INET6) { top = 16; } else { Assert_failure("bad af"); } if (prefix < (8 * top)) { address[prefix >> 3] &= ( 0xff << (8 - (prefix % 8)) ); for (int i = (prefix >> 3) + 1; i < top; i++) { address[i] = 0; } } } static Er_DEFUN(void addDeleteRoutes(int sock, bool delete, struct RouteInfo* ri, struct Allocator* tempAlloc)) { struct Message* msg = Message_new(0, 512, tempAlloc); for (;ri;ri = ri->next) { struct IfIndexAttr ifa = { .rta = { .rta_len = sizeof(struct IfIndexAttr), .rta_type = RTA_OIF }, .ifIndex = ri->ifIndex }; Er(Message_epush(msg, &ifa, sizeof(struct IfIndexAttr))); int addrLen = (ri->af == AF_INET6) ? 16 : 4; Er(Message_epush(msg, ri->dstAddr, addrLen)); bitShave(msg->bytes, ri->prefix, ri->af); struct rtattr rta = { .rta_len = sizeof(struct rtattr) + addrLen, .rta_type = RTA_DST }; Er(Message_epush(msg, &rta, sizeof(struct rtattr))); struct rtmsg route = { .rtm_family = ri->af, .rtm_dst_len = ri->prefix, .rtm_table = RT_TABLE_MAIN, .rtm_scope = (delete) ? RT_SCOPE_NOWHERE : RT_SCOPE_LINK, .rtm_protocol = (delete) ? RTPROT_UNSPEC : ri->protocol, .rtm_type = (delete) ? RTN_UNSPEC : RTN_UNICAST }; Er(Message_epush(msg, &route, sizeof(struct rtmsg))); struct nlmsghdr hdr = { .nlmsg_len = msg->length + sizeof(struct nlmsghdr), .nlmsg_type = (delete) ? RTM_DELROUTE : RTM_NEWROUTE, .nlmsg_flags = NLM_F_REQUEST | ((delete) ? 0 : NLM_F_CREATE) // | NLM_F_ACK, }; Er(Message_epush(msg, &hdr, sizeof(struct nlmsghdr))); ssize_t sz = send(sock, msg->bytes, msg->length, 0); if (sz < 0) { Er_raise(tempAlloc, "send() -> %s", strerror(errno)); } Message_reset(msg); } Er_ret(); } static int closeSocket(struct Allocator_OnFreeJob* job) { long sock = (long) job->userData; close((int)sock); return 0; } static Er_DEFUN(int mkSocket(struct Allocator* alloc)) { int sock = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (sock < 0) { Er_raise(alloc, "socket(PF_NETLINK) -> %s", strerror(errno)); } Allocator_onFree(alloc, closeSocket, (void*) ((long) sock)); Er_ret(sock); } static struct RouteInfo* riForSockaddrs(struct Sockaddr** prefixSet, int prefixCount, int ifIndex, struct Allocator* alloc) { struct RouteInfo* out = NULL; for (int i = 0; i < prefixCount; i++) { struct RouteInfo* ri = Allocator_calloc(alloc, sizeof(struct RouteInfo), 1); ri->protocol = RTPROT_CJDNS; ri->prefix = Sockaddr_getPrefix(prefixSet[i]); ri->af = Sockaddr_getFamily(prefixSet[i]); ri->ifIndex = ifIndex; uint8_t* addr; int len = Sockaddr_getAddress(prefixSet[i], &addr); Assert_true(len == 4 || len == 16); Bits_memcpy(ri->dstAddr, addr, len); ri->next = out; out = ri; } return out; } static void logRis(struct RouteInfo* ri, struct Log* logger, char* msg) { for (; ri; ri = ri->next) { uint8_t addrBuff[40] = {0}; if (ri->af == AF_INET6) { AddrTools_printIp(addrBuff, ri->dstAddr); } else if (ri->af == AF_INET) { snprintf(addrBuff, 40, "%u.%u.%u.%u", ri->dstAddr[0], ri->dstAddr[1], ri->dstAddr[2], ri->dstAddr[3]); } Log_debug(logger, "%s %s/%u", msg, addrBuff, ri->prefix); } } Er_DEFUN(void NetPlatform_setRoutes(const char* ifName, struct Sockaddr** prefixSet, int prefixCount, struct Log* logger, struct Allocator* tempAlloc)) { int ifIndex = Er(ifIndexForName(ifName, tempAlloc)); struct RouteInfo* newRi = riForSockaddrs(prefixSet, prefixCount, ifIndex, tempAlloc); int sock = Er(mkSocket(tempAlloc)); struct RouteInfo* oldRi = Er(getRoutes(sock, ifIndex, tempAlloc)); logRis(oldRi, logger, "DELETE ROUTE"); Er(addDeleteRoutes(sock, true, oldRi, tempAlloc)); logRis(newRi, logger, "ADD ROUTE"); Er(addDeleteRoutes(sock, false, newRi, tempAlloc)); Er_ret(); }