blob: a418ecabd3013f50e85704f621b190fecb182bb6 [file] [log] [blame]
/* 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;
}