diff --git a/sbin/route/Makefile b/sbin/route/Makefile --- a/sbin/route/Makefile +++ b/sbin/route/Makefile @@ -6,7 +6,7 @@ PACKAGE=runtime PROG= route MAN= route.8 -SRCS= route.c keywords.h +SRCS= route.c route_netlink.c keywords.h WARNS?= 3 CLEANFILES+=keywords.h diff --git a/sbin/route/route.c b/sbin/route/route.c --- a/sbin/route/route.c +++ b/sbin/route/route.c @@ -114,7 +114,7 @@ static TAILQ_HEAD(fibl_head_t, fibl) fibl_head; -static void printb(int, const char *); +void printb(int, const char *); static void flushroutes(int argc, char *argv[]); static int flushroutes_fib(int); static int getaddr(int, char *, int); @@ -135,7 +135,7 @@ static int prefixlen(const char *); static void print_getmsg(struct rt_msghdr *, int, int); static void print_rtmsg(struct rt_msghdr *, size_t); -static const char *routename(struct sockaddr *); +const char *routename(struct sockaddr *); static int rtmsg(int, int, int); static void set_metric(char *, int); static int set_sofib(int); @@ -149,6 +149,9 @@ #define READ_TIMEOUT 10 static volatile sig_atomic_t stop_read; +int nl_send(int cmd, int rtm_flags, int fib, struct sockaddr_storage *so, + struct rt_metrics *rt_metrics); + static void stopit(int sig __unused) { @@ -526,7 +529,7 @@ return (error); } -static const char * +const char * routename(struct sockaddr *sa) { struct sockaddr_dl *sdl; @@ -1488,6 +1491,10 @@ cmd = RTM_DELETE; flags |= RTF_PINNED; } + + errno = nl_send(cmd, flags, fib, so, &rt_metrics); + return (errno == 0 ? 0 : -1); + #define rtm m_rtmsg.m_rtm rtm.rtm_type = cmd; rtm.rtm_flags = flags; @@ -1571,7 +1578,7 @@ static const char metricnames[] = "\011weight\010rttvar\7rtt\6ssthresh\5sendpipe\4recvpipe\3expire" "\1mtu"; -static const char routeflags[] = +const char routeflags[] = "\1UP\2GATEWAY\3HOST\4REJECT\5DYNAMIC\6MODIFIED\7DONE" "\012XRESOLVE\013LLINFO\014STATIC\015BLACKHOLE" "\017PROTO2\020PROTO1\021PRCLONING\022WASCLONED\023PROTO3" @@ -1812,7 +1819,7 @@ (void)fflush(stdout); } -static void +void printb(int b, const char *str) { int i; diff --git a/sbin/route/route_netlink.c b/sbin/route/route_netlink.c new file mode 100644 --- /dev/null +++ b/sbin/route/route_netlink.c @@ -0,0 +1,305 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const char *routename(struct sockaddr *); +void printb(int, const char *); +extern const char routeflags[]; + +int nl_send(int cmd, int rtm_flags, int fib, struct sockaddr_storage *so, + struct rt_metrics *rt_metrics); + +static void print_getmsg(struct snl_state *ss, struct nlmsghdr *hdr, struct sockaddr *dst); + +#define s6_addr32 __u6_addr.__u6_addr32 +#define bitcount32(x) __bitcount32((uint32_t)(x)) +static int +inet6_get_plen(const struct in6_addr *addr) +{ + + return (bitcount32(addr->s6_addr32[0]) + bitcount32(addr->s6_addr32[1]) + + bitcount32(addr->s6_addr32[2]) + bitcount32(addr->s6_addr32[3])); +} + +static void +ip6_writemask(struct in6_addr *addr6, uint8_t mask) +{ + uint32_t *cp; + + for (cp = (uint32_t *)addr6; mask >= 32; mask -= 32) + *cp++ = 0xFFFFFFFF; + if (mask > 0) + *cp = htonl(mask ? ~((1 << (32 - mask)) - 1) : 0); +} + +static struct sockaddr * +get_netmask(struct snl_state *ss, int family, int plen) +{ + if (family == AF_INET) { + if (plen == 32) + return (NULL); + + struct sockaddr_in *sin = snl_allocz(ss, sizeof(*sin)); + + sin->sin_len = sizeof(*sin); + sin->sin_family = family; + sin->sin_addr.s_addr = htonl(plen ? ~((1 << (32 - plen)) - 1) : 0); + + return (struct sockaddr *)sin; + } else if (family == AF_INET6) { + if (plen == 128) + return (NULL); + + struct sockaddr_in6 *sin6 = snl_allocz(ss, sizeof(*sin6)); + + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_family = family; + ip6_writemask(&sin6->sin6_addr, plen); + + return (struct sockaddr *)sin6; + } + return (NULL); +} + +/* +int +nl_recv(struct snl_state *ss) +{ +} +*/ + +int +nl_send(int cmd, int rtm_flags, int fib, struct sockaddr_storage *so, + struct rt_metrics *rt_metrics) +{ + struct snl_state ss = {}; + struct snl_writer nw; + int nl_type = 0, nl_flags = 0; + + if (!snl_init(&ss, NETLINK_ROUTE)) + return (ENOTSUP); + snl_init_writer(&ss, &nw); + + switch (cmd) { + case RTSOCK_RTM_ADD: + nl_type = RTM_NEWROUTE; + nl_flags = NLM_F_CREATE | NLM_F_APPEND; /* Do append by default */ + break; + case RTSOCK_RTM_CHANGE: + nl_type = RTM_NEWROUTE; + nl_flags = NLM_F_REPLACE; + break; + case RTSOCK_RTM_DELETE: + nl_type = RTM_DELROUTE; + break; + case RTSOCK_RTM_GET: + nl_type = RTM_GETROUTE; + break; + default: + exit(1); + } + + struct sockaddr *dst = (struct sockaddr *)&so[RTAX_DST]; + struct sockaddr *mask = (struct sockaddr *)&so[RTAX_NETMASK]; + struct sockaddr *gw = (struct sockaddr *)&so[RTAX_GATEWAY]; + + if (dst == NULL) + return (EINVAL); + + struct nlmsghdr *hdr = snl_create_msg_request(&nw, nl_type); + hdr->nlmsg_flags |= nl_flags; + + int plen = 0; + int rtm_type = RTN_UNICAST; + + switch (dst->sa_family) { + case AF_INET: + { + struct sockaddr_in *mask4 = (struct sockaddr_in *)mask; + + plen = mask4 ? bitcount32(mask4->sin_addr.s_addr) : 32; + break; + } + case AF_INET6: + { + struct sockaddr_in6 *mask6 = (struct sockaddr_in6 *)mask; + + plen = mask6 ? inet6_get_plen(&mask6->sin6_addr) : 128; + break; + } + default: + return (ENOTSUP); + } + + if (rtm_flags & RTF_REJECT) + rtm_type = RTN_PROHIBIT; + else if (rtm_flags & RTF_BLACKHOLE) + rtm_type = RTN_BLACKHOLE; + + struct rtmsg *rtm = snl_reserve_msg_object(&nw, struct rtmsg); + rtm->rtm_family = dst->sa_family; + rtm->rtm_protocol = RTPROT_STATIC; + rtm->rtm_type = rtm_type; + rtm->rtm_dst_len = plen; + + snl_add_msg_attr_ip(&nw, RTA_DST, dst); + snl_add_msg_attr_u32(&nw, RTA_TABLE, fib); + + if (rtm_flags & RTF_GATEWAY) { + if (gw->sa_family == dst->sa_family) + snl_add_msg_attr_ip(&nw, RTA_GATEWAY, gw); + else + snl_add_msg_attr_ipvia(&nw, RTA_VIA, gw); + } else if (gw != NULL) { + /* Should be AF_LINK */ + struct sockaddr_dl *sdl = (struct sockaddr_dl *)gw; + if (sdl->sdl_index != 0) + snl_add_msg_attr_u32(&nw, RTA_OIF, sdl->sdl_index); + } + + if (rtm_flags != 0) + snl_add_msg_attr_u32(&nw, NL_RTA_RTFLAGS, rtm_flags); + + if (rt_metrics->rmx_mtu > 0) { + int off = snl_add_msg_attr_nested(&nw, RTA_METRICS); + snl_add_msg_attr_u32(&nw, RTAX_MTU, rt_metrics->rmx_mtu); + snl_end_attr_nested(&nw, off); + } + + if (rt_metrics->rmx_weight > 0) + snl_add_msg_attr_u32(&nw, NL_RTA_WEIGHT, rt_metrics->rmx_weight); + + hdr = snl_finalize_msg(&nw); + if (hdr != NULL) { + hdr = snl_get_reply(&ss, hdr); + struct snl_errmsg_data e = {}; + + if (nl_type == NL_RTM_GETROUTE) { + if (hdr->nlmsg_type == NL_RTM_NEWROUTE) + print_getmsg(&ss, hdr, dst); + else { + snl_check_return(&ss, hdr, &e); + if (e.error == ESRCH) + warn("route has not been found"); + else + warn("message indicates error %d", e.error); + } + + return (0); + } + + if (snl_check_return(&ss, hdr, &e)) + return (e.error); + } + return (EINVAL); +} + +static bool +get_ifdata(struct snl_state *ss, uint32_t ifindex, struct snl_parsed_link_simple *link) +{ + struct snl_state ss_tmp = {}; + bool ret = false; + + if (!snl_init(&ss_tmp, NETLINK_ROUTE)) + return (false); + + struct { + struct nlmsghdr hdr; + struct ifinfomsg ifmsg; + } msg = { + .hdr.nlmsg_type = NL_RTM_GETLINK, + .hdr.nlmsg_len = sizeof(msg), + .hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, + .ifmsg.ifi_index = ifindex, + }; + + struct nlmsghdr *hdr = snl_get_reply(&ss_tmp, &msg.hdr); + + if (hdr != NULL && hdr->nlmsg_type == RTM_NEWLINK) { + if (snl_parse_nlmsg(&ss_tmp, hdr, &snl_rtm_link_parser_simple, link)) { + if (link->ifla_ifname != NULL) { + int len = strlen(link->ifla_ifname) + 1; + char *name = snl_allocz(ss, len); + + strlcpy(name, link->ifla_ifname, len); + link->ifla_ifname = name; + ret = true; + } + } + } + + snl_free(&ss_tmp); + return (ret); +} + +static void +print_getmsg(struct snl_state *ss, struct nlmsghdr *hdr, struct sockaddr *dst) +{ + struct timespec ts; + struct snl_parsed_route r = { .rtax_weight = RT_DEFAULT_WEIGHT }; + + if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_route_parser, &r)) + return; + + struct snl_parsed_link_simple link = {}; + if (!get_ifdata(ss, r.rta_oif, &link)) + return; + + if (r.rtax_mtu == 0) + r.rtax_mtu = link.ifla_mtu; + + (void)printf(" route to: %s\n", routename(dst)); + + if (r.rta_dst) + (void)printf("destination: %s\n", routename(r.rta_dst)); + struct sockaddr *mask = get_netmask(ss, r.rtm_family, r.rtm_dst_len); + if (mask) + (void)printf(" mask: %s\n", routename(mask)); + if (r.rta_gw && (r.rta_rtflags & RTF_GATEWAY)) + (void)printf(" gateway: %s\n", routename(r.rta_gw)); + (void)printf(" fib: %u\n", (unsigned int)r.rta_table); + if (link.ifla_ifname) + (void)printf(" interface: %s\n", link.ifla_ifname); + (void)printf(" flags: "); + printb(r.rta_rtflags, routeflags); + + struct rt_metrics rmx = { + .rmx_mtu = r.rtax_mtu, + .rmx_weight = r.rtax_weight, + .rmx_expire = r.rta_expire, + }; + + printf("\n%9s %9s %9s %9s %9s %10s %9s\n", "recvpipe", + "sendpipe", "ssthresh", "rtt,msec", "mtu ", "weight", "expire"); + printf("%8lu ", rmx.rmx_recvpipe); + printf("%8lu ", rmx.rmx_sendpipe); + printf("%8lu ", rmx.rmx_ssthresh); + printf("%8lu ", 0UL); + printf("%8lu ", rmx.rmx_mtu); + printf("%8lu ", rmx.rmx_weight); + if (rmx.rmx_expire > 0) + clock_gettime(CLOCK_REALTIME_FAST, &ts); + else + ts.tv_sec = 0; + printf("%8ld \n", (long)(rmx.rmx_expire - ts.tv_sec)); +} +