Newer
Older
* hostmngr.c - implementation file for network hosts management.
*
* Copyright (C) 2023 IOPSYS Software Solutions AB. All rights reserved.
*
* See LICENSE file for license related information.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if_arp.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <netlink/route/link.h>
#include <json-c/json.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>
#include <easy/easy.h>
#include "debug.h"
#include "timer.h"
#include "neigh.h"
static void hostmngr_update_interface_bridgeinfo(struct local_interface *iface)
{
iface->is_bridge = if_isbridge(iface->ifname);
if (!iface->is_bridge) {
int br_ifindex;
br_ifindex = if_isbridge_interface(iface->ifname);
if (br_ifindex > 0) {
iface->is_brif = true;
iface->brport = if_brportnum(iface->ifname);
iface->br_ifindex = br_ifindex;
}
}
}
static void hostmngr_update_interface_ipaddrs(struct local_interface *iface)
{
struct ip_address ips[32] = {0};
int num_ipaddrs = 32;
int ret;
ret = if_getaddrs(iface->ifname, ips, &num_ipaddrs);
if (!ret && num_ipaddrs > 0) {
iface->ipaddrs = calloc(num_ipaddrs, sizeof(struct ip_address));
if (iface->ipaddrs) {
iface->num_ipaddrs = num_ipaddrs;
memcpy(iface->ipaddrs, ips, num_ipaddrs * sizeof(struct ip_address));
}
}
static void hostmngr_update_interface_mediatype(struct local_interface *iface)
{
if (strncmp(iface->ifname, "lo", 2)) {
if (is_wifi_interface(iface->ifname)) {
iface->mediatype = IF_MEDIA_WIFI;
iface->is_affiliated_ap = is_affiliated_ap(iface->ifname);
if (iface->is_affiliated_ap)
ap_get_mld_ifname(iface->ifname, iface->mld_ifname);
}
} else {
iface->mediatype = IF_MEDIA_ETH;
}
}
}
struct local_interface *hostmngr_alloc_interface(const char *ifname)
{
struct local_interface *iface = NULL;
iface = calloc(1, sizeof(*iface));
if (!iface) {
return NULL;
}
snprintf(iface->ifname, 16, "%s", ifname);
iface->ifindex = if_nametoindex(ifname);
dbg("%s: %s (%d)\n", __func__, iface->ifname, iface->ifindex);
if_gethwaddr(ifname, iface->macaddr);
if_getflags(ifname, &iface->ifflags);
if_getoperstate(ifname, &iface->operstate);
if_getcarrier(ifname, &iface->carrier);
hostmngr_update_interface_bridgeinfo(iface);
hostmngr_update_interface_ipaddrs(iface);
hostmngr_update_interface_mediatype(iface);
void enum_interfaces_cb(struct nl_object *obj, void *arg)
{
struct enum_interfaces_arg {
struct list_head *iflist;
int *n;
} *argp = arg;
struct ip_address ips[32] = {0};
struct local_interface *iface = NULL;
struct rtnl_link *link = NULL;
struct nl_addr *a = NULL;
nl_object_get(obj);
link = (struct rtnl_link *)obj;
if (rtnl_link_get_arptype(link) == ARPHRD_VOID)
goto out;
iface = calloc(1, sizeof(*iface));
if (!iface) {
*argp->n = 0;
return;
}
strncpy(iface->ifname, rtnl_link_get_name(link), 16);
iface->ifindex = rtnl_link_get_ifindex(link);
iface->priv = NULL;
if (a && nl_addr_get_len(a) == 6)
memcpy(iface->macaddr, nl_addr_get_binary_addr(a), nl_addr_get_len(a));
iface->ifflags = rtnl_link_get_flags(link);
iface->operstate = rtnl_link_get_operstate(link);
hostmngr_update_interface_bridgeinfo(iface);
hostmngr_update_interface_ipaddrs(iface);
hostmngr_update_interface_mediatype(iface);
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
list_add_tail(&iface->list, argp->iflist);
*argp->n += 1;
out:
nl_object_put((struct nl_object *)link);
}
int enum_interfaces(struct list_head *head, int *num)
{
struct nl_cache *cache;
struct nl_sock *sk;
int ret = 0;
struct enum_interfaces_arg {
struct list_head *head;
int *n;
} arg = {
.head = head,
.n = num,
};
sk = nl_socket_alloc();
if (sk == NULL) {
ret = -errno;
return ret;
}
nl_connect(sk, NETLINK_ROUTE);
ret = rtnl_link_alloc_cache(sk, AF_UNSPEC, &cache);
if (ret) {
nl_socket_free(sk);
ret = -errno;
return -1;
}
nl_cache_foreach(cache, enum_interfaces_cb, &arg);
nl_cache_put(cache);
nl_socket_free(sk);
return 0;
}
int hostmngr_enumerate_interfaces(struct hostmngr_private *priv)
{
return enum_interfaces(&priv->iflist, &priv->num_local_interfaces);
}
static void hostmngr_init_interface_private(struct hostmngr_private *priv)
{
struct local_interface *e;
if (priv->num_local_interfaces == 0 || list_empty(&priv->iflist))
return;
list_for_each_entry(e, &priv->iflist, list) {
e->priv = priv;
continue;
if (e->is_bridge)
hostmngr_register_arp_sock(e->ifname, &e->arpsk, e);
void hostmngr_print_interfaces(struct hostmngr_private *priv)
{
struct local_interface *e;
int i = 0;
if (priv->num_local_interfaces == 0 || list_empty(&priv->iflist))
return;
list_for_each_entry(e, &priv->iflist, list) {
dbg("%d: ifname = %16s, mac = " MACFMT ", ifindex = %3d, flags = 0x%08x, opstate = 0x%02x, is-bridge = %d, brport = %d\n",
e->ifflags, e->operstate, e->is_bridge, e->brport);
void hostmngr_free_interfaces(struct hostmngr_private *priv)
{
struct local_interface *e = NULL, *tmp;
int i = 0;
if (priv->num_local_interfaces == 0 || list_empty(&priv->iflist))
return;
list_for_each_entry_safe(e, tmp, &priv->iflist, list) {
if (e->mediainfo)
free(e->mediainfo);
if (e->num_ipaddrs && e->ipaddrs)
free(e->ipaddrs);
list_del(&e->list);
free(e);
}
}
int hostmngr_enumerate_interfaces_in_zone(struct hostmngr_private *priv,
const char *zone, char *iflist,
int *num)
{
}
#endif
int hostmngr_include_interface(struct hostmngr_private *priv, const char *ifname)
if (if_isbridge(ifname)) {
char ifnames[32][16] = {0};
int n = 32;
int ret;
int i;
ret = br_get_iflist(ifname, &n, ifnames);
if (ret)
return -1;
list_for_each_entry(e, &priv->iflist, list) {
for (i = 0; i < n; i++) {
if (!strncmp(e->ifname, ifnames[i], strlen(e->ifname))) {
e->exclude = 0;
dbg("Include interface '%s'\n", e->ifname);
break;
}
}
}
}
if (!strncmp(e->ifname, ifname, strlen(e->ifname))) {
e->exclude = 0;
dbg("Include interface '%s'\n", e->ifname);
break;
}
}
return 0;
}
int hostmngr_exclude_interfaces(struct hostmngr_private *priv, void *useropts)
struct hostmngr_useropts *uopts = (struct hostmngr_useropts *)useropts;
struct local_interface *e;
int i;
/* exclude interface(s) passed in cmdline */
if (!uopts || uopts->num_exclude == 0)
return 0;
list_for_each_entry(e, &priv->iflist, list) {
for (i = 0; i < uopts->num_exclude; i++) {
if (!strncmp(e->ifname, uopts->exclude_ifnames[i],
strlen(e->ifname))) {
e->exclude = 1;
break;
}
}
int hostmngr_get_interface_neighbors(struct hostmngr_private *priv)
{
struct local_interface *e;
if (priv->num_local_interfaces == 0 || list_empty(&priv->iflist))
return 0;
list_for_each_entry(e, &priv->iflist, list) {
if (e->exclude || hwaddr_is_zero(e->macaddr))
continue;
if (e->is_ap)
hostmngr_get_wifi_stations(priv, e);
}
list_for_each_entry(e, &priv->iflist, list) {
if (e->exclude || hwaddr_is_zero(e->macaddr))
continue;
hostmngr_get_known_neighbors(priv, e->ifname);
}
return 0;
}
void remove_newline(char *buf)
{
int len = strlen(buf) - 1;
if (buf[len] == '\n')
buf[len] = 0;
}
int read_dhcpv4_lease_table(void **clients, int *num_clients)
{
struct dhcp_clients {
uint8_t macaddr[6];
char ipv4[24];
char hostname[256];
unsigned long leasetime;
} *hosts = NULL, *tmp = NULL, *ptr;
FILE *f = NULL;
char line[256];
int cnt = 0;
int ret = 0;
f = fopen("/var/dhcp.leases", "r");
if (!f)
return -1;
while (fgets(line, sizeof(line), f) != NULL) {
unsigned long leasetime = 0;
char macaddr[18] = {0};
char ipaddr[24] = {0};
char hostname[256] = {0};
char clid[256] = {0};
remove_newline(line);
if (sscanf(line, "%lu %17s %23s %255s %255s", &leasetime,
ptr = realloc(hosts, (cnt + 1) * sizeof(struct dhcp_clients));
if (!ptr) {
if (hosts)
free(hosts);
tmp = hosts + cnt;
memset(tmp, 0, sizeof(struct dhcp_clients));
hwaddr_aton(macaddr, tmp->macaddr);
strncpy(tmp->ipv4, ipaddr, sizeof(ipaddr)-1);
strncpy(tmp->hostname, hostname, sizeof(hostname)-1);
tmp->leasetime = leasetime;
cnt += 1;
}
}
*clients = hosts;
*num_clients = cnt;
dbg("%s: num-dhcphosts = %d\n", __func__, *num_clients);
void hostmngr_update_hostname_of_neighbors(struct hostmngr_private *priv,
void *dhclients, int num)
{
struct dhcp_clients {
uint8_t macaddr[6];
char ipv4[24];
char hostname[256];
unsigned long leasetime;
} *h, *hosts = (struct dhcp_clients *)dhclients;
struct neigh_entry *e = NULL;
struct neigh_ip_entry *ipn;
int i;
if (num == 0 || !hosts)
return;
for (i = 0; i < num; i++) {
h = hosts + i;
e = neigh_lookup(&priv->neigh_q, h->macaddr);
if (e) {
strncpy(e->hostname, h->hostname, sizeof(e->hostname) - 1);
inet_aton(h->ipv4, &e->ipv4.addr.ip4);
e->ipv4.family = AF_INET;
e->leasetime = h->leasetime;
e->ipv4_type_dhcp = 1;
if (!neigh_is_ip_known(e, &e->ipv4)) {
struct ip_address_entry *new;
char newip[64] = {0};
inet_ntop(e->ipv4.family, &e->ipv4.addr, newip, sizeof(newip));
dbg("%s: Adding new ipaddress %s to entry " MACFMT"\n",
__func__, newip, MAC2STR(e->macaddr));
new = calloc(1, sizeof(*new));
if (new) {
memcpy(&new->ip, &e->ipv4, sizeof(e->ipv4));
list_add_tail(&new->list, &e->iplist);
}
}
ipn = neigh_ip_entry_lookup2(priv, &e->ipv4);
if (ipn) {
ipn->neigh = e;
memcpy(ipn->macaddr, e->macaddr, 6);
dbg("Host " MACFMT " with ip = %s (0x%x) added to iptable\n",
MAC2STR(e->macaddr), h->ipv4, e->ipv4.addr.ip4.s_addr);
int hostmngr_get_neigh_hostname(struct neigh_queue *q, uint8_t *macaddr)
struct hostmngr_private *priv = container_of(q, struct hostmngr_private, neigh_q); //TODO:
struct dhcp_clients {
uint8_t macaddr[6];
char ipv4[24];
char hostname[256];
unsigned long leasetime;
} *h, *hosts;
struct neigh_entry *e = NULL;
struct neigh_ip_entry *ipn;
int i;
void *entries = NULL;
int num = 0;
int ret;
e = neigh_lookup(q, macaddr);
if (!e)
return -1;
ret = read_dhcpv4_lease_table(&entries, &num);
if (ret || num <= 0) {
ret = -1;
goto out;
}
dbg("%s: num-dhcphosts = %d\n", __func__, num);
hosts = (struct dhcp_clients *)entries;
for (i = 0; i < num; i++) {
h = hosts + i;
if (hwaddr_equal(h->macaddr, macaddr)) {
strncpy(e->hostname, h->hostname, sizeof(e->hostname) - 1);
inet_aton(h->ipv4, &e->ipv4.addr.ip4);
e->ipv4.family = AF_INET;
e->leasetime = h->leasetime;
e->ipv4_type_dhcp = 1;
if (!neigh_is_ip_known(e, &e->ipv4)) {
struct ip_address_entry *new;
char newip[64] = {0};
inet_ntop(e->ipv4.family, &e->ipv4.addr, newip, sizeof(newip));
dbg("%s: Adding new ipaddress %s to entry " MACFMT"\n",
__func__, newip, MAC2STR(e->macaddr));
new = calloc(1, sizeof(*new));
if (new) {
memcpy(&new->ip, &e->ipv4, sizeof(e->ipv4));
list_add_tail(&new->list, &e->iplist);
}
}
ipn = neigh_ip_entry_lookup2(priv, &e->ipv4);
if (ipn) {
ipn->neigh = e;
memcpy(ipn->macaddr, e->macaddr, 6);
dbg("Host " MACFMT " with ip = %s (0x%x) added to iptable\n",
MAC2STR(e->macaddr), h->ipv4, e->ipv4.addr.ip4.s_addr);
out:
/* if lease table has been consulted before with no results
* assume static IP address
*/
if (e->event_pending && !strlen(e->hostname)) {
e->ipv4_type_static = 1;
e->ipv4_type_dhcp = 0;
}
if (entries)
free(entries);
int hostmngr_get_neighbors_hostname(struct hostmngr_private *priv)
{
void *entries = NULL;
int num = 0;
int ret;
ret = read_dhcpv4_lease_table(&entries, &num);
if (!ret && num > 0) {
hostmngr_update_hostname_of_neighbors(priv, entries, num);
int hostmngr_register_notifier_file(struct hostmngr_private *priv, const char *filename)
{
#if 0 //TODO
int wd;
priv->fd_dhcp4 = inotify_init1(IN_NONBLOCK);
if (priv->fd_dhcp4 == -1) {
dbg("Error: %s\n", strerror(errno));
return -1;
}
wd = inotify_add_watch(fd, filename, IN_MODIFY);
if (wd == -1) {
close(priv->fd_dhcp4);
return -1;
}
//TODO; uloop_fd_add() etc.
#endif
return 0;
}
static void recv_arp_cb(struct uloop_fd *u, unsigned int events)
{
struct arp_sock *sk = container_of(u, struct arp_sock, uloop);
struct local_interface *iface = (struct local_interface *)sk->priv;
if (u->error) {
dbg("%s: - error -\n", __func__);
u->error = false;
/*
if (e->error_cb &&
getsockopt(u->fd, SOL_SOCKET, SO_ERROR, &ret, &ret_len) == 0) {
e->error_cb(e, ret);
}
*/
}
for (;;) {
char buf[512] = {0};
ssize_t len;
struct ethhdr *eh;
len = read(u->fd, buf, sizeof(buf));
if (len == -1) {
//dbg("%s: len = %d (%s)\n", __func__, len, strerror(errno));
return;
}
if (len <= sizeof(struct ethhdr)) {
return;
}
eh = (struct ethhdr *)buf;
if (ntohs(eh->h_proto) == ETH_P_ARP) {
struct hostmngr_private *priv = (struct hostmngr_private *)iface->priv;
err("ARP received from " MACFMT" on %s\n", MAC2STR(eh->h_source), iface->ifname);
if (!iface->is_brif || !iface->nomaster)
neigh_mark_reachable(&priv->neigh_q, eh->h_source, iface->ifname);
}
}
}
int hostmngr_register_arp_sock(const char *ifname, struct arp_sock *arpsk, void *priv)
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
{
struct sockaddr_ll sa;
int ifindex;
int flags;
int s;
if (!ifname || !arpsk)
return -1;
s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
if (s < 0)
return -1;
flags = fcntl(s, F_GETFL, 0);
if (flags != -1)
fcntl(s, F_SETFL, flags | O_NONBLOCK);
ifindex = if_nametoindex(ifname);
if (!ifindex) {
close(s);
return -1;
}
memset(&sa, 0, sizeof(struct sockaddr_ll));
sa.sll_family = AF_PACKET;
sa.sll_protocol = htons(ETH_P_ARP);
sa.sll_ifindex = ifindex;
if (bind(s, (struct sockaddr *)&sa, sizeof(struct sockaddr_ll)) < 0) {
err("Failed to bind arp sock\n");
close(s);
return -1;
}
arpsk->uloop.fd = s;
arpsk->uloop.cb = recv_arp_cb;
uloop_fd_add(&arpsk->uloop, ULOOP_READ);
arpsk->priv = priv;
dbg("%s: %s\n", __func__, ifname);
return 0;
}
void hostmngr_unregister_arp_sock(struct arp_sock *arpsk)
{
if (arpsk) {
uloop_fd_delete(&arpsk->uloop);
close(arpsk->uloop.fd);
arpsk->priv = NULL;
}
}
char *hostmngr_brport_to_ifname(struct hostmngr_private *priv, uint16_t brport)
{
struct local_interface *iface;
if (list_empty(&priv->iflist))
return NULL;
list_for_each_entry(iface, &priv->iflist, list) {
if (iface->is_brif && iface->brport == brport)
return iface->ifname;
}
return NULL;
}
struct local_interface *hostmngr_get_master_interface(struct hostmngr_private *priv,
int ifindex)
{
struct local_interface *iface;
if (list_empty(&priv->iflist))
return NULL;
list_for_each_entry(iface, &priv->iflist, list) {
if (iface->is_bridge && iface->ifindex == ifindex)
return iface;
}
return NULL;
}
struct local_interface *hostmngr_ifname_to_interface(struct hostmngr_private *priv,
const char *ifname)
{
struct local_interface *iface;
if (!ifname || !priv)
return NULL;
if (list_empty(&priv->iflist))
return NULL;
list_for_each_entry(iface, &priv->iflist, list) {
if (!strncmp(iface->ifname, ifname, 16))
return iface;
}
return NULL;
}
char *hostmngr_ifname_to_network(struct hostmngr_private *priv, const char *ifname)
{
struct local_interface *iface, *mif;
if (!ifname || !priv || list_empty(&priv->iflist))
return NULL;
list_for_each_entry(iface, &priv->iflist, list) {
if (strncmp(iface->ifname, ifname, 16))
continue;
if (iface->is_brif && iface->br_ifindex > 0) {
char master[16] = {0};
if_indextoname(iface->br_ifindex, master);
mif = hostmngr_ifname_to_interface(priv, master);
if (mif)
return mif->network;
}
return iface->network;
}
return NULL;
}
int hostmngr_handle_ethport_carrier_off(struct hostmngr_private *priv,
const char *ifname)
{
if (!priv || !ifname)
return -1;
neigh_set_unreachable(&priv->neigh_q, ifname);
return 0;
}
int hostmngr_handle_ethport_carrier_on(struct hostmngr_private *priv,
const char *ifname)
neigh_probe_unreachable(&priv->neigh_q, ifname);
return 0;
}
void heartbeat_timer_cb(atimer_t *t)
{
struct hostmngr_private *p = container_of(t, struct hostmngr_private, hbtimer);
UNUSED(p);
switch (signal_pending) {
case SIGUSR2:
signal_pending = 0;
err("%s", "Received SIGUSR2\n");
break;
default:
break;
}
timer_set(t, 1000);
}
static void hostmngr_periodic_refresh(atimer_t *t)
struct hostmngr_private *p = container_of(t, struct hostmngr_private, refreshtimer);
hostmngr_get_1905_aladdr(p);
hostmngr_get_1905_topology(p);
int hostmngr_init_private(struct hostmngr_private *priv, void *useropts)
#if 0
priv->use_ieee1905 = false;
priv->i1905 = lookup_object(priv->bus, IEEE1905_OBJECT);
if (priv->i1905 != OBJECT_INVALID) {
dbg("%s: Successfully got %s object (id = %u)\n",
__func__, IEEE1905_OBJECT, priv->i1905);
priv->i1905_topology = lookup_object(priv->bus, IEEE1905_TOPOLOGY_OBJECT);
if (priv->i1905_topology != OBJECT_INVALID) {
dbg("%s: Successfully got %s object (id = %u)\n",
__func__, IEEE1905_TOPOLOGY_OBJECT, priv->i1905_topology);
priv->use_ieee1905 = true;
}
} else {
dbg("%s: Failed to get %s object\n", __func__, IEEE1905_OBJECT);
}
#endif
INIT_LIST_HEAD(&priv->hostlist);
hostmngr_include_interface(priv, DEFAULT_LAN_IFNAME);
hostmngr_exclude_interfaces(priv, useropts);
hostmngr_init_interface_private(priv);
hostmngr_get_1905_aladdr(priv);
if (priv->cfg.history && priv->cfg.history_persist) {
neigh_history_load_from_json_file(priv, priv->cfg.history_file);
hosts_load_from_json_file(priv, priv->cfg.hosts_file);
}
hostmngr_get_interface_neighbors(priv);
hostmngr_get_neighbors_hostname(priv);
hostmngr_register_notifier_file(priv, "/var/dhcp.leases");
nfct_get_entries_nolo(priv);
return ret;
}
void hostmngr_exit_private(struct hostmngr_private *priv)
if (priv->cfg.history) {
neigh_history_store_to_json_file(priv, priv->cfg.history_file);
hosts_store_to_json_file(priv, priv->cfg.hosts_file);
}
neigh_flowtable_flush(priv);
neigh_iptable_flush(priv);
hostmngr_free_interfaces(priv);
neigh_history_free(&priv->neigh_q);
struct hostmngr_useropts *uopts = (struct hostmngr_useropts *)opts;
struct hostmngr_private *p;
set_sighandler(SIGUSR2, hostmngr_sighandler);
set_sighandler(SIGPIPE, SIG_IGN);
/* and SIGINT/SIGTERM handlers from uloop cancel uloop */
*priv = NULL;
p = calloc(1, sizeof(struct hostmngr_private));
if (!p)
return -1;
uloop_init();
p->bus = ubus_connect(uopts->ubus_sockpath);
if (!p->bus) {
err("Failed to connect to ubus\n");
goto out_err;
}
ubus_add_uloop(p->bus);
hostmngr_config_defaults(&p->cfg);
ret = hostmngr_reconfig(&p->cfg, uopts->confpath, uopts->conffile);
if (ret) {
err("Invalid config\n");
goto out_err;
}
ret = hostmngr_publish_object(p, uopts->objname);
if (ret)
goto out_err;
timer_init(&p->hbtimer, heartbeat_timer_cb);
timer_init(&p->refreshtimer, hostmngr_periodic_refresh);
timer_set(&p->refreshtimer, 2000);
*priv = p;
return 0;
out_err:
uloop_done();
free(p);
return -1;
}
struct hostmngr_private *priv = (struct hostmngr_private *)handle;
if (!priv)
return 0;
timer_del(&priv->hbtimer);
timer_del(&priv->refreshtimer);
hostmngr_unregister_local_events(priv);
hostmngr_unregister_nlevents(priv);
hostmngr_remove_object(priv);
hostmngr_exit_private(priv);
ubus_free(priv->bus);
uloop_done();
free(priv);
struct hostmngr_useropts *opts = (struct hostmngr_useropts *)useropts;
void *ctx;
int ret;
if (opts->daemonize)
do_daemonize(opts->pidfile);
start_logging(opts);
err("%s : Failed to init.\n", HOSTS_OBJECT);