| /* NHRP netlink/neighbor table arpd code |
| * Copyright (c) 2014-2016 Timo Teräs |
| * |
| * This file is free software: you may copy, redistribute and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, either version 2 of the License, or |
| * (at your option) any later version. |
| */ |
| |
| #include <fcntl.h> |
| #include <net/if.h> |
| #include <netinet/if_ether.h> |
| #include <linux/netlink.h> |
| #include <linux/neighbour.h> |
| #include <linux/netfilter/nfnetlink_log.h> |
| |
| #include "thread.h" |
| #include "nhrpd.h" |
| #include "netlink.h" |
| #include "znl.h" |
| |
| int netlink_req_fd = -1; |
| int netlink_nflog_group; |
| static int netlink_log_fd = -1; |
| static struct thread *netlink_log_thread; |
| static int netlink_listen_fd = -1; |
| |
| typedef void (*netlink_dispatch_f)(struct nlmsghdr *msg, struct zbuf *zb); |
| |
| void netlink_update_binding(struct interface *ifp, union sockunion *proto, union sockunion *nbma) |
| { |
| struct nlmsghdr *n; |
| struct ndmsg *ndm; |
| struct zbuf *zb = zbuf_alloc(512); |
| |
| n = znl_nlmsg_push(zb, nbma ? RTM_NEWNEIGH : RTM_DELNEIGH, NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_CREATE); |
| ndm = znl_push(zb, sizeof(*ndm)); |
| *ndm = (struct ndmsg) { |
| .ndm_family = sockunion_family(proto), |
| .ndm_ifindex = ifp->ifindex, |
| .ndm_type = RTN_UNICAST, |
| .ndm_state = nbma ? NUD_REACHABLE : NUD_FAILED, |
| }; |
| znl_rta_push(zb, NDA_DST, sockunion_get_addr(proto), family2addrsize(sockunion_family(proto))); |
| if (nbma) |
| znl_rta_push(zb, NDA_LLADDR, sockunion_get_addr(nbma), family2addrsize(sockunion_family(nbma))); |
| znl_nlmsg_complete(zb, n); |
| zbuf_send(zb, netlink_req_fd); |
| zbuf_recv(zb, netlink_req_fd); |
| zbuf_free(zb); |
| } |
| |
| static void netlink_neigh_msg(struct nlmsghdr *msg, struct zbuf *zb) |
| { |
| struct ndmsg *ndm; |
| struct rtattr *rta; |
| struct nhrp_cache *c; |
| struct interface *ifp; |
| struct zbuf payload; |
| union sockunion addr; |
| size_t len; |
| char buf[SU_ADDRSTRLEN]; |
| int state; |
| |
| ndm = znl_pull(zb, sizeof(*ndm)); |
| if (!ndm) return; |
| |
| sockunion_family(&addr) = AF_UNSPEC; |
| while ((rta = znl_rta_pull(zb, &payload)) != NULL) { |
| len = zbuf_used(&payload); |
| switch (rta->rta_type) { |
| case NDA_DST: |
| sockunion_set(&addr, ndm->ndm_family, zbuf_pulln(&payload, len), len); |
| break; |
| } |
| } |
| |
| ifp = if_lookup_by_index(ndm->ndm_ifindex); |
| if (!ifp || sockunion_family(&addr) == AF_UNSPEC) |
| return; |
| |
| c = nhrp_cache_get(ifp, &addr, 0); |
| if (!c) |
| return; |
| |
| if (msg->nlmsg_type == RTM_GETNEIGH) { |
| debugf(NHRP_DEBUG_KERNEL, "Netlink: who-has %s dev %s", |
| sockunion2str(&addr, buf, sizeof buf), |
| ifp->name); |
| |
| if (c->cur.type >= NHRP_CACHE_CACHED) { |
| nhrp_cache_set_used(c, 1); |
| netlink_update_binding(ifp, &addr, &c->cur.peer->vc->remote.nbma); |
| } |
| } else { |
| debugf(NHRP_DEBUG_KERNEL, "Netlink: update %s dev %s nud %x", |
| sockunion2str(&addr, buf, sizeof buf), |
| ifp->name, ndm->ndm_state); |
| |
| state = (msg->nlmsg_type == RTM_NEWNEIGH) ? ndm->ndm_state : NUD_FAILED; |
| nhrp_cache_set_used(c, state == NUD_REACHABLE); |
| } |
| } |
| |
| static int netlink_route_recv(struct thread *t) |
| { |
| uint8_t buf[ZNL_BUFFER_SIZE]; |
| int fd = THREAD_FD(t); |
| struct zbuf payload, zb; |
| struct nlmsghdr *n; |
| |
| zbuf_init(&zb, buf, sizeof(buf), 0); |
| while (zbuf_recv(&zb, fd) > 0) { |
| while ((n = znl_nlmsg_pull(&zb, &payload)) != 0) { |
| debugf(NHRP_DEBUG_KERNEL, "Netlink: Received msg_type %u, msg_flags %u", |
| n->nlmsg_type, n->nlmsg_flags); |
| switch (n->nlmsg_type) { |
| case RTM_GETNEIGH: |
| case RTM_NEWNEIGH: |
| case RTM_DELNEIGH: |
| netlink_neigh_msg(n, &payload); |
| break; |
| } |
| } |
| } |
| |
| thread_add_read(master, netlink_route_recv, 0, fd); |
| |
| return 0; |
| } |
| |
| static void netlink_log_register(int fd, int group) |
| { |
| struct nlmsghdr *n; |
| struct nfgenmsg *nf; |
| struct nfulnl_msg_config_cmd cmd; |
| struct zbuf *zb = zbuf_alloc(512); |
| |
| n = znl_nlmsg_push(zb, (NFNL_SUBSYS_ULOG<<8) | NFULNL_MSG_CONFIG, NLM_F_REQUEST | NLM_F_ACK); |
| nf = znl_push(zb, sizeof(*nf)); |
| *nf = (struct nfgenmsg) { |
| .nfgen_family = AF_UNSPEC, |
| .version = NFNETLINK_V0, |
| .res_id = htons(group), |
| }; |
| cmd.command = NFULNL_CFG_CMD_BIND; |
| znl_rta_push(zb, NFULA_CFG_CMD, &cmd, sizeof(cmd)); |
| znl_nlmsg_complete(zb, n); |
| |
| zbuf_send(zb, fd); |
| zbuf_free(zb); |
| } |
| |
| static void netlink_log_indication(struct nlmsghdr *msg, struct zbuf *zb) |
| { |
| struct nfgenmsg *nf; |
| struct rtattr *rta; |
| struct zbuf rtapl, pktpl; |
| struct interface *ifp; |
| struct nfulnl_msg_packet_hdr *pkthdr = NULL; |
| uint32_t *in_ndx = NULL; |
| |
| nf = znl_pull(zb, sizeof(*nf)); |
| if (!nf) return; |
| |
| memset(&pktpl, 0, sizeof(pktpl)); |
| while ((rta = znl_rta_pull(zb, &rtapl)) != NULL) { |
| switch (rta->rta_type) { |
| case NFULA_PACKET_HDR: |
| pkthdr = znl_pull(&rtapl, sizeof(*pkthdr)); |
| break; |
| case NFULA_IFINDEX_INDEV: |
| in_ndx = znl_pull(&rtapl, sizeof(*in_ndx)); |
| break; |
| case NFULA_PAYLOAD: |
| pktpl = rtapl; |
| break; |
| /* NFULA_HWHDR exists and is supposed to contain source |
| * hardware address. However, for ip_gre it seems to be |
| * the nexthop destination address if the packet matches |
| * route. */ |
| } |
| } |
| |
| if (!pkthdr || !in_ndx || !zbuf_used(&pktpl)) |
| return; |
| |
| ifp = if_lookup_by_index(htonl(*in_ndx)); |
| if (!ifp) |
| return; |
| |
| nhrp_peer_send_indication(ifp, htons(pkthdr->hw_protocol), &pktpl); |
| } |
| |
| static int netlink_log_recv(struct thread *t) |
| { |
| uint8_t buf[ZNL_BUFFER_SIZE]; |
| int fd = THREAD_FD(t); |
| struct zbuf payload, zb; |
| struct nlmsghdr *n; |
| |
| netlink_log_thread = NULL; |
| |
| zbuf_init(&zb, buf, sizeof(buf), 0); |
| while (zbuf_recv(&zb, fd) > 0) { |
| while ((n = znl_nlmsg_pull(&zb, &payload)) != 0) { |
| debugf(NHRP_DEBUG_KERNEL, "Netlink-log: Received msg_type %u, msg_flags %u", |
| n->nlmsg_type, n->nlmsg_flags); |
| switch (n->nlmsg_type) { |
| case (NFNL_SUBSYS_ULOG<<8) | NFULNL_MSG_PACKET: |
| netlink_log_indication(n, &payload); |
| break; |
| } |
| } |
| } |
| |
| THREAD_READ_ON(master, netlink_log_thread, netlink_log_recv, 0, netlink_log_fd); |
| |
| return 0; |
| } |
| |
| void netlink_set_nflog_group(int nlgroup) |
| { |
| if (netlink_log_fd >= 0) { |
| THREAD_OFF(netlink_log_thread); |
| close(netlink_log_fd); |
| netlink_log_fd = -1; |
| } |
| netlink_nflog_group = nlgroup; |
| if (nlgroup) { |
| netlink_log_fd = znl_open(NETLINK_NETFILTER, 0); |
| netlink_log_register(netlink_log_fd, nlgroup); |
| THREAD_READ_ON(master, netlink_log_thread, netlink_log_recv, 0, netlink_log_fd); |
| } |
| } |
| |
| int netlink_init(void) |
| { |
| netlink_req_fd = znl_open(NETLINK_ROUTE, 0); |
| netlink_listen_fd = znl_open(NETLINK_ROUTE, RTMGRP_NEIGH); |
| thread_add_read(master, netlink_route_recv, 0, netlink_listen_fd); |
| |
| return 0; |
| } |
| |
| int netlink_configure_arp(unsigned int ifindex, int pf) |
| { |
| struct nlmsghdr *n; |
| struct ndtmsg *ndtm; |
| struct rtattr *rta; |
| struct zbuf *zb = zbuf_alloc(512); |
| int r; |
| |
| n = znl_nlmsg_push(zb, RTM_SETNEIGHTBL, NLM_F_REQUEST | NLM_F_REPLACE); |
| ndtm = znl_push(zb, sizeof(*ndtm)); |
| *ndtm = (struct ndtmsg) { |
| .ndtm_family = pf, |
| }; |
| |
| znl_rta_push(zb, NDTA_NAME, pf == AF_INET ? "arp_cache" : "ndisc_cache", 10); |
| |
| rta = znl_rta_nested_push(zb, NDTA_PARMS); |
| znl_rta_push_u32(zb, NDTPA_IFINDEX, ifindex); |
| znl_rta_push_u32(zb, NDTPA_APP_PROBES, 1); |
| znl_rta_push_u32(zb, NDTPA_MCAST_PROBES, 0); |
| znl_rta_push_u32(zb, NDTPA_UCAST_PROBES, 0); |
| znl_rta_nested_complete(zb, rta); |
| |
| znl_nlmsg_complete(zb, n); |
| r = zbuf_send(zb, netlink_req_fd); |
| zbuf_recv(zb, netlink_req_fd); |
| zbuf_free(zb); |
| |
| return r; |
| } |