// SPDX-License-Identifier: ISC /* * Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include "config.h" #include #include #include #ifdef GNU_LINUX #include #include #include #include #include #include #include #include #include #include #include #include "rt_netlink.h" #include "fpm/fpm.h" #include "lib/libfrr.h" XREF_SETUP(); struct glob { int server_sock; int sock; bool reflect; bool dump_hex; }; struct glob glob_space; struct glob *glob = &glob_space; #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) /* * get_print_buf */ static char * get_print_buf(size_t *buf_len) { static char print_bufs[16][128]; static int counter; counter++; if (counter >= 16) counter = 0; *buf_len = 128; return &print_bufs[counter][0]; } /* * create_listen_sock */ static int create_listen_sock(int port, int *sock_p) { int sock; struct sockaddr_in addr; int reuse; sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock < 0) { fprintf(stderr, "Failed to create socket: %s\n", strerror(errno)); return 0; } reuse = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) { fprintf(stderr, "Failed to set reuse addr option: %s\n", strerror(errno)); } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(port); if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { fprintf(stderr, "Failed to bind to port %d: %s\n", port, strerror(errno)); close(sock); return 0; } if (listen(sock, 5)) { fprintf(stderr, "Failed to listen on socket: %s\n", strerror(errno)); close(sock); return 0; } *sock_p = sock; return 1; } /* * accept_conn */ static int accept_conn(int listen_sock) { int sock; struct sockaddr_in client_addr = { 0 }; unsigned int client_len; while (1) { char buf[120]; fprintf(stdout, "Waiting for client connection...\n"); client_len = sizeof(client_addr); sock = accept(listen_sock, (struct sockaddr *)&client_addr, &client_len); if (sock >= 0) { fprintf(stdout, "Accepted client %s\n", inet_ntop(AF_INET, &client_addr.sin_addr, buf, sizeof(buf))); return sock; } fprintf(stderr, "Failed to accept socket: %s\n", strerror(errno)); } } /* * read_fpm_msg */ static fpm_msg_hdr_t * read_fpm_msg(char *buf, size_t buf_len) { char *cur, *end; long need_len, bytes_read, have_len; fpm_msg_hdr_t *hdr; int reading_full_msg; end = buf + buf_len; cur = buf; hdr = (fpm_msg_hdr_t *)buf; while (1) { reading_full_msg = 0; have_len = cur - buf; if (have_len < (long)FPM_MSG_HDR_LEN) { need_len = FPM_MSG_HDR_LEN - have_len; } else { need_len = fpm_msg_len(hdr) - have_len; assert(need_len >= 0 && need_len <= (end - cur)); if (!need_len) return hdr; reading_full_msg = 1; } bytes_read = read(glob->sock, cur, need_len); if (bytes_read == 0) { fprintf(stdout, "Socket closed as that read returned 0\n"); return NULL; } if (bytes_read < 0) { fprintf(stderr, "Error reading from socket: %s\n", strerror(errno)); return NULL; } cur += bytes_read; if (bytes_read < need_len) { fprintf(stderr, "Read %lu bytes but expected to read %lu bytes instead\n", bytes_read, need_len); return NULL; } if (reading_full_msg) return hdr; if (!fpm_msg_ok(hdr, buf_len)) { assert(0); fprintf(stderr, "Malformed fpm message\n"); return NULL; } } } /* * netlink_msg_type_to_s */ static const char * netlink_msg_type_to_s(uint16_t type) { switch (type) { case RTM_NEWROUTE: return "New route"; case RTM_DELROUTE: return "Del route"; case RTM_NEWNEXTHOP: return "New Nexthop Group"; case RTM_DELNEXTHOP: return "Del Nexthop Group"; default: return "Unknown"; } } /* * netlink_prot_to_s */ static const char * netlink_prot_to_s(unsigned char prot) { switch (prot) { case RTPROT_KERNEL: return "Kernel"; case RTPROT_BOOT: return "Boot"; case RTPROT_STATIC: return "Static"; case RTPROT_ZEBRA: return "Zebra"; case RTPROT_DHCP: return "Dhcp"; case RTPROT_BGP: return "BGP"; case RTPROT_ISIS: return "ISIS"; case RTPROT_OSPF: return "OSPF"; case RTPROT_RIP: return "RIP"; case RTPROT_RIPNG: return "RIPNG"; case RTPROT_BABEL: return "BABEL"; case RTPROT_NHRP: return "NHRP"; case RTPROT_EIGRP: return "EIGRP"; case RTPROT_SHARP: return "SHARP"; case RTPROT_PBR: return "PBR"; case RTPROT_ZSTATIC: return "Static"; default: return "Unknown"; } } #define MAX_NHS 16 struct netlink_nh { struct rtattr *gateway; int if_index; uint16_t encap_type; uint32_t vxlan_vni; }; struct netlink_msg_ctx { struct nlmsghdr *hdr; /* * Stuff pertaining to route messages. */ struct rtmsg *rtmsg; struct rtattr *rtattrs[RTA_MAX + 1]; /* * Nexthops. */ struct netlink_nh nhs[MAX_NHS]; unsigned long num_nhs; struct rtattr *dest; struct rtattr *src; int *metric; unsigned int *nhgid; const char *err_msg; }; /* * netlink_msg_ctx_init */ static inline void netlink_msg_ctx_init(struct netlink_msg_ctx *ctx) { memset(ctx, 0, sizeof(*ctx)); } /* * netlink_msg_ctx_set_err */ static inline void netlink_msg_ctx_set_err(struct netlink_msg_ctx *ctx, const char *err_msg) { if (ctx->err_msg) return; ctx->err_msg = err_msg; } /* * parse_rtattrs_ */ static int parse_rtattrs_(struct rtattr *rta, size_t len, struct rtattr **rtas, uint16_t num_rtas, const char **err_msg) { memset(rtas, 0, num_rtas * sizeof(rtas[0])); for (; len > 0; rta = RTA_NEXT(rta, len)) { uint16_t type = rta->rta_type & NLA_TYPE_MASK; if (!RTA_OK(rta, len)) { *err_msg = "Malformed rta"; return 0; } if (type >= num_rtas) { warn("Unknown rtattr type %d", rta->rta_type); continue; } rtas[type] = rta; } return 1; } /* * parse_rtattrs */ static int parse_rtattrs(struct netlink_msg_ctx *ctx, struct rtattr *rta, size_t len) { const char *err_msg; err_msg = NULL; if (!parse_rtattrs_(rta, len, ctx->rtattrs, ARRAY_SIZE(ctx->rtattrs), &err_msg)) { netlink_msg_ctx_set_err(ctx, err_msg); return 0; } return 1; } /* * netlink_msg_ctx_add_nh */ static int netlink_msg_ctx_add_nh(struct netlink_msg_ctx *ctx, int if_index, struct rtattr *gateway, uint16_t encap_type, uint32_t vxlan_vni) { struct netlink_nh *nh; if (ctx->num_nhs + 1 >= ARRAY_SIZE(ctx->nhs)) { warn("Too many next hops"); return 0; } nh = &ctx->nhs[ctx->num_nhs]; ctx->num_nhs++; nh->gateway = gateway; nh->if_index = if_index; nh->encap_type = encap_type; nh->vxlan_vni = vxlan_vni; return 1; } /* * parse_multipath_attr */ static int parse_multipath_attr(struct netlink_msg_ctx *ctx, struct rtattr *mpath_rtattr) { int len; struct rtnexthop *rtnh; struct rtattr *rtattrs[RTA_MAX + 1]; struct rtattr *tb[RTA_MAX + 1]; struct rtattr *gateway; const char *err_msg; rtnh = RTA_DATA(mpath_rtattr); len = RTA_PAYLOAD(mpath_rtattr); for (; len > 0; len -= NLMSG_ALIGN(rtnh->rtnh_len), rtnh = RTNH_NEXT(rtnh)) { uint32_t vxlan_vni; uint16_t encap_type; if (!RTNH_OK(rtnh, len)) { netlink_msg_ctx_set_err(ctx, "Malformed nh"); return 0; } if (rtnh->rtnh_len <= sizeof(*rtnh)) { netlink_msg_ctx_set_err(ctx, "NH len too small"); return 0; } /* * Parse attributes included in the nexthop. */ err_msg = NULL; if (!parse_rtattrs_(RTNH_DATA(rtnh), rtnh->rtnh_len - sizeof(*rtnh), rtattrs, ARRAY_SIZE(rtattrs), &err_msg)) { netlink_msg_ctx_set_err(ctx, err_msg); return 0; } gateway = rtattrs[RTA_GATEWAY]; memset(tb, 0, sizeof(tb)); if (rtattrs[RTA_ENCAP]) { parse_rtattrs_(RTA_DATA(rtattrs[RTA_ENCAP]), rtattrs[RTA_ENCAP]->rta_len - sizeof(struct rtattr), tb, ARRAY_SIZE(tb), &err_msg); } if (rtattrs[RTA_ENCAP_TYPE]) encap_type = *(uint16_t *)RTA_DATA(rtattrs[RTA_ENCAP_TYPE]); else encap_type = 0; if (tb[0]) vxlan_vni = *(uint32_t *)RTA_DATA(tb[0]); else vxlan_vni = 0; netlink_msg_ctx_add_nh(ctx, rtnh->rtnh_ifindex, gateway, encap_type, vxlan_vni); } return 1; } /* * parse_route_msg */ static int parse_route_msg(struct netlink_msg_ctx *ctx) { int len; struct rtattr **rtattrs, *rtattr, *gateway, *oif; int if_index; ctx->rtmsg = NLMSG_DATA(ctx->hdr); len = ctx->hdr->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg)); if (len < 0) { netlink_msg_ctx_set_err(ctx, "Bad message length"); return 0; } if (!parse_rtattrs(ctx, RTM_RTA(ctx->rtmsg), len)) return 0; rtattrs = ctx->rtattrs; ctx->dest = rtattrs[RTA_DST]; ctx->src = rtattrs[RTA_PREFSRC]; rtattr = rtattrs[RTA_PRIORITY]; if (rtattr) ctx->metric = (int *)RTA_DATA(rtattr); rtattr = rtattrs[RTA_NH_ID]; if (rtattr) ctx->nhgid = (unsigned int *)RTA_DATA(rtattr); gateway = rtattrs[RTA_GATEWAY]; oif = rtattrs[RTA_OIF]; if (gateway || oif) { struct rtattr *tb[RTA_MAX + 1] = { 0 }; uint16_t encap_type = 0; uint32_t vxlan_vni = 0; if_index = 0; if (oif) if_index = *((int *)RTA_DATA(oif)); if (rtattrs[RTA_ENCAP]) { const char *err_msg; parse_rtattrs_(RTA_DATA(rtattrs[RTA_ENCAP]), rtattrs[RTA_ENCAP]->rta_len - sizeof(struct rtattr), tb, ARRAY_SIZE(tb), &err_msg); } if (rtattrs[RTA_ENCAP_TYPE]) encap_type = *(uint16_t *)RTA_DATA(rtattrs[RTA_ENCAP_TYPE]); if (tb[0]) vxlan_vni = *(uint32_t *)RTA_DATA(tb[0]); netlink_msg_ctx_add_nh(ctx, if_index, gateway, encap_type, vxlan_vni); } rtattr = rtattrs[RTA_MULTIPATH]; if (rtattr) parse_multipath_attr(ctx, rtattr); return 1; } /* * addr_to_s */ static const char * addr_to_s(unsigned char family, void *addr) { size_t buf_len; char *buf; buf = get_print_buf(&buf_len); return inet_ntop(family, addr, buf, buf_len); } /* * netlink_msg_ctx_print */ static int netlink_msg_ctx_snprint(struct netlink_msg_ctx *ctx, char *buf, size_t buf_len) { struct nlmsghdr *hdr; struct rtmsg *rtmsg; struct netlink_nh *nh; char *cur, *end; unsigned long i; hdr = ctx->hdr; rtmsg = ctx->rtmsg; cur = buf; end = buf + buf_len; cur += snprintf(cur, end - cur, "%s %s/%d, Prot: %s(%u)", netlink_msg_type_to_s(hdr->nlmsg_type), addr_to_s(rtmsg->rtm_family, RTA_DATA(ctx->dest)), rtmsg->rtm_dst_len, netlink_prot_to_s(rtmsg->rtm_protocol), rtmsg->rtm_protocol); if (ctx->metric) cur += snprintf(cur, end - cur, ", Metric: %d", *ctx->metric); if (ctx->nhgid) cur += snprintf(cur, end - cur, ", nhgid: %u", *ctx->nhgid); for (i = 0; i < ctx->num_nhs; i++) { cur += snprintf(cur, end - cur, "\n "); nh = &ctx->nhs[i]; if (nh->gateway) { cur += snprintf(cur, end - cur, " %s", addr_to_s(rtmsg->rtm_family, RTA_DATA(nh->gateway))); } if (nh->if_index) { cur += snprintf(cur, end - cur, " via interface %d", nh->if_index); } if (nh->encap_type) cur += snprintf(cur, end - cur, ", Encap Type: %u Vxlan vni %u", nh->encap_type, nh->vxlan_vni); } return cur - buf; } /* * print_netlink_msg_ctx */ static void print_netlink_msg_ctx(struct netlink_msg_ctx *ctx) { char buf[1024]; netlink_msg_ctx_snprint(ctx, buf, sizeof(buf)); printf("%s\n", buf); } static void fpm_listener_hexdump(const void *mem, size_t len) { char line[64]; const uint8_t *src = mem; const uint8_t *end = src + len; if (!glob->dump_hex) return; if (len == 0) { printf("%016lx: (zero length / no data)\n", (long)src); return; } while (src < end) { struct fbuf fb = { .buf = line, .pos = line, .len = sizeof(line), }; const uint8_t *lineend = src + 8; uint32_t line_bytes = 0; printf("%016lx: ", (long)src); while (src < lineend && src < end) { printf("%02x ", *src++); line_bytes++; } if (line_bytes < 8) printf("%*s", (8 - line_bytes) * 3, ""); src -= line_bytes; while (src < lineend && src < end && fb.pos < fb.buf + fb.len) { uint8_t byte = *src++; if (isprint(byte)) *fb.pos++ = byte; else *fb.pos++ = '.'; } printf("\n"); } } /* * parse_netlink_msg */ static void parse_netlink_msg(char *buf, size_t buf_len, fpm_msg_hdr_t *fpm) { struct netlink_msg_ctx ctx_space, *ctx; struct nlmsghdr *hdr; unsigned int len; fpm_listener_hexdump(buf, buf_len); ctx = &ctx_space; hdr = (struct nlmsghdr *)buf; len = buf_len; for (; NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len)) { netlink_msg_ctx_init(ctx); ctx->hdr = hdr; switch (hdr->nlmsg_type) { case RTM_DELROUTE: case RTM_NEWROUTE: parse_route_msg(ctx); if (ctx->err_msg) { fprintf(stderr, "Error parsing route message: %s\n", ctx->err_msg); } print_netlink_msg_ctx(ctx); if (glob->reflect && hdr->nlmsg_type == RTM_NEWROUTE && ctx->rtmsg->rtm_protocol > RTPROT_STATIC) { printf(" Route %s(%u) reflecting back\n", netlink_prot_to_s( ctx->rtmsg->rtm_protocol), ctx->rtmsg->rtm_protocol); ctx->rtmsg->rtm_flags |= RTM_F_OFFLOAD; write(glob->sock, fpm, fpm_msg_len(fpm)); } break; default: fprintf(stdout, "Ignoring netlink message - Type: %s(%d)\n", netlink_msg_type_to_s(hdr->nlmsg_type), hdr->nlmsg_type); } } } /* * process_fpm_msg */ static void process_fpm_msg(fpm_msg_hdr_t *hdr) { fprintf(stdout, "FPM message - Type: %d, Length %d\n", hdr->msg_type, ntohs(hdr->msg_len)); if (hdr->msg_type != FPM_MSG_TYPE_NETLINK) { fprintf(stderr, "Unknown fpm message type %u\n", hdr->msg_type); return; } parse_netlink_msg(fpm_msg_data(hdr), fpm_msg_data_len(hdr), hdr); } /* * fpm_serve */ static void fpm_serve(void) { char buf[FPM_MAX_MSG_LEN * 4]; fpm_msg_hdr_t *hdr; while (1) { hdr = read_fpm_msg(buf, sizeof(buf)); if (!hdr) return; process_fpm_msg(hdr); } } int main(int argc, char **argv) { pid_t daemon; int r; bool fork_daemon = false; memset(glob, 0, sizeof(*glob)); while ((r = getopt(argc, argv, "rdv")) != -1) { switch (r) { case 'r': glob->reflect = true; break; case 'd': fork_daemon = true; break; case 'v': glob->dump_hex = true; break; } } if (fork_daemon) { daemon = fork(); if (daemon) exit(0); } if (!create_listen_sock(FPM_DEFAULT_PORT, &glob->server_sock)) exit(1); /* * Server forever. */ while (1) { glob->sock = accept_conn(glob->server_sock); fpm_serve(); fprintf(stdout, "Done serving client"); } } #else int main(int argc, char **argv) { fprintf(stderr, "This program only works on linux"); exit(-1); } #endif