327 lines
7.3 KiB
C
327 lines
7.3 KiB
C
/**
|
|
* Copyright (C) 2017 Hans Dedecker <dedeckeh@gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License v2 as published by
|
|
* the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
|
|
#include <netlink/netlink.h>
|
|
#include <netlink/msg.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include "6brd.h"
|
|
|
|
struct event_socket {
|
|
struct odhcpd_event ev;
|
|
struct nl_sock *sock;
|
|
int sock_bufsize;
|
|
};
|
|
|
|
static void handle_rtnl_event(struct odhcpd_event *ev);
|
|
static int cb_rtnl_valid(struct nl_msg *msg, void *arg);
|
|
static void catch_rtnl_err(struct odhcpd_event *e, int error);
|
|
static struct nl_sock *create_socket(int protocol);
|
|
static void netlink_dump_addr_table (const int v6);
|
|
|
|
static struct nl_sock *rtnl_socket = NULL;
|
|
static struct event_socket rtnl_event = {
|
|
.ev = {
|
|
.uloop = {.fd = - 1, },
|
|
.handle_dgram = NULL,
|
|
.handle_error = catch_rtnl_err,
|
|
.recv_msgs = handle_rtnl_event,
|
|
},
|
|
.sock = NULL,
|
|
.sock_bufsize = 133120,
|
|
};
|
|
|
|
int netlink_init(void)
|
|
{
|
|
rtnl_socket = create_socket(NETLINK_ROUTE);
|
|
if (!rtnl_socket) {
|
|
do_log (LOG_ERR, "Unable to open nl socket: %s", strerror (errno));
|
|
goto err;
|
|
}
|
|
|
|
rtnl_event.sock = create_socket(NETLINK_ROUTE);
|
|
if (!rtnl_event.sock) {
|
|
do_log (LOG_ERR, "Unable to open nl event socket: %s", strerror (errno));
|
|
goto err;
|
|
}
|
|
|
|
rtnl_event.ev.uloop.fd = nl_socket_get_fd(rtnl_event.sock);
|
|
|
|
if (nl_socket_set_buffer_size(rtnl_event.sock, rtnl_event.sock_bufsize, 0))
|
|
goto err;
|
|
|
|
nl_socket_disable_seq_check(rtnl_event.sock);
|
|
|
|
nl_socket_modify_cb(rtnl_event.sock, NL_CB_VALID, NL_CB_CUSTOM,
|
|
cb_rtnl_valid, NULL);
|
|
|
|
if (nl_socket_add_memberships (rtnl_event.sock,
|
|
RTNLGRP_NEIGH, RTNLGRP_LINK, 0))
|
|
goto err;
|
|
|
|
odhcpd_register(&rtnl_event.ev);
|
|
|
|
return 0;
|
|
|
|
err:
|
|
if (rtnl_socket) {
|
|
nl_socket_free(rtnl_socket);
|
|
rtnl_socket = NULL;
|
|
}
|
|
|
|
if (rtnl_event.sock) {
|
|
nl_socket_free(rtnl_event.sock);
|
|
rtnl_event.sock = NULL;
|
|
rtnl_event.ev.uloop.fd = -1;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static void handle_rtnl_event(struct odhcpd_event *e)
|
|
{
|
|
struct event_socket *ev_sock = container_of(e, struct event_socket, ev);
|
|
|
|
nl_recvmsgs_default(ev_sock->sock);
|
|
}
|
|
|
|
/* Handler for neighbor cache entries from the kernel. This is our source
|
|
* to learn and unlearn hosts on interfaces. */
|
|
static int cb_rtnl_valid(struct nl_msg *msg, void *arg)
|
|
{
|
|
struct nlmsghdr *hdr = nlmsg_hdr(msg);
|
|
struct netevent_handler_info event_info;
|
|
int add = 0;
|
|
char ipbuf[INET6_ADDRSTRLEN];
|
|
|
|
memset(&event_info, 0, sizeof(event_info));
|
|
switch (hdr->nlmsg_type) {
|
|
case RTM_NEWLINK: {
|
|
struct ifinfomsg *ifi = nlmsg_data(hdr);
|
|
struct nlattr *nla[__IFLA_MAX];
|
|
|
|
if (!nlmsg_valid_hdr(hdr, sizeof(*ifi)) ||
|
|
ifi->ifi_family != AF_UNSPEC)
|
|
return NL_SKIP;
|
|
|
|
nlmsg_parse(hdr, sizeof(*ifi), nla, __IFLA_MAX - 1, NULL);
|
|
if (!nla[IFLA_IFNAME])
|
|
return NL_SKIP;
|
|
|
|
event_info.iface = odhcpd_get_interface_by_name(nla_get_string(nla[IFLA_IFNAME]));
|
|
if (!event_info.iface)
|
|
return NL_SKIP;
|
|
|
|
if (event_info.iface->ifindex != ifi->ifi_index) {
|
|
event_info.iface->ifindex = ifi->ifi_index;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RTM_NEWNEIGH:
|
|
add = 1;
|
|
/* fall through */
|
|
case RTM_DELNEIGH: {
|
|
struct ndmsg *ndm = nlmsg_data(hdr);
|
|
struct nlattr *nla[__NDA_MAX];
|
|
|
|
if (!nlmsg_valid_hdr(hdr, sizeof(*ndm)) ||
|
|
ndm->ndm_family != AF_INET6)
|
|
return NL_SKIP;
|
|
|
|
event_info.iface = odhcpd_get_interface_by_index(ndm->ndm_ifindex);
|
|
if (!event_info.iface)
|
|
return NL_SKIP;
|
|
|
|
nlmsg_parse(hdr, sizeof(*ndm), nla, __NDA_MAX - 1, NULL);
|
|
if (!nla[NDA_DST])
|
|
return NL_SKIP;
|
|
|
|
nla_memcpy(&event_info.neigh.dst, nla[NDA_DST], sizeof(event_info.neigh.dst));
|
|
|
|
if (IN6_IS_ADDR_LINKLOCAL(&event_info.neigh.dst) ||
|
|
IN6_IS_ADDR_MULTICAST(&event_info.neigh.dst))
|
|
return NL_SKIP;
|
|
|
|
inet_ntop(AF_INET6, &event_info.neigh.dst, ipbuf, sizeof(ipbuf));
|
|
do_log (LOG_DEBUG, "Netlink %s %s%%%s", add ? "newneigh" : "delneigh",
|
|
ipbuf, event_info.iface->ifname);
|
|
|
|
event_info.neigh.state = ndm->ndm_state;
|
|
event_info.neigh.flags = ndm->ndm_flags;
|
|
ndp_netevent_cb (add ? NETEV_NEIGH6_ADD : NETEV_NEIGH6_DEL, &event_info);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return NL_SKIP;
|
|
}
|
|
|
|
return NL_OK;
|
|
}
|
|
|
|
static void catch_rtnl_err(struct odhcpd_event *e, int error)
|
|
{
|
|
struct event_socket *ev_sock = container_of(e, struct event_socket, ev);
|
|
|
|
if (error != ENOBUFS)
|
|
goto err;
|
|
|
|
/* Double netlink event buffer size */
|
|
ev_sock->sock_bufsize *= 2;
|
|
|
|
if (nl_socket_set_buffer_size(ev_sock->sock, ev_sock->sock_bufsize, 0))
|
|
goto err;
|
|
|
|
netlink_dump_addr_table(1);
|
|
return;
|
|
|
|
err:
|
|
odhcpd_deregister(e);
|
|
}
|
|
|
|
static struct nl_sock *create_socket(int protocol)
|
|
{
|
|
struct nl_sock *nl_sock;
|
|
|
|
nl_sock = nl_socket_alloc();
|
|
if (!nl_sock)
|
|
goto err;
|
|
|
|
if (nl_connect(nl_sock, protocol) < 0)
|
|
goto err;
|
|
|
|
return nl_sock;
|
|
|
|
err:
|
|
if (nl_sock)
|
|
nl_socket_free(nl_sock);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int netlink_setup_route(const struct in6_addr *addr, const int prefixlen,
|
|
const int ifindex, const struct in6_addr *gw,
|
|
const uint32_t metric, const int add)
|
|
{
|
|
struct nl_msg *msg;
|
|
struct rtmsg rtm = {
|
|
.rtm_family = AF_INET6,
|
|
.rtm_dst_len = prefixlen,
|
|
.rtm_src_len = 0,
|
|
.rtm_table = RT_TABLE_MAIN,
|
|
.rtm_protocol = (add ? RTPROT_STATIC : RTPROT_UNSPEC),
|
|
.rtm_scope = (add ? (gw ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK) : RT_SCOPE_NOWHERE),
|
|
.rtm_type = (add ? RTN_UNICAST : RTN_UNSPEC),
|
|
};
|
|
int ret = 0;
|
|
|
|
msg = nlmsg_alloc_simple(add ? RTM_NEWROUTE : RTM_DELROUTE,
|
|
add ? NLM_F_CREATE | NLM_F_REPLACE : 0);
|
|
if (!msg)
|
|
return -1;
|
|
|
|
nlmsg_append(msg, &rtm, sizeof(rtm), 0);
|
|
|
|
nla_put(msg, RTA_DST, sizeof(*addr), addr);
|
|
nla_put_u32(msg, RTA_OIF, ifindex);
|
|
nla_put_u32(msg, RTA_PRIORITY, metric);
|
|
|
|
if (gw)
|
|
nla_put(msg, RTA_GATEWAY, sizeof(*gw), gw);
|
|
|
|
ret = nl_send_auto_complete(rtnl_socket, msg);
|
|
nlmsg_free(msg);
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return nl_wait_for_ack(rtnl_socket);
|
|
}
|
|
|
|
|
|
int netlink_setup_proxy_neigh(const struct in6_addr *addr,
|
|
const int ifindex, const int add)
|
|
{
|
|
struct nl_msg *msg;
|
|
struct ndmsg ndm = {
|
|
.ndm_family = AF_INET6,
|
|
.ndm_flags = NTF_PROXY,
|
|
.ndm_ifindex = ifindex,
|
|
};
|
|
int ret = 0, flags = NLM_F_REQUEST;
|
|
|
|
if (add)
|
|
flags |= NLM_F_REPLACE | NLM_F_CREATE;
|
|
|
|
msg = nlmsg_alloc_simple(add ? RTM_NEWNEIGH : RTM_DELNEIGH, flags);
|
|
if (!msg)
|
|
return -1;
|
|
|
|
nlmsg_append(msg, &ndm, sizeof(ndm), 0);
|
|
|
|
nla_put(msg, NDA_DST, sizeof(*addr), addr);
|
|
|
|
ret = nl_send_auto_complete(rtnl_socket, msg);
|
|
nlmsg_free(msg);
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return nl_wait_for_ack(rtnl_socket);
|
|
}
|
|
|
|
|
|
void netlink_dump_neigh_table(const int proxy)
|
|
{
|
|
struct nl_msg *msg;
|
|
struct ndmsg ndm = {
|
|
.ndm_family = AF_INET6,
|
|
.ndm_flags = proxy ? NTF_PROXY : 0,
|
|
};
|
|
|
|
msg = nlmsg_alloc_simple(RTM_GETNEIGH, NLM_F_REQUEST | NLM_F_DUMP);
|
|
if (!msg)
|
|
return;
|
|
|
|
nlmsg_append(msg, &ndm, sizeof(ndm), 0);
|
|
|
|
nl_send_auto_complete(rtnl_event.sock, msg);
|
|
|
|
nlmsg_free(msg);
|
|
}
|
|
|
|
static void netlink_dump_addr_table (const int v6)
|
|
{
|
|
struct nl_msg *msg;
|
|
struct ifaddrmsg ifa = {
|
|
.ifa_family = v6 ? AF_INET6 : AF_INET,
|
|
};
|
|
|
|
msg = nlmsg_alloc_simple(RTM_GETADDR, NLM_F_REQUEST | NLM_F_DUMP);
|
|
if (!msg)
|
|
return;
|
|
|
|
nlmsg_append(msg, &ifa, sizeof(ifa), 0);
|
|
|
|
nl_send_auto_complete(rtnl_event.sock, msg);
|
|
|
|
nlmsg_free(msg);
|
|
}
|