Import Upstream version 1.2.2
[quagga-debian.git] / nhrpd / netlink_arp.c
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 }