Skip to content
Snippets Groups Projects
hostmngr.c 14.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • Anjan Chanda's avatar
    Anjan Chanda committed
    /*
    
     * hostmngr.c - implementation file for network hosts management.
    
    Anjan Chanda's avatar
    Anjan Chanda committed
     *
     * 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 <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 <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 "wifi_api.h"
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    #include "util.h"
    
    #include "useropts.h"
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    #include "debug.h"
    #include "timer.h"
    #include "neigh.h"
    #include "topology.h"
    
    
    static int signal_pending;
    
    static void topologyd_sighandler(int sig)
    {
    	signal_pending = sig;
    }
    
    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;
    	struct rtnl_link *link;
    	int num_ipaddrs = 32;
    	struct nl_addr *a;
    	int ret;
    
    
    	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->exclude = 1;
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    
    	a = rtnl_link_get_addr(link);
    	if (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);
    	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;
    		}
    	}
    
    	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));
    		}
    	}
    
    
    	if (is_wifi_interface(iface->ifname)) {
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    		iface->mediatype = IF_MEDIA_WIFI;
    
    		if (is_ap_interface(iface->ifname))
    			iface->is_ap = true;
    	} else {
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    		iface->mediatype = IF_MEDIA_ETH;
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    
    	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 topology_enumerate_interfaces(struct topologyd_private *priv)
    {
    	return enum_interfaces(&priv->iflist, &priv->num_local_interfaces);
    }
    
    void topology_print_interfaces(struct topologyd_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\n",
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    			i++, e->ifname, MAC2STR(e->macaddr), e->ifindex,
    			e->ifflags, e->operstate, e->is_bridge);
    	}
    }
    
    
    #if 0
    int topology_enumerate_interfaces_in_zone(struct topologyd_private *priv,
    					  const char *zone, char *iflist,
    					  int *num)
    {
    
    }
    #endif
    
    
    int topology_include_interface(struct topologyd_private *priv, const char *ifname)
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    {
    	struct local_interface *e;
    
    
    	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;
    				}
    			}
    		}
    	}
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    
    	list_for_each_entry(e, &priv->iflist, list) {
    
    		if (!strncmp(e->ifname, ifname, strlen(e->ifname))) {
    			e->exclude = 0;
    			dbg("Include interface '%s'\n", e->ifname);
    			break;
    		}
    	}
    
    	return 0;
    }
    
    int topology_exclude_interfaces(struct topologyd_private *priv, void *useropts)
    {
    	struct topologyd_useropts *uopts = (struct topologyd_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;
    			}
    		}
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	}
    
    	return 0;
    }
    
    int topology_get_interface_neighbors(struct topologyd_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;
    
    		topologyd_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;
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	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;
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    		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,
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    			   macaddr, ipaddr, hostname, clid) == 5) {
    
    
    			ptr = realloc(hosts, (cnt + 1) * sizeof(struct dhcp_clients));
    			if (!ptr) {
    				if (hosts)
    					free(hosts);
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    				*num_clients = 0;
    				ret = -1;
    				goto out;
    			}
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    			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);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    out:
    	fclose(f);
    	return ret;
    }
    
    void topology_update_hostname_of_neighbors(struct topologyd_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;
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	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;
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    
    
    			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);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    			}
    		}
    	}
    }
    
    int topology_get_neigh_hostname(struct neigh_queue *q, uint8_t *macaddr)
    {
    	struct topologyd_private *priv = container_of(q, struct topologyd_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;
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	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)
    		return -1;
    
    
    	dbg("%s: num-dhcphosts = %d\n", __func__, num);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	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;
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    
    
    			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);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    			}
    			ret = 0;
    			break;
    		}
    	}
    
    	free(entries);
    	return ret;
    }
    
    int topology_get_neighbors_hostname(struct topologyd_private *priv)
    {
    	void *entries = NULL;
    	int num = 0;
    	int ret;
    
    	ret = read_dhcpv4_lease_table(&entries, &num);
    	if (!ret && num > 0) {
    		topology_update_hostname_of_neighbors(priv, entries, num);
    		free(entries);
    	}
    
    	return 0;
    }
    
    int topology_register_notifier_file(struct topologyd_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));
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    		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;
    }
    
    char *topology_brport_to_ifname(struct topologyd_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 *topologyd_ifname_to_interface(struct topologyd_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;
    }
    
    int topology_handle_ethport_carrier_off(struct topologyd_private *priv,
    					const char *ifname, int off)
    {
    	if (!priv || !ifname)
    		return -1;
    
    	neigh_set_unreachable(&priv->neigh_q, ifname, off);
    	return 0;
    }
    
    void heartbeat_timer_cb(atimer_t *t)
    {
    	struct topologyd_private *p = container_of(t, struct topologyd_private, hbtimer);
    
    	UNUSED(p);
    	switch (signal_pending) {
    	case SIGUSR2:
    		signal_pending = 0;
    		err("%s", "Received SIGUSR2\n");
    		break;
    	default:
    		break;
    	}
    
    	timer_set(t, 1000);
    }
    
    void topology_timer_cb(atimer_t *t)
    {
    	//struct topologyd_private *p = container_of(t, struct topologyd_private, topotimer);
    
    	timer_set(t, 60000);
    }
    
    static void topologyd_periodic_refresh(atimer_t *t)
    {
    	struct topologyd_private *p = container_of(t, struct topologyd_private, refreshtimer);
    
    	topologyd_get_1905_topology(p);
    	timer_set(t, 5000);
    }
    
    
    int topologyd_init_private(struct topologyd_private *priv, void *useropts)
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    {
    	int ret;
    
    #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",
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    			__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",
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    				__func__, IEEE1905_TOPOLOGY_OBJECT, priv->i1905_topology);
    			priv->use_ieee1905 = true;
    		}
    	} else {
    
    		dbg("%s: Failed to get %s object\n", __func__, IEEE1905_OBJECT);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	}
    #endif
    	INIT_LIST_HEAD(&priv->iflist);
    	ret = topology_enumerate_interfaces(priv);
    	if (!ret)
    		topology_print_interfaces(priv);
    
    
    	topology_include_interface(priv, DEFAULT_LAN_IFNAME);
    	topology_exclude_interfaces(priv, useropts);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    
    	neigh_queue_init(&priv->neigh_q);
    
    
    	if (priv->cfg.history)
    		neigh_history_load_from_json_file(priv, priv->cfg.history_file);
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	topology_get_interface_neighbors(priv);
    	topology_get_neighbors_hostname(priv);
    
    	topology_register_notifier_file(priv, "/var/dhcp.leases");
    
    	nfct_get_entries_nolo(priv);
    
    	return ret;
    }
    
    void topologyd_exit_private(struct topologyd_private *priv)
    {
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	neigh_queue_free(&priv->neigh_q);
    
    
    	if (priv->cfg.history)
    		neigh_history_store_to_json_file(priv, priv->cfg.history_file);
    
    	neigh_history_free(&priv->neigh_q);
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	return;
    }
    
    int topologyd_init(void **priv, void *opts)
    {
    	struct topologyd_useropts *uopts = (struct topologyd_useropts *)opts;
    	struct topologyd_private *p;
    	int ret;
    
    
    	set_sighandler(SIGUSR2, topologyd_sighandler);
    	set_sighandler(SIGPIPE, SIG_IGN);
    	/* and SIGINT/SIGTERM handlers from uloop cancel uloop */
    
    	*priv = NULL;
    	p = calloc(1, sizeof(struct topologyd_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);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	if (ret) {
    		err("Invalid config\n");
    		goto out_err;
    	}
    
    	hostmngr_dump_config(&p->cfg);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    
    
    	ret = topologyd_init_private(p, opts);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	if (ret)
    		goto out_err;
    
    	ret = topologyd_publish_object(p, uopts->objname);
    	if (ret)
    		goto out_err;
    
    	ret = topologyd_register_local_events(p);
    	if (ret)
    		goto out_err;
    
    	ret = topologyd_register_nlevents(p);
    	if (ret)
    		goto out_err;
    
    	timer_init(&p->hbtimer, heartbeat_timer_cb);
    	timer_init(&p->topotimer, topology_timer_cb);
    	timer_init(&p->refreshtimer, topologyd_periodic_refresh);
    
    	timer_set(&p->hbtimer, 1000);
    	timer_set(&p->topotimer, 1500);
    	timer_set(&p->refreshtimer, 1000);
    
    	*priv = p;
    	return 0;
    
    out_err:
    	uloop_done();
    	free(p);
    	return -1;
    }
    
    void topologyd_run(void *handle)
    {
    	UNUSED(handle);
    
    	uloop_run();
    }
    
    int topologyd_exit(void *handle)
    {
    	struct topologyd_private *priv = (struct topologyd_private *)handle;
    
    	if (!priv)
    		return 0;
    
    	timer_del(&priv->topotimer);
    	timer_del(&priv->hbtimer);
    	timer_del(&priv->refreshtimer);
    
    	topologyd_unregister_local_events(priv);
    	topologyd_unregister_nlevents(priv);
    
    	topologyd_remove_object(priv);
    	topologyd_exit_private(priv);
    	//topologyd_config_free(&priv->cfg);
    
    	ubus_free(priv->bus);
    	uloop_done();
    	free(priv);
    
    	dbg("topologyd_exit\n");
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	return 0;
    }
    
    
    int hostmngr_main(void *useropts)
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    {
    	struct topologyd_useropts *opts = (struct topologyd_useropts *)useropts;
    	void *ctx;
    	int ret;
    
    
    	if (opts->daemonize)
    		do_daemonize(opts->pidfile);
    
    	start_logging(opts);
    
    	ret = topologyd_init(&ctx, opts);
    	if (ret) {
    
    		err("%s : Failed to init.\n", HOSTS_OBJECT);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    		return -1;
    	}
    
    	topologyd_run(ctx);
    	topologyd_exit(ctx);
    
    	stop_logging();
    
    	return 0;
    }