Timo Teräs | dafa05e | 2017-01-19 17:27:01 +0200 | [diff] [blame] | 1 | /* NHRP netlink/neighbor table arpd code |
| 2 | * Copyright (c) 2014-2016 Timo Teräs |
| 3 | * |
| 4 | * This file is free software: you may copy, redistribute and/or modify |
| 5 | * it under the terms of the GNU General Public License as published by |
| 6 | * the Free Software Foundation, either version 2 of the License, or |
| 7 | * (at your option) any later version. |
| 8 | */ |
| 9 | |
| 10 | #include <fcntl.h> |
| 11 | #include <net/if.h> |
| 12 | #include <netinet/if_ether.h> |
| 13 | #include <linux/netlink.h> |
| 14 | #include <linux/neighbour.h> |
| 15 | #include <linux/netfilter/nfnetlink_log.h> |
| 16 | |
| 17 | #include "thread.h" |
| 18 | #include "nhrpd.h" |
| 19 | #include "netlink.h" |
| 20 | #include "znl.h" |
| 21 | |
| 22 | int netlink_req_fd = -1; |
| 23 | int netlink_nflog_group; |
| 24 | static int netlink_log_fd = -1; |
| 25 | static struct thread *netlink_log_thread; |
| 26 | static int netlink_listen_fd = -1; |
| 27 | |
| 28 | typedef void (*netlink_dispatch_f)(struct nlmsghdr *msg, struct zbuf *zb); |
| 29 | |
| 30 | void netlink_update_binding(struct interface *ifp, union sockunion *proto, union sockunion *nbma) |
| 31 | { |
| 32 | struct nlmsghdr *n; |
| 33 | struct ndmsg *ndm; |
| 34 | struct zbuf *zb = zbuf_alloc(512); |
| 35 | |
| 36 | n = znl_nlmsg_push(zb, nbma ? RTM_NEWNEIGH : RTM_DELNEIGH, NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_CREATE); |
| 37 | ndm = znl_push(zb, sizeof(*ndm)); |
| 38 | *ndm = (struct ndmsg) { |
| 39 | .ndm_family = sockunion_family(proto), |
| 40 | .ndm_ifindex = ifp->ifindex, |
| 41 | .ndm_type = RTN_UNICAST, |
| 42 | .ndm_state = nbma ? NUD_REACHABLE : NUD_FAILED, |
| 43 | }; |
| 44 | znl_rta_push(zb, NDA_DST, sockunion_get_addr(proto), family2addrsize(sockunion_family(proto))); |
| 45 | if (nbma) |
| 46 | znl_rta_push(zb, NDA_LLADDR, sockunion_get_addr(nbma), family2addrsize(sockunion_family(nbma))); |
| 47 | znl_nlmsg_complete(zb, n); |
| 48 | zbuf_send(zb, netlink_req_fd); |
| 49 | zbuf_recv(zb, netlink_req_fd); |
| 50 | zbuf_free(zb); |
| 51 | } |
| 52 | |
| 53 | static void netlink_neigh_msg(struct nlmsghdr *msg, struct zbuf *zb) |
| 54 | { |
| 55 | struct ndmsg *ndm; |
| 56 | struct rtattr *rta; |
| 57 | struct nhrp_cache *c; |
| 58 | struct interface *ifp; |
| 59 | struct zbuf payload; |
| 60 | union sockunion addr; |
| 61 | size_t len; |
| 62 | char buf[SU_ADDRSTRLEN]; |
| 63 | int state; |
| 64 | |
| 65 | ndm = znl_pull(zb, sizeof(*ndm)); |
| 66 | if (!ndm) return; |
| 67 | |
| 68 | sockunion_family(&addr) = AF_UNSPEC; |
| 69 | while ((rta = znl_rta_pull(zb, &payload)) != NULL) { |
| 70 | len = zbuf_used(&payload); |
| 71 | switch (rta->rta_type) { |
| 72 | case NDA_DST: |
| 73 | sockunion_set(&addr, ndm->ndm_family, zbuf_pulln(&payload, len), len); |
| 74 | break; |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | ifp = if_lookup_by_index(ndm->ndm_ifindex); |
| 79 | if (!ifp || sockunion_family(&addr) == AF_UNSPEC) |
| 80 | return; |
| 81 | |
| 82 | c = nhrp_cache_get(ifp, &addr, 0); |
| 83 | if (!c) |
| 84 | return; |
| 85 | |
| 86 | if (msg->nlmsg_type == RTM_GETNEIGH) { |
| 87 | debugf(NHRP_DEBUG_KERNEL, "Netlink: who-has %s dev %s", |
| 88 | sockunion2str(&addr, buf, sizeof buf), |
| 89 | ifp->name); |
| 90 | |
| 91 | if (c->cur.type >= NHRP_CACHE_CACHED) { |
| 92 | nhrp_cache_set_used(c, 1); |
| 93 | netlink_update_binding(ifp, &addr, &c->cur.peer->vc->remote.nbma); |
| 94 | } |
| 95 | } else { |
| 96 | debugf(NHRP_DEBUG_KERNEL, "Netlink: update %s dev %s nud %x", |
| 97 | sockunion2str(&addr, buf, sizeof buf), |
| 98 | ifp->name, ndm->ndm_state); |
| 99 | |
| 100 | state = (msg->nlmsg_type == RTM_NEWNEIGH) ? ndm->ndm_state : NUD_FAILED; |
| 101 | nhrp_cache_set_used(c, state == NUD_REACHABLE); |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | static int netlink_route_recv(struct thread *t) |
| 106 | { |
| 107 | uint8_t buf[ZNL_BUFFER_SIZE]; |
| 108 | int fd = THREAD_FD(t); |
| 109 | struct zbuf payload, zb; |
| 110 | struct nlmsghdr *n; |
| 111 | |
| 112 | zbuf_init(&zb, buf, sizeof(buf), 0); |
| 113 | while (zbuf_recv(&zb, fd) > 0) { |
| 114 | while ((n = znl_nlmsg_pull(&zb, &payload)) != 0) { |
| 115 | debugf(NHRP_DEBUG_KERNEL, "Netlink: Received msg_type %u, msg_flags %u", |
| 116 | n->nlmsg_type, n->nlmsg_flags); |
| 117 | switch (n->nlmsg_type) { |
| 118 | case RTM_GETNEIGH: |
| 119 | case RTM_NEWNEIGH: |
| 120 | case RTM_DELNEIGH: |
| 121 | netlink_neigh_msg(n, &payload); |
| 122 | break; |
| 123 | } |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | thread_add_read(master, netlink_route_recv, 0, fd); |
| 128 | |
| 129 | return 0; |
| 130 | } |
| 131 | |
| 132 | static void netlink_log_register(int fd, int group) |
| 133 | { |
| 134 | struct nlmsghdr *n; |
| 135 | struct nfgenmsg *nf; |
| 136 | struct nfulnl_msg_config_cmd cmd; |
| 137 | struct zbuf *zb = zbuf_alloc(512); |
| 138 | |
| 139 | n = znl_nlmsg_push(zb, (NFNL_SUBSYS_ULOG<<8) | NFULNL_MSG_CONFIG, NLM_F_REQUEST | NLM_F_ACK); |
| 140 | nf = znl_push(zb, sizeof(*nf)); |
| 141 | *nf = (struct nfgenmsg) { |
| 142 | .nfgen_family = AF_UNSPEC, |
| 143 | .version = NFNETLINK_V0, |
| 144 | .res_id = htons(group), |
| 145 | }; |
| 146 | cmd.command = NFULNL_CFG_CMD_BIND; |
| 147 | znl_rta_push(zb, NFULA_CFG_CMD, &cmd, sizeof(cmd)); |
| 148 | znl_nlmsg_complete(zb, n); |
| 149 | |
| 150 | zbuf_send(zb, fd); |
| 151 | zbuf_free(zb); |
| 152 | } |
| 153 | |
| 154 | static void netlink_log_indication(struct nlmsghdr *msg, struct zbuf *zb) |
| 155 | { |
| 156 | struct nfgenmsg *nf; |
| 157 | struct rtattr *rta; |
| 158 | struct zbuf rtapl, pktpl; |
| 159 | struct interface *ifp; |
| 160 | struct nfulnl_msg_packet_hdr *pkthdr = NULL; |
| 161 | uint32_t *in_ndx = NULL; |
| 162 | |
| 163 | nf = znl_pull(zb, sizeof(*nf)); |
| 164 | if (!nf) return; |
| 165 | |
| 166 | memset(&pktpl, 0, sizeof(pktpl)); |
| 167 | while ((rta = znl_rta_pull(zb, &rtapl)) != NULL) { |
| 168 | switch (rta->rta_type) { |
| 169 | case NFULA_PACKET_HDR: |
| 170 | pkthdr = znl_pull(&rtapl, sizeof(*pkthdr)); |
| 171 | break; |
| 172 | case NFULA_IFINDEX_INDEV: |
| 173 | in_ndx = znl_pull(&rtapl, sizeof(*in_ndx)); |
| 174 | break; |
| 175 | case NFULA_PAYLOAD: |
| 176 | pktpl = rtapl; |
| 177 | break; |
| 178 | /* NFULA_HWHDR exists and is supposed to contain source |
| 179 | * hardware address. However, for ip_gre it seems to be |
| 180 | * the nexthop destination address if the packet matches |
| 181 | * route. */ |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | if (!pkthdr || !in_ndx || !zbuf_used(&pktpl)) |
| 186 | return; |
| 187 | |
| 188 | ifp = if_lookup_by_index(htonl(*in_ndx)); |
| 189 | if (!ifp) |
| 190 | return; |
| 191 | |
| 192 | nhrp_peer_send_indication(ifp, htons(pkthdr->hw_protocol), &pktpl); |
| 193 | } |
| 194 | |
| 195 | static int netlink_log_recv(struct thread *t) |
| 196 | { |
| 197 | uint8_t buf[ZNL_BUFFER_SIZE]; |
| 198 | int fd = THREAD_FD(t); |
| 199 | struct zbuf payload, zb; |
| 200 | struct nlmsghdr *n; |
| 201 | |
| 202 | netlink_log_thread = NULL; |
| 203 | |
| 204 | zbuf_init(&zb, buf, sizeof(buf), 0); |
| 205 | while (zbuf_recv(&zb, fd) > 0) { |
| 206 | while ((n = znl_nlmsg_pull(&zb, &payload)) != 0) { |
| 207 | debugf(NHRP_DEBUG_KERNEL, "Netlink-log: Received msg_type %u, msg_flags %u", |
| 208 | n->nlmsg_type, n->nlmsg_flags); |
| 209 | switch (n->nlmsg_type) { |
| 210 | case (NFNL_SUBSYS_ULOG<<8) | NFULNL_MSG_PACKET: |
| 211 | netlink_log_indication(n, &payload); |
| 212 | break; |
| 213 | } |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | THREAD_READ_ON(master, netlink_log_thread, netlink_log_recv, 0, netlink_log_fd); |
| 218 | |
| 219 | return 0; |
| 220 | } |
| 221 | |
| 222 | void netlink_set_nflog_group(int nlgroup) |
| 223 | { |
| 224 | if (netlink_log_fd >= 0) { |
| 225 | THREAD_OFF(netlink_log_thread); |
| 226 | close(netlink_log_fd); |
| 227 | netlink_log_fd = -1; |
| 228 | } |
| 229 | netlink_nflog_group = nlgroup; |
| 230 | if (nlgroup) { |
| 231 | netlink_log_fd = znl_open(NETLINK_NETFILTER, 0); |
| 232 | netlink_log_register(netlink_log_fd, nlgroup); |
| 233 | THREAD_READ_ON(master, netlink_log_thread, netlink_log_recv, 0, netlink_log_fd); |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | int netlink_init(void) |
| 238 | { |
| 239 | netlink_req_fd = znl_open(NETLINK_ROUTE, 0); |
| 240 | netlink_listen_fd = znl_open(NETLINK_ROUTE, RTMGRP_NEIGH); |
| 241 | thread_add_read(master, netlink_route_recv, 0, netlink_listen_fd); |
| 242 | |
| 243 | return 0; |
| 244 | } |
| 245 | |
| 246 | int netlink_configure_arp(unsigned int ifindex, int pf) |
| 247 | { |
| 248 | struct nlmsghdr *n; |
| 249 | struct ndtmsg *ndtm; |
| 250 | struct rtattr *rta; |
| 251 | struct zbuf *zb = zbuf_alloc(512); |
| 252 | int r; |
| 253 | |
| 254 | n = znl_nlmsg_push(zb, RTM_SETNEIGHTBL, NLM_F_REQUEST | NLM_F_REPLACE); |
| 255 | ndtm = znl_push(zb, sizeof(*ndtm)); |
| 256 | *ndtm = (struct ndtmsg) { |
| 257 | .ndtm_family = pf, |
| 258 | }; |
| 259 | |
| 260 | znl_rta_push(zb, NDTA_NAME, pf == AF_INET ? "arp_cache" : "ndisc_cache", 10); |
| 261 | |
| 262 | rta = znl_rta_nested_push(zb, NDTA_PARMS); |
| 263 | znl_rta_push_u32(zb, NDTPA_IFINDEX, ifindex); |
| 264 | znl_rta_push_u32(zb, NDTPA_APP_PROBES, 1); |
| 265 | znl_rta_push_u32(zb, NDTPA_MCAST_PROBES, 0); |
| 266 | znl_rta_push_u32(zb, NDTPA_UCAST_PROBES, 0); |
| 267 | znl_rta_nested_complete(zb, rta); |
| 268 | |
| 269 | znl_nlmsg_complete(zb, n); |
| 270 | r = zbuf_send(zb, netlink_req_fd); |
| 271 | zbuf_recv(zb, netlink_req_fd); |
| 272 | zbuf_free(zb); |
| 273 | |
| 274 | return r; |
| 275 | } |