From d891ad5a488c842f951d80c5f8fce3aa42cf3226 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Wed, 19 Aug 2026 21:36:48 -0500 Subject: [PATCH] netlink address get/new/del --- Makefile | 3 +- dhcp4.cpp | 18 ++++ dhcp4.h | 2 + netlink.cpp | 107 ++++++++++++++++++++ rtnetlink.cpp | 263 ++++++++++++++++++++++++++++++++++++++++++++++++++ rtnetlink.h | 23 +++++ test2.cpp | 2 +- 7 files changed, 416 insertions(+), 2 deletions(-) create mode 100644 netlink.cpp create mode 100644 rtnetlink.cpp create mode 100644 rtnetlink.h diff --git a/Makefile b/Makefile index b22b0fd..04c7bce 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,8 @@ OBJS = \ ip4.o \ packet.o \ udp4.o \ - eth.o + eth.o \ + rtnetlink.o all: client test2 diff --git a/dhcp4.cpp b/dhcp4.cpp index db3aea2..0420078 100644 --- a/dhcp4.cpp +++ b/dhcp4.cpp @@ -1,4 +1,5 @@ #include +#include #include @@ -54,6 +55,23 @@ namespace dhcp4 { return (sizeof (message)) + index; } + char const * message_type_tostr(int message_type) + { + switch (message_type) { + case message_type::dhcpdiscover: return "dhcpdiscover"; + case message_type::dhcpoffer: return "dhcpoffer"; + case message_type::dhcprequest: return "dhcprequest"; + case message_type::dhcpdecline: return "dhcpdecline"; + case message_type::dhcpack: return "dhcpack"; + case message_type::dhcpnak: return "dhcpnak"; + case message_type::dhcprelease: return "dhcprelease"; + case message_type::dhcpinform: return "dhcpinform"; + default: + assert(false); + return nullptr; + } + } + void parse_options(message const * m, int options_length, options * o) { uint8_t const * options = m->options; diff --git a/dhcp4.h b/dhcp4.h index 9147d93..476c4aa 100644 --- a/dhcp4.h +++ b/dhcp4.h @@ -80,5 +80,7 @@ namespace dhcp4 { uint8_t const * chaddr, uint32_t dhcp_message_type); + char const * message_type_tostr(int message_type); + void parse_options(message const * m, int options_length, options * o); } diff --git a/netlink.cpp b/netlink.cpp new file mode 100644 index 0000000..3400ace --- /dev/null +++ b/netlink.cpp @@ -0,0 +1,107 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ip4.h" +#include "rtnetlink.h" + +int main() +{ + int ret; + + int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) { + perror("socket"); + exit(EXIT_FAILURE); + } + + struct sockaddr_nl bsa = { .nl_family = AF_NETLINK }; + ret = bind(fd, (struct sockaddr *)&bsa, (sizeof (sockaddr_nl))); + if (ret < 0) { + perror("bind"); + exit(EXIT_FAILURE); + } + + struct sockaddr_nl gsa; + socklen_t addrlen = (sizeof (sockaddr_nl)); + ret = getsockname(fd, (struct sockaddr *)&gsa, &addrlen); + if (ret < 0) { + perror("getsockname"); + exit(EXIT_FAILURE); + } + + ////////////////////////////////////////////////////////////////////// + // interface index + ////////////////////////////////////////////////////////////////////// + + char const * interface_name = "wlp174s0"; + struct ifreq if_index = {}; + strncpy(if_index.ifr_name, interface_name, IFNAMSIZ - 1); + int iret = ioctl(fd, SIOCGIFINDEX, &if_index); + if (iret < 0) { + perror("ioctl(SIOCGIFINDEX)"); + exit(EXIT_FAILURE); + } + printf("ifa_index %d\n", if_index.ifr_ifindex); + + ////////////////////////////////////////////////////////////////////// + // message + ////////////////////////////////////////////////////////////////////// + + int rtrets = rtnetlink::send_getaddr(fd, if_index.ifr_ifindex); + if (rtrets < 0) { + exit(EXIT_FAILURE); + } + + const int out_length = 64; + rtnetlink::newaddr out[out_length]; + int rtreth = rtnetlink::handle_getaddr(fd, if_index.ifr_ifindex, out, out_length); + if (rtreth < 0) { + exit(EXIT_FAILURE); + } + + for (int i = 0; i < rtreth; i++) { + printf("out[%d] prefixlen %d\n", i, out[i].ifa_prefixlen); + printf("out[%d] local %s\n", i, ip4::tostr(out[i].local)); + printf("out[%d] address %s\n", i, ip4::tostr(out[i].address)); + + // del + { + printf("del\n"); + int dret = rtnetlink::send_typeaddr(fd, RTM_DELADDR, if_index.ifr_ifindex, + out[i].ifa_prefixlen, out[i].local, out[i].address); + if (dret < 0) { + exit(EXIT_FAILURE); + } + int eret = rtnetlink::handle_error(fd); + if (eret < 0) { + exit(EXIT_FAILURE); + } + } + + // new + { + printf("new\n"); + int dret = rtnetlink::send_typeaddr(fd, RTM_NEWADDR, if_index.ifr_ifindex, + out[i].ifa_prefixlen, out[i].local, out[i].address); + if (dret < 0) { + exit(EXIT_FAILURE); + } + int eret = rtnetlink::handle_error(fd); + if (eret < 0) { + exit(EXIT_FAILURE); + } + } + } + + return 0; +} diff --git a/rtnetlink.cpp b/rtnetlink.cpp new file mode 100644 index 0000000..7c19207 --- /dev/null +++ b/rtnetlink.cpp @@ -0,0 +1,263 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "rtnetlink.h" + +static inline int align4(int len) +{ + return (len + 3) & (~3); +} + +struct newaddr_msg { + struct nlmsghdr nl; + struct ifaddrmsg ifaddr; +}; + +struct deladdr_msg { + struct nlmsghdr nl; + struct ifaddrmsg ifaddr; + struct { + struct rtattr rtattr; + uint32_t value; + } attr[2]; +}; + +namespace rtnetlink { + + bool parse_newaddr(uint8_t const * buf, ssize_t length, + uint32_t ifa_index, + newaddr * out, int out_length, + int & index) + { + uint8_t const * bufi = buf; + + while (index < out_length) { + struct nlmsghdr * nl = (struct nlmsghdr *)&bufi[0]; + struct ifaddrmsg * ifaddr = (struct ifaddrmsg *)&bufi[(sizeof (struct nlmsghdr))]; + size_t rtattr_offset = (sizeof (struct nlmsghdr)) + (sizeof (struct ifaddrmsg)); + + assert((nl->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI); + + if (nl->nlmsg_type == NLMSG_DONE) { + return true; + } + + assert(nl->nlmsg_type == RTM_NEWADDR); + + out[index].ifa_prefixlen = ifaddr->ifa_prefixlen; + + while (rtattr_offset < nl->nlmsg_len) { + struct rtattr * rtattr = (struct rtattr *)&bufi[rtattr_offset]; + rtattr_offset += align4(rtattr->rta_len); + + void * value = (void *)(((ptrdiff_t)rtattr) + (sizeof (struct rtattr))); + + switch (rtattr->rta_type) { + case IFA_ADDRESS: + out[index].address = *((uint32_t *)value); + break; + case IFA_LOCAL: + out[index].local = *((uint32_t *)value); + break; + } + } + + if (ifaddr->ifa_index == ifa_index) { + index += 1; + } + + bufi = &bufi[nl->nlmsg_len]; + if (bufi - buf == length) { + break; + } + } + + return false; + } + + int handle_getaddr(int fd, uint32_t ifa_index, newaddr * out, int out_length) + { + bool done = false; + int index = 0; + + while (!done) { + struct sockaddr_nl addr = { .nl_family = AF_NETLINK }; + struct iovec iov[1] = {}; + struct msghdr msg = { + .msg_name = (void *)&addr, + .msg_namelen = (sizeof (sockaddr_nl)), + .msg_iov = iov, + .msg_iovlen = 1, + .msg_flags = MSG_TRUNC, + }; + ssize_t peek_length = recvmsg(fd, &msg, MSG_PEEK | MSG_TRUNC); + if (peek_length < 0) { + perror("recvmsg(MSG_PEEK|MSG_TRUNC)"); + return -1; + } + + uint8_t * buf = (uint8_t *)malloc(peek_length); + + iov[0].iov_base = buf; + iov[0].iov_len = peek_length; + msg.msg_flags = 0; + ssize_t length = recvmsg(fd, &msg, 0); + if (length < 0) { + perror("recvmsg"); + return -1; + } + assert(peek_length == length); + + done = parse_newaddr(buf, length, ifa_index, out, out_length, index); + free(buf); + } + + return index; + } + + int send_getaddr(int fd, uint32_t ifa_index) + { + const size_t message_length = (sizeof (struct newaddr_msg)); + + struct newaddr_msg rtmmsg = { + .nl = { + .nlmsg_len = message_length, + .nlmsg_type = RTM_GETADDR, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP, + .nlmsg_seq = 1234, + }, + .ifaddr = { + .ifa_family = AF_INET, + .ifa_scope = RT_SCOPE_UNIVERSE, + .ifa_index = ifa_index, + } + }; + + struct iovec iov[1] = { + { + .iov_base = &rtmmsg, + .iov_len = message_length, + } + }; + struct sockaddr_nl addr = { .nl_family = AF_NETLINK }; + struct msghdr msg = { + .msg_name = (void *)&addr, + .msg_namelen = (sizeof (sockaddr_nl)), + .msg_iov = iov, + .msg_iovlen = 1, + }; + + ssize_t sret = sendmsg(fd, &msg, 0); + if (sret < 0) { + perror("sendmsg"); + return -1; + } + + return 0; + } + + int send_typeaddr(int fd, uint16_t nlmsg_type, uint32_t ifa_index, uint8_t ifa_prefixlen, uint32_t local, uint32_t address) + { + const size_t message_length = (sizeof (struct deladdr_msg)); + + struct deladdr_msg rtmmsg = { + .nl = { + .nlmsg_len = message_length, + .nlmsg_type = nlmsg_type, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, + .nlmsg_seq = 12345, + }, + .ifaddr = { + .ifa_family = AF_INET, + .ifa_prefixlen = ifa_prefixlen, + .ifa_scope = RT_SCOPE_UNIVERSE, + .ifa_index = ifa_index, + }, + .attr = { + { + .rtattr = { .rta_len = 8, .rta_type = IFA_LOCAL }, + .value = local, + }, + { + .rtattr = { .rta_len = 8, .rta_type = IFA_ADDRESS }, + .value = address, + } + } + }; + + struct iovec iov[1] = { + { + .iov_base = &rtmmsg, + .iov_len = message_length, + } + }; + struct sockaddr_nl addr = { .nl_family = AF_NETLINK }; + struct msghdr msg = { + .msg_name = (void *)&addr, + .msg_namelen = (sizeof (sockaddr_nl)), + .msg_iov = iov, + .msg_iovlen = 1, + }; + + ssize_t sret = sendmsg(fd, &msg, 0); + if (sret < 0) { + perror("sendmsg"); + return -1; + } + + return 0; + } + + int handle_error(int fd) + { + struct sockaddr_nl addr = { .nl_family = AF_NETLINK }; + struct iovec iov[1] = {}; + struct msghdr msg = { + .msg_name = (void *)&addr, + .msg_namelen = (sizeof (sockaddr_nl)), + .msg_iov = iov, + .msg_iovlen = 1, + .msg_flags = MSG_TRUNC, + }; + ssize_t peek_length = recvmsg(fd, &msg, MSG_PEEK | MSG_TRUNC); + if (peek_length < 0) { + perror("recvmsg(MSG_PEEK|MSG_TRUNC)"); + return -1; + } + + uint8_t * buf = (uint8_t *)malloc(peek_length); + + iov[0].iov_base = buf; + iov[0].iov_len = peek_length; + msg.msg_flags = 0; + ssize_t length = recvmsg(fd, &msg, 0); + if (length < 0) { + perror("recvmsg"); + return -1; + } + assert(peek_length == length); + + struct nlmsghdr * nl = (struct nlmsghdr *)&buf[0]; + struct nlmsgerr * msgerr = (struct nlmsgerr *)&buf[(sizeof (struct nlmsghdr))]; + + assert(nl->nlmsg_type == NLMSG_ERROR); + if (msgerr->error < 0) { + printf("msgerr %d %s\n", msgerr->error, strerror(-msgerr->error)); + return -1; + } + + free(buf); + + return 0; + } +} diff --git a/rtnetlink.h b/rtnetlink.h new file mode 100644 index 0000000..597eb92 --- /dev/null +++ b/rtnetlink.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace rtnetlink { + + struct newaddr { + uint32_t address; + uint32_t local; + uint32_t ifa_prefixlen; + }; + + bool parse_newaddr(uint8_t const * buf, ssize_t length, + uint32_t ifa_index, + newaddr * out, int out_length, + int & index); + + int handle_getaddr(int fd, uint32_t ifa_index, newaddr * out, int out_length); + int send_getaddr(int fd, uint32_t ifa_index); + + int send_typeaddr(int fd, uint16_t nlmsg_type, uint32_t ifa_index, uint8_t ifa_prefixlen, uint32_t local, uint32_t address); + int handle_error(int fd); +} diff --git a/test2.cpp b/test2.cpp index 567bcd0..74397a7 100644 --- a/test2.cpp +++ b/test2.cpp @@ -19,7 +19,7 @@ void parse_packet(uint8_t const * buf, int length) int dhcp4_options_length = length - (packet::dhcp4_offset + (offsetof (dhcp4::message, options))); dhcp4::options options = {}; dhcp4::parse_options(p.dhcp4, dhcp4_options_length, &options); - printf("message type: %d\n", options.message_type); + printf("message type: %s\n", dhcp4::message_type_tostr(options.message_type)); printf("subnet mask: %s\n", ip4::tostr(options.subnet_mask)); printf("router: %s\n", ip4::tostr(options.router)); printf("domain name server: %s\n", ip4::tostr(options.domain_name_server));