 /*
  * netaid_monitor.c - Monitor-netlink
  * 
  * This file makes use of the following monitor-netlink example:
  * 
  * https://github.com/lihongguang/monitor-netlink
  * 
  * All modifications to the original source file are:
  * Copyright (C) 2026 Aitor Cuadrado Zubizarreta <aitor@genuen.org>
  * 
  * simple-netaid is free software: you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the
  * Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
  * 
  * simple-netaid 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.
  * 
  * You should have received a copy of the GNU General Public License along
  * with this program.  If not, see <http://www.gnu.org/licenses/>.
  * 
  * See the COPYING file.
  */
  
/* gcc -D_GNU_SOURCE netaid_monitor.c -o netaid_monitor -lnetaid */
   
  
#include <stdio.h>
#include <stdlib.h> 
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <time.h>
#include <signal.h>
#include <memory.h>
#include <net/if.h>
#include <linux/if.h>
#ifndef IFF_LOWER_UP
#define IFF_LOWER_UP 0x10000
#endif
#include <asm/types.h>
#include <arpa/inet.h>
#include <sys/un.h>      /*  UNIX socket  */
#include <sys/types.h>  
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <netinet/in.h>
#include <assert.h>
#include <inttypes.h>
#include <sys/wait.h>
#include <spawn.h>
#include <sys/stat.h>

// ethtool
#include <linux/sockios.h>
#include <sys/ioctl.h>

#include "netlink_monitor.h"
#include "notify.h"
#include "util.h"
#include "def.h"
#include "policy.h"

#include <json.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/json_script.h>
#include <libubox/uloop.h>

static struct blob_buf bb_t;
static struct blob_buf ev_b;
static struct uloop_fd netlink_fd;
static struct uloop_timeout hotplug_deferred;
static char pending_hotplug_ifname[IF_NAMESIZE];
static char hotplug_user_disconnected_if[IF_NAMESIZE];
static char hotplug_connect_active_if[IF_NAMESIZE];
static bool hotplug_carrier_down_seen;

static volatile sig_atomic_t shall_connect = 1;
static char strtty[4] = { 0 };

/* Atomic flag to prevent reentries and manage clean shutdowns */
static volatile sig_atomic_t is_processing_hotplug = 0;

static void hotplug_deferred_cb(struct uloop_timeout *t);

/* Writer end of NETAID_EVENTS_PIPE (simple-netaid-gtk runs cat on the FIFO). */
static int events_log_fd = -1;

static void events_log_ensure_fifo(void)
{
	struct stat st;

	if (stat(NETAID_EVENTS_PIPE, &st) == 0) {
		if (!S_ISFIFO(st.st_mode))
			syslog(LOG_WARNING, "netaid: %s exists but is not a FIFO",
			       NETAID_EVENTS_PIPE);
		return;
	}

	if (mkfifo(NETAID_EVENTS_PIPE, 0644) != 0 && errno != EEXIST)
		syslog(LOG_WARNING, "netaid: cannot create %s: %s",
		       NETAID_EVENTS_PIPE, strerror(errno));
}

static void events_log_open(void)
{
	if (events_log_fd >= 0)
		return;

	events_log_ensure_fifo();
	events_log_fd = open(NETAID_EVENTS_PIPE, O_WRONLY | O_NONBLOCK);
}

static void events_log_close(void)
{
	if (events_log_fd >= 0) {
		close(events_log_fd);
		events_log_fd = -1;
	}
}

static void events_log_write(const char *time_str, const char *buffer)
{
	char line[4096];
	int n;
	ssize_t w;

	if (!time_str || !buffer)
		return;

	events_log_open();
	if (events_log_fd < 0)
		return;

	n = snprintf(line, sizeof(line), "[%s] %s\n", time_str, buffer);
	if (n <= 0)
		return;
	if (n >= (int)sizeof(line))
		n = (int)sizeof(line) - 1;

	w = write(events_log_fd, line, (size_t)n);
	if (w < 0 && (errno == EPIPE || errno == ENXIO))
		events_log_close();
}

static int nl_msg_append(char *dst, size_t dst_sz, const char *src)
{
    size_t off = strlen(dst);
    size_t len = strlen(src);

    if (off + len + 1 > dst_sz)
        return -1;

    memcpy(dst + off, src, len + 1);
    return 0;
}

void netlink_hotplug_user_disconnect(const char *ifname)
{
    if (!ifname || !ifname[0])
        return;

    strncpy(hotplug_user_disconnected_if, ifname,
            sizeof(hotplug_user_disconnected_if) - 1);
    hotplug_user_disconnected_if[sizeof(hotplug_user_disconnected_if) - 1] = '\0';
    hotplug_carrier_down_seen = false;
    uloop_timeout_cancel(&hotplug_deferred);
    pending_hotplug_ifname[0] = '\0';
}

void netlink_hotplug_user_connect_success(const char *ifname)
{
    if (!ifname || !ifname[0])
        return;

    if (hotplug_user_disconnected_if[0] &&
        strcmp(hotplug_user_disconnected_if, ifname) == 0) {
        hotplug_user_disconnected_if[0] = '\0';
        hotplug_carrier_down_seen = false;
    }
}

void netlink_hotplug_carrier_down(const char *ifname)
{
    if (!ifname || !ifname[0])
        return;

    hotplug_carrier_down_seen = true;
}

void netlink_hotplug_connect_begin(const char *ifname)
{
    if (!ifname || !ifname[0])
        return;

    strncpy(hotplug_connect_active_if, ifname,
            sizeof(hotplug_connect_active_if) - 1);
    hotplug_connect_active_if[sizeof(hotplug_connect_active_if) - 1] = '\0';
    uloop_timeout_cancel(&hotplug_deferred);
    pending_hotplug_ifname[0] = '\0';
}

void netlink_hotplug_connect_end(const char *ifname)
{
    if (!ifname || !ifname[0])
        return;

    if (hotplug_connect_active_if[0] &&
        strcmp(hotplug_connect_active_if, ifname) == 0)
        hotplug_connect_active_if[0] = '\0';
}

void netlink_hotplug_processing_end(void)
{
    is_processing_hotplug = 0;
}

static bool hotplug_should_suppress(const char *ifname)
{
    if (!ifname || !ifname[0])
        return false;

    if (hotplug_user_disconnected_if[0] &&
        strcmp(hotplug_user_disconnected_if, ifname) == 0) {
        /* Cable still plugged after GTK ifdown: ignore spurious link-up. */
        if (!hotplug_carrier_down_seen)
            return true;
    }

    if (hotplug_connect_active_if[0] &&
        strcmp(hotplug_connect_active_if, ifname) == 0)
        return true;

    return false;
}

static void schedule_hotplug(const char *ifname)
{
    char log_buf[128];

    if (!ifname || !ifname[0])
        return;

    if (hotplug_should_suppress(ifname)) {
        if (hotplug_user_disconnected_if[0] &&
            strcmp(hotplug_user_disconnected_if, ifname) == 0 &&
            !hotplug_carrier_down_seen) {
            snprintf(log_buf, sizeof(log_buf),
                     "Hotplug ignored for %s (disconnect via GTK; unplug cable to reconnect)",
                     ifname);
        } else {
            snprintf(log_buf, sizeof(log_buf),
                     "Hotplug ignored for %s (connect already in progress)", ifname);
        }
        log_netlink_event(log_buf);
        return;
    }

    if (hotplug_user_disconnected_if[0] &&
        strcmp(hotplug_user_disconnected_if, ifname) == 0 &&
        hotplug_carrier_down_seen) {
        hotplug_user_disconnected_if[0] = '\0';
        hotplug_carrier_down_seen = false;
    }

    uloop_timeout_cancel(&hotplug_deferred);
    pending_hotplug_ifname[0] = '\0';
    wired_connection_hotplug_trigger(ifname);
}

static void hotplug_deferred_cb(struct uloop_timeout *t)
{
    (void)t;
    /* Legacy: no longer used; kept for ABI stability within this file. */
}

static bool link_is_physically_plugged(struct ifinfomsg *ifi)
{
    bool link_state = (ifi->ifi_flags & IFF_RUNNING) ? true : false;

    return link_state && (ifi->ifi_flags & IFF_LOWER_UP);
}
 
typedef enum { IFSTATUS_UP, IFSTATUS_DOWN, IFSTATUS_ERR } interface_status_t;

#ifndef IFF_LOWER_UP
/* The driver indicates that the Layer 1 (physical layer) is up, 
 * meaning the network link is physically connected and active */
#define IFF_LOWER_UP 0x10000
#endif

#define MAX_SIZE 1024
#define MAXBUF      (BUFSIZ * 2)

/* Message structure. */
struct message {
    int key;
    const char *str;
};

static const struct message nlmsg_str[] = {
   {RTM_NEWROUTE, "RTM_NEWROUTE"},
   {RTM_DELROUTE, "RTM_DELROUTE"},
   {RTM_GETROUTE, "RTM_GETROUTE"},
   {RTM_NEWLINK,  "RTM_NEWLINK"},
   {RTM_DELLINK,  "RTM_DELLINK"},
   {RTM_GETLINK,  "RTM_GETLINK"},
   {RTM_NEWADDR,  "RTM_NEWADDR"},
   {RTM_DELADDR,  "RTM_DELADDR"},
   {RTM_GETADDR,  "RTM_GETADDR"},
   {0,            NULL}
};

static const struct message rtable_str[] = {
   {RT_TABLE_UNSPEC,   "unspecified"},
   {RT_TABLE_COMPAT,   "compat"},
   {RT_TABLE_DEFAULT,  "default"},
   {RT_TABLE_MAIN,     "main"},
   {RT_TABLE_LOCAL,    "local"},
   {RT_TABLE_MAX,      "all"},
   {0,                 NULL}
};

static const struct message rtproto_str[] = {
   {RTPROT_REDIRECT, "redirect"},
   {RTPROT_KERNEL,   "kernel"},
   {RTPROT_BOOT,     "boot"},
   {RTPROT_STATIC,   "static"},
   {RTPROT_GATED,    "GateD"},
   {RTPROT_RA,       "router advertisement"},
   {RTPROT_MRT,      "MRT"},
   {RTPROT_ZEBRA,    "Zebra"},
#ifdef RTPROT_BIRD
   {RTPROT_BIRD,     "BIRD"},
#endif /* RTPROT_BIRD */
   {RTPROT_RIP,      "rip"},
   //{RTPROT_RIPNG,    "ripng"},
   {RTPROT_OSPF,     "ospf"},
   //{RTPROT_OSPF6,    "ospfv3"},
   {RTPROT_BGP,      "bgp4/4+"},
   {0,               NULL}
};

static const struct message rtype_str[] = {
   {RTN_UNSPEC,       "unspecified"},
   {RTN_UNICAST,      "unicast"},
   {RTN_LOCAL,        "host"},
   {RTN_BROADCAST,    "broadcast"},
   {RTN_ANYCAST,      "anycast"},
   {RTN_MULTICAST,    "multicast"},
   {RTN_BLACKHOLE,    "blackhole"},
   {RTN_UNREACHABLE,  "unreachable"},
   {RTN_PROHIBIT,     "prohibit"},
   {RTN_THROW,        "throw"},
   {RTN_NAT,          "nat"},
   {RTN_XRESOLVE,     "xresolve"},
   {0,                NULL}
};

static const struct message rscope_str[] = {
   {RT_SCOPE_UNIVERSE,    "global"},
   {RT_SCOPE_SITE,        "AS local"},
   {RT_SCOPE_LINK,        "link local"},
   {RT_SCOPE_HOST,        "host local"},
   {RT_SCOPE_NOWHERE,     "nowhere"},
   {0,                    NULL}
};

static int is_wired_or_loopback_interface(const char *ifname)
{
    char path[256];
    snprintf(path, sizeof(path), "/sys/class/net/%s/wireless", ifname);
    return access(path, F_OK) != 0;
}

void wired_connection_hotplug_trigger(const char *ifname)
{                   
    char log_buf[256];

    if (is_processing_hotplug) {
        snprintf(log_buf, sizeof(log_buf),
                 "Hotplug ignored for %s (previous attempt still running)", ifname);
        log_netlink_event(log_buf);
        syslog(LOG_NOTICE, "%s", log_buf);
        return;
    }
    is_processing_hotplug = 1;

    snprintf(log_buf, sizeof(log_buf),
             "Starting automatic configuration for %s...", ifname);
    log_netlink_event(log_buf);
    syslog(LOG_INFO, "%s", log_buf);

    if (wired_connection_hotplug_start(ifname) != 0) {
        snprintf(log_buf, sizeof(log_buf), "Configuration error for %s", ifname);
        log_netlink_event(log_buf);
        is_processing_hotplug = 0;
    }
}

/* Socket interface to kernel */
struct nlsock {
    int sock;
    int seq;
    struct sockaddr_nl snl;
    const char *name;
} netlink = { -1, 0, {0}, "netlink-listen"}; /* kernel messages */

void netlink_event_cb(struct uloop_fd *u, unsigned int events)
{
    (void)events;

    if (u->fd <= 0)
        return;

    while (netlink_parse_info(netlink_link_change, &netlink) == 0)
        ;
}

#include "prefix.h"

static inline char *if_index2name(int index, char *name)
{
    char *if_name = if_indextoname(index, name);
    if (!if_name)
        return "";
    return if_name;
}

static inline unsigned int if_name2index(char *name)
{
    return if_nametoindex(name);
}

/* Message lookup function. */
static const char *lookup(const struct message *mes, int key)
{
    const struct message *pnt;

    for (pnt = mes; pnt->str; pnt++)
        if (pnt->key == key)
            return pnt->str;

    return "unknown";
}

/* Wrapper around strerror to handle case where it returns NULL. */
const char *safe_strerror(int errnum)
{
    const char *s = strerror(errnum);
    return (s != NULL) ? s : "Unknown error";
}

/* Make socket for Linux netlink interface. */
static int netlink_socket (struct nlsock *nl, unsigned long groups)
{
    int ret =0;
    struct sockaddr_nl snl;
    int sock;
    int namelen;

    sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (sock < 0) {
        fprintf(stderr, "Can't open %s socket: %s\n", nl->name, safe_strerror(errno));
        return -1;
    }

    memset (&snl, 0, sizeof(snl));
    snl.nl_family = AF_NETLINK;
    snl.nl_groups = groups;

    /* Bind the socket to the netlink structure for anything. */
    ret = bind(sock, (struct sockaddr *)&snl, sizeof(snl));
    if (ret < 0) {
        fprintf(stderr, "Can't bind %s socket to group 0x%x: %s\n",
                    nl->name, snl.nl_groups, safe_strerror(errno));
        close(sock);
        return -1;
    }

    /* multiple netlink sockets will have different nl_pid */
    namelen = sizeof(snl);
    ret = getsockname(sock, (struct sockaddr *)&snl, (socklen_t *)&namelen);
    if (ret < 0 || namelen != sizeof(snl)) {
        fprintf(stderr, "Can't get %s socket name: %s\n", nl->name, safe_strerror(errno));
        close(sock);
        return -1;
    }

    nl->snl = snl;
    nl->sock = sock;

    if (fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK) < 0)
        log_warn("Cannot set netlink socket non-blocking: %m");

    return ret;
}

#ifndef HAVE_NETLINK
#define HAVE_NETLINK
/* Receive buffer size for netlink socket */
u_int32_t nl_rcvbufsize = 4194304;
#endif /* HAVE_NETLINK */

#ifndef SO_RCVBUFFORCE
#define SO_RCVBUFFORCE  (33)
#endif

static int netlink_recvbuf(struct nlsock *nl, uint32_t bufsize)
{
    u_int32_t oldsize;
    u_int32_t actual;
    socklen_t newlen = sizeof(actual);
    socklen_t oldlen = sizeof(oldsize);
    int rc __attribute__((unused)) = 0;
    char nl_tmp[MAX_SIZE] = {0};

    rc = getsockopt(nl->sock, SOL_SOCKET, SO_RCVBUF, &oldsize, &oldlen);
    if (rc < 0) {
        fprintf(stderr,
                "Can't get %s receive buffer size: %s\n", nl->name, safe_strerror(errno));
        return -1;
    }

    /* Try force option (linux >= 2.6.14) and fall back to normal set */
    rc = setsockopt(nl->sock, SOL_SOCKET, SO_RCVBUFFORCE, &bufsize, sizeof(bufsize));
    if (rc < 0) {
        rc = setsockopt(nl->sock, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize));
        if (rc < 0) {
            fprintf(stderr, "Can't set %s receive buffer size: %s\n", 
                    nl->name, safe_strerror(errno));
            return -1;
        }
    }

    rc = getsockopt(nl->sock, SOL_SOCKET, SO_RCVBUF, &actual, &newlen);
    if (rc < 0) {
        fprintf(stderr,
                "Can't get %s receive buffer size: %s\n", nl->name, safe_strerror(errno));
        return -1;
    }   
   
    rc = snprintf(nl_tmp, sizeof(nl_tmp),
            "Setting netlink socket receive buffer size: %u -> %u (actual %u)",
            oldsize, bufsize, actual);
    if (rc > 0 && (size_t)rc < sizeof(nl_tmp))
        log_netlink_event(nl_tmp);
  
    return 0;
}

int kernel_init(void)
{
    unsigned long groups;

    groups = RTMGRP_LINK | RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_IFADDR |
                RTMGRP_IPV6_ROUTE | RTMGRP_IPV6_IFADDR;

    memset(&hotplug_deferred, 0, sizeof(hotplug_deferred));
    pending_hotplug_ifname[0] = '\0';
    hotplug_user_disconnected_if[0] = '\0';
    hotplug_connect_active_if[0] = '\0';
    hotplug_carrier_down_seen = false;

    if (netlink_socket(&netlink, groups) < 0)
        return -1;

    if (netlink.sock <= 0)
        return -1;

    netlink_fd.fd = netlink.sock;
    netlink_fd.cb = netlink_event_cb;
    uloop_fd_add(&netlink_fd, ULOOP_READ);

    netlink_recvbuf(&netlink, nl_rcvbufsize);

    events_log_ensure_fifo();

    syslog(LOG_INFO, "Netlink monitor initialized and added to uloop.");
    return 0;
}

static int is_valid_kernel_table(u_int32_t table_id)
{
    if ((table_id == RT_TABLE_MAIN) || 
        (table_id == RT_TABLE_LOCAL) ||
        (table_id == RT_TABLE_COMPAT) ||
        (table_id > RT_TABLE_UNSPEC))
            return 1;
    else
        return 0;
}

/* Utility function for parse rtattr. */
static void netlink_parse_rtattr (struct rtattr **tb, int max,
        struct rtattr *rta, int len)
{
    while (RTA_OK (rta, len)) {
        if (rta->rta_type <= max)
            tb[rta->rta_type] = rta;
        rta = RTA_NEXT (rta, len);
    }
}

/* Routing information change from the kernel. */
static int netlink_route_change(struct sockaddr_nl *snl, struct nlmsghdr *h)
{
    int len;
    struct rtmsg *rtm;
    struct rtattr *tb[RTA_MAX + 1];
    char anyaddr[16] = {0};
    //char straddr[INET6_ADDRSTRLEN];
    char if_name[IFNAMSIZ];
    int rc __attribute__((unused)) = 0;
    char nl_msg[MAX_SIZE] = {0};
    char nl_tmp[MAX_SIZE];

    int index;
    int table;
    int metric __attribute((unused));

    void *dest;
    void *gate;
    void *src __attribute((unused));

    rtm = NLMSG_DATA(h);

    if (h->nlmsg_type != RTM_NEWROUTE && h->nlmsg_type != RTM_DELROUTE) {
        /* If this is not route add/delete message print warning. */
        memset(&nl_tmp, 0, MAX_SIZE);
        rc = snprintf(nl_tmp, sizeof(nl_tmp), "Kernel message: %d", h->nlmsg_type);
        log_netlink_event(nl_tmp);
        return 0;
    }

    if (rtm->rtm_flags & RTM_F_CLONED) {
        log_netlink_event("This route is cloned from another route!");
        return 0;
    }
 
    rc = snprintf(nl_msg, sizeof(nl_msg), 
          "nlmsg: %s, family: %s, rtable: %d-%s, rtype: %d-%s, rtproto: %d-%s",
          lookup(nlmsg_str, h->nlmsg_type),
          rtm->rtm_family == AF_INET ? "ipv4" : "ipv6",
          rtm->rtm_table, lookup(rtable_str, rtm->rtm_table),
          rtm->rtm_type, lookup(rtype_str, rtm->rtm_type),
          rtm->rtm_protocol, lookup(rtproto_str, rtm->rtm_protocol));

    table = rtm->rtm_table;
    if (!is_valid_kernel_table(table)) {
        memset(&nl_tmp, 0, MAX_SIZE);
        rc = snprintf(nl_tmp, sizeof(nl_tmp), "invalid kernel table: %d", table);
        log_netlink_event(nl_tmp);
        return 0;
    }

    len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg));
    if (len < 0) {
        log_netlink_event("netlink msg length error!");
        return -1;
    }

    memset(tb, 0, sizeof tb);
    netlink_parse_rtattr(tb, RTA_MAX, RTM_RTA(rtm), len);

    if (rtm->rtm_src_len != 0) {
        log_netlink_event("netlink_route_change(): no src len");
        return 0;
    }

    index = 0;
    metric = 0;
    dest = NULL;
    gate = NULL;
    src = NULL;

    if (tb[RTA_OIF])
        index = *(int *)RTA_DATA(tb[RTA_OIF]);

    if (tb[RTA_DST])
        dest = RTA_DATA(tb[RTA_DST]);
    else
        dest = anyaddr;

    if (tb[RTA_GATEWAY])
        gate = RTA_DATA(tb[RTA_GATEWAY]);

    if (tb[RTA_PREFSRC])
        src = RTA_DATA(tb[RTA_PREFSRC]);

    if (h->nlmsg_type == RTM_NEWROUTE && tb[RTA_PRIORITY])
        metric = *(int *)RTA_DATA(tb[RTA_PRIORITY]);

    if (rtm->rtm_family == AF_INET) {
        struct prefix_ipv4 p;
        p.family = AF_INET;
        memcpy (&p.prefix, dest, 4);
        p.prefixlen = rtm->rtm_dst_len;

        //inet_ntop(p->family, &p->u.prefix, straddr, INET6_ADDRSTRLEN);

        memset(&nl_tmp, 0, MAX_SIZE);
        if (h->nlmsg_type == RTM_NEWROUTE) {
            rc = snprintf(nl_tmp, sizeof(nl_tmp), "\tadd route %s/%d", 
                        inet_ntoa(p.prefix), p.prefixlen);
            nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
        } else {
            rc = snprintf(nl_tmp, sizeof(nl_tmp), "\tdel route %s/%d",
                        inet_ntoa(p.prefix), p.prefixlen);
            nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
        }
     
        memset(&nl_tmp, 0, MAX_SIZE);
        if (!tb[RTA_MULTIPATH]) {
            if (gate) {
                if (index) {
                    rc = snprintf(nl_tmp, sizeof(nl_tmp),
                                " nexthop via %s dev %s",
                                inet_ntoa(*(struct in_addr *)gate),
                                if_index2name(index, if_name));
                    nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
                } else {
                    rc = snprintf(nl_tmp, sizeof(nl_tmp), " nexthop via %s",
                                inet_ntoa(*(struct in_addr *)gate));
                    nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
                }
            } else {
                if (index) {
                    rc = snprintf(nl_tmp, sizeof(nl_tmp), " dev %s",
                                if_index2name(index, if_name));
                    nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
                }
            }
        } else {
            /* This is a multipath route */
            struct rtnexthop *rtnh = (struct rtnexthop *)RTA_DATA(tb[RTA_MULTIPATH]);

            len = RTA_PAYLOAD (tb[RTA_MULTIPATH]);
            for (;;) {
                if (len < (int)sizeof(*rtnh) || rtnh->rtnh_len > len)
                    break;

                index = rtnh->rtnh_ifindex;
                gate = 0;
                if (rtnh->rtnh_len > sizeof (*rtnh)) {
                    memset (tb, 0, sizeof(tb));
                    netlink_parse_rtattr (tb, RTA_MAX, RTNH_DATA(rtnh),
                                        rtnh->rtnh_len - sizeof(*rtnh));
                    if (tb[RTA_GATEWAY])
                        gate = RTA_DATA(tb[RTA_GATEWAY]);
                }

                if (gate) {
                    if (index) {
                        rc = snprintf(nl_tmp, sizeof(nl_tmp),
                                    " nexthop via %s dev %s",
                                    inet_ntoa(*(struct in_addr *)gate),
                                    if_index2name(index, if_name));
                        nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
                    } else {
                        rc = snprintf(nl_tmp, sizeof(nl_tmp), " nexthop via %s",
                                inet_ntoa(*(struct in_addr *)gate));
                        nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
                    }
                } else {
                    if (index) {
                        rc = snprintf(nl_tmp, sizeof(nl_tmp), " dev %s",
                                    if_index2name(index, if_name));
                        nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
                    }
                }
                len -= NLMSG_ALIGN(rtnh->rtnh_len);
                rtnh = RTNH_NEXT(rtnh);
            }
        }
    }

#ifdef HAVE_IPV6
    if (rtm->rtm_family == AF_INET6) {
        struct prefix_ipv6 p;
        char buf[BUFSIZ];

        p.family = AF_INET6;
        memcpy (&p.prefix, dest, 16);
        p.prefixlen = rtm->rtm_dst_len;
     
        memset(&nl_tmp, 0, MAX_SIZE);
        if (h->nlmsg_type == RTM_NEWROUTE) {
            rc = snprintf(nl_tmp, sizeof(nl_tmp), "\tadd route %s/%d",
                        inet_ntop(AF_INET6, &p.prefix, buf, BUFSIZ), p.prefixlen);
            nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
        } else {
            rc = snprintf(nl_tmp, sizeof(nl_tmp), "\tdel route %s/%d",
                        inet_ntop(AF_INET6, &p.prefix, buf, BUFSIZ), p.prefixlen);
            nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
        }

        // FIXME: add multipath process.
        if (h->nlmsg_type == RTM_NEWROUTE) {
            memset(&nl_tmp, 0, MAX_SIZE);
            if (gate) {
                if (index) {
                    rc = snprintf(nl_tmp, sizeof(nl_tmp), " nexthop via %s dev %s",
                                inet_ntop(AF_INET6, gate, buf, INET6_ADDRSTRLEN),
                                if_index2name(index, if_name));
                    nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
                } else {
                    rc = snprintf(nl_tmp, sizeof(nl_tmp), " nexthop via %s",
                                inet_ntop(AF_INET6, gate, buf, INET6_ADDRSTRLEN));
                    nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
                }
            } else {
                rc = snprintf(nl_tmp, sizeof(nl_tmp), " dev %s",
                            if_index2name(index, if_name));
                nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
            }
        } else {
            if (gate) {
                memset(&nl_tmp, 0, MAX_SIZE);
                rc = snprintf(nl_tmp, sizeof(nl_tmp), " nexthop via %s",
                            inet_ntop(AF_INET6, gate, buf, INET6_ADDRSTRLEN));
                nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
            }
            if (index) {
                memset(&nl_tmp, 0, MAX_SIZE);
                rc = snprintf(nl_tmp, sizeof(nl_tmp), " dev %s", if_index2name(index, if_name));
                nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
            }
            if (gate || index)
                strcat(nl_msg, "");
        }
    }
#endif /* HAVE_IPV6 */

    if(*nl_msg != 0) log_netlink_event(nl_msg);

    return 0;
}

/* Utility function to parse hardware link-layer address */
static void netlink_interface_get_hw_addr(struct rtattr **tb,
        u_char hw_addr[], int *hw_addr_len)
{
    int i;
    int rc __attribute__((unused)) = 0;
    char nl_msg[MAX_SIZE] = {0};

    if (tb[IFLA_ADDRESS]) {
        int __hw_addr_len;

        __hw_addr_len = RTA_PAYLOAD(tb[IFLA_ADDRESS]);

        if (__hw_addr_len > IF_HWADDR_MAX) {
            rc = snprintf(nl_msg, sizeof(nl_msg),
                        "Hardware address is too large: %d", __hw_addr_len);
            log_netlink_event(nl_msg);
        } else {
            *hw_addr_len = __hw_addr_len;
            memcpy(hw_addr, RTA_DATA(tb[IFLA_ADDRESS]), __hw_addr_len);

            for (i = 0; i < __hw_addr_len; i++)
                if (hw_addr[i] != 0)
                    break;

            if (i == __hw_addr_len)
                *hw_addr_len = 0;
            else
                *hw_addr_len = __hw_addr_len;
        }
    }
}

int netlink_interface_filter(struct sockaddr_nl *snl, struct nlmsghdr *h, struct rtattr **tb)
{    
    unsigned int index;
    char nl_msg[MAX_SIZE] = {0};
    char nl_tmp[MAX_SIZE] = {0};
    struct ifinfomsg *ifi = NLMSG_DATA(h);
    char *name;

#ifdef IFLA_WIRELESS
    /* check for wireless messages to ignore */
    if ((tb[IFLA_WIRELESS] != NULL) && (ifi->ifi_change == 0)) {
        memset(&nl_tmp, 0, MAX_SIZE);
        snprintf(nl_tmp, sizeof(nl_tmp), "%s: ignoring IFLA_WIRELESS message", __func__);
        //log_netlink_event(nl_tmp);
        return 0;
    }
#endif /* IFLA_WIRELESS */

    if (tb[IFLA_IFNAME] == NULL)
        return -1;
    
    name = (char *)RTA_DATA(tb[IFLA_IFNAME]);

    //if_index2name(ifi->ifi_index, ifname);

    /* Add interface. */
    if (h->nlmsg_type == RTM_NEWLINK) {
        u_char hw_addr[IF_HWADDR_MAX];
        int hw_addr_len = 0;

        index = if_name2index(name);
        if (0 == index) {
            snprintf(nl_msg, sizeof(nl_msg),
                     "add link dev %s index %d", name, ifi->ifi_index);
        } else {
            snprintf(nl_msg, sizeof(nl_msg),
                     "update link dev %s index %d", name, ifi->ifi_index);
        }

        if (is_wired_or_loopback_interface(name) && strcmp(name, "lo")) {
            if (ifi->ifi_flags & IFF_UP) {
                if (link_is_physically_plugged(ifi)) {
                    snprintf(nl_tmp, sizeof(nl_tmp),
                            "Physical connection detected from %s", name);
                    nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
                    shall_connect = 1;
                    schedule_hotplug(name);
                } else {
                    snprintf(nl_tmp, sizeof(nl_tmp),
                            "Cable disconnected from %s", name);
                    nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
                    shall_connect = 0;
                    netlink_hotplug_carrier_down(name);
                    wired_disconnect(name);
                }
            } else {
                shall_connect = 0;
                if (!link_is_physically_plugged(ifi))
                    netlink_hotplug_carrier_down(name);
            }
        }

        netlink_interface_get_hw_addr(tb, hw_addr, &hw_addr_len);

        int i;
        for (i = 0; i < hw_addr_len; i++) {
            memset(&nl_tmp, 0, MAX_SIZE);
            snprintf(nl_tmp, sizeof(nl_tmp), "%s%02x", i == 0 ? "" : ":", hw_addr[i]);
            nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
        }
        memset(&nl_tmp, 0, MAX_SIZE);
        if (tb[IFLA_MTU]) {
            snprintf(nl_tmp, sizeof(nl_tmp), " mtu %d flags %d",
                    *(int *)RTA_DATA(tb[IFLA_MTU]), ifi->ifi_flags & 0x0000fffff);
        } else {
            snprintf(nl_tmp, sizeof(nl_tmp), " flags %d",
                    ifi->ifi_flags & 0x0000fffff);
        }
        nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);

    } else {
        /* RTM_DELLINK. */
        index = if_name2index(name);
        memset(&nl_tmp, 0, MAX_SIZE);
        if (0 == index)
            snprintf(nl_tmp, sizeof(nl_tmp),
                     "interface %s is deleted but can't find", name);
        else
            snprintf(nl_tmp, sizeof(nl_tmp),
                     "delete link dev %s index %d", name, ifi->ifi_index);
        nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
    }
   
    if (*nl_msg != 0)
        log_netlink_event(nl_msg);

    return 0;
}

int netlink_link_change(struct sockaddr_nl *snl, struct nlmsghdr *h)
{
    struct ifinfomsg *ifi = NLMSG_DATA(h);
    struct rtattr *tb[IFLA_MAX + 1];
    
    int len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifinfomsg));
    if (len < 0)
        return -1;

    if (h->nlmsg_type != RTM_NEWLINK && h->nlmsg_type != RTM_DELLINK)
        return 0;

    memset(tb, 0, sizeof(tb));
    netlink_parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);

    return netlink_interface_filter(snl, h, tb); 
}

/* Lookup interface IPv4/IPv6 address. */
static int netlink_interface_addr(struct sockaddr_nl *snl, struct nlmsghdr *h)
{
    int len;
    struct ifaddrmsg *ifa;
    struct rtattr *tb[IFA_MAX + 1];
    char name[IFNAMSIZ];
    char *if_name;
    char buf[BUFSIZ];
    int rc __attribute__((unused)) = 0;
    char nl_msg[MAX_SIZE] = {0};
    char nl_tmp[MAX_SIZE];

    ifa = NLMSG_DATA (h);

    if (ifa->ifa_family != AF_INET
#ifdef HAVE_IPV6
    && ifa->ifa_family != AF_INET6
#endif /* HAVE_IPV6 */
    )
    return 0;

    if (h->nlmsg_type != RTM_NEWADDR && h->nlmsg_type != RTM_DELADDR)
        return 0;

    len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifaddrmsg));
    if (len < 0)
        return -1;

    memset (tb, 0, sizeof tb);
    netlink_parse_rtattr (tb, IFA_MAX, IFA_RTA(ifa), len);

    if_name = if_index2name(ifa->ifa_index, name);
    if (!if_name || if_name[0] == '\0') {
        fprintf(stderr,
                "netlink_interface_addr can't find interface by index %d\n", ifa->ifa_index);
        return -1;
    }

    rc = snprintf(nl_msg, sizeof(nl_tmp), "nlmsg: %s, family: %s, %s on dev %s",
                lookup(nlmsg_str, h->nlmsg_type), ifa->ifa_family == AF_INET ? "ipv4" : "ipv6",
                (h->nlmsg_type == RTM_NEWADDR) ? "add addr" : "del addr", if_name);

    if (tb[IFA_LOCAL]) {
        memset(&nl_tmp, 0, MAX_SIZE);
        rc = snprintf(nl_tmp, sizeof(nl_tmp), "\t  IFA_LOCAL     %s/%d",
                    inet_ntop (ifa->ifa_family, RTA_DATA(tb[IFA_LOCAL]),
                    buf, BUFSIZ), ifa->ifa_prefixlen);
        nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
    }
    if (tb[IFA_ADDRESS]) {
        memset(&nl_tmp, 0, MAX_SIZE);
        rc = snprintf(nl_tmp, sizeof(nl_tmp), "\t  IFA_ADDRESS   %s/%d",
                    inet_ntop(ifa->ifa_family, RTA_DATA (tb[IFA_ADDRESS]),
                    buf, BUFSIZ), ifa->ifa_prefixlen);
        nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
    }
    if (tb[IFA_BROADCAST]) {
        memset(&nl_tmp, 0, MAX_SIZE);
        rc = snprintf(nl_tmp, sizeof(nl_tmp), "\t  IFA_BROADCAST %s/%d",
                    inet_ntop(ifa->ifa_family, RTA_DATA(tb[IFA_BROADCAST]),
                    buf, BUFSIZ), ifa->ifa_prefixlen);
        nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
    }
    if (tb[IFA_LABEL] && strcmp (if_name, RTA_DATA(tb[IFA_LABEL]))) {
        memset(&nl_tmp, 0, MAX_SIZE);
        rc = snprintf(nl_tmp, sizeof(nl_tmp), "\t  IFA_LABEL     %s",
                    (char *)RTA_DATA(tb[IFA_LABEL]));
        nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
    }

    if (tb[IFA_CACHEINFO]) {
        struct ifa_cacheinfo *ci = RTA_DATA(tb[IFA_CACHEINFO]);
        memset(&nl_tmp, 0, MAX_SIZE);
        rc = snprintf(nl_tmp, sizeof(nl_tmp), "\t  IFA_CACHEINFO pref %d, valid %d",
                    ci->ifa_prefered, ci->ifa_valid);
        nl_msg_append(nl_msg, sizeof(nl_msg), nl_tmp);
    }
   
    if (*nl_msg != 0)
        log_netlink_event(nl_msg);
 
    return 0;
}

static int netlink_information_fetch(struct sockaddr_nl *snl, struct nlmsghdr *h)
{
    /* Ignore messages that aren't from the kernel */
    if (snl->nl_pid != 0) {
        fprintf(stderr,
                "Ignoring message from pid %u that isn't from the kernel!\n", snl->nl_pid);
        return 0;
    }
 
    switch (h->nlmsg_type) {
    case RTM_NEWROUTE:
        return netlink_route_change(snl, h);
        break;
    case RTM_DELROUTE:
        return netlink_route_change(snl, h);
        break;
    case RTM_NEWLINK:
        return netlink_link_change(snl, h);
        break;
    case RTM_DELLINK:
        return netlink_link_change(snl, h);
        break;
    case RTM_NEWADDR:
        return netlink_interface_addr(snl, h);
        break;
    case RTM_DELADDR:
        return netlink_interface_addr(snl, h);
        break;  
    default:
        fprintf(stderr, "Unknown netlink nlmsg_type %d\n", h->nlmsg_type);
        break;
    }
   
    return 0;
}

#define NL_PKT_BUF_SIZE         8192

/* Hack for GNU libc version 2. */
#ifndef MSG_TRUNC
#define MSG_TRUNC      0x20
#endif /* MSG_TRUNC */

/* Receive message from netlink interface and pass those information
 * to the given function.
 */
int netlink_parse_info(int (*filter)(struct sockaddr_nl *, struct nlmsghdr *),
        struct nlsock *nl)
{   
    int status;
    int ret = 0;
    int error;
    int rc __attribute__((unused)) = 0;
    char nl_tmp[MAX_SIZE] = {0};
    char buf[NL_PKT_BUF_SIZE];

    struct iovec iov = {
        .iov_base = buf,
        .iov_len = sizeof(buf)
    };
    struct sockaddr_nl snl;
    struct msghdr msg = {
        .msg_name = (void *)&snl,
        .msg_namelen = sizeof(snl),
        .msg_iov = &iov,
        .msg_iovlen = 1
    };
    struct nlmsghdr *h;
    
    status = recvmsg(nl->sock, &msg, MSG_DONTWAIT);

    if (status < 0) {
        if (errno == EINTR)
            return 0;
        if (errno == EAGAIN || errno == EWOULDBLOCK)
            return -1;
        log_error("%s recvmsg error: %m", nl->name);
        return -1;
    }

    if (status == 0) {
        log_error("%s EOF", nl->name);
        return -1;
    }

    if (msg.msg_flags & MSG_TRUNC) {
        log_error("%s netlink message truncated, discarding", nl->name);
        return -1;
    }

    if (msg.msg_namelen != sizeof(snl)) {
        memset(&nl_tmp, 0, MAX_SIZE);
        rc = snprintf(nl_tmp, sizeof(nl_tmp),
                    "%s sender address length error: length %d",
                    nl->name, msg.msg_namelen);
        return -1;
    }

    for (h = (struct nlmsghdr *)buf; NLMSG_OK(h, (unsigned int)status); h = NLMSG_NEXT(h, status)) {
                    
        /* Finish of reading. */
        if (h->nlmsg_type == NLMSG_DONE)
            return ret;

        /* Error handling. */
        if (h->nlmsg_type == NLMSG_ERROR) {
            struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(h);
            int errnum = err->error;
            int msg_type = err->msg.nlmsg_type;

            /* If the error field is zero, then this is an ACK */
            if (err->error == 0) {
#if 0
                memset(&nl_tmp, 0, MAX_SIZE);
                rc = snprintf(nl_tmp, sizeof(nl_tmp),
                                "%s: %s ACK: type=%s(%u), seq=%u, pid=%u",
                                __FUNCTION__, nl->name,
                                lookup (nlmsg_str, err->msg.nlmsg_type),
                                err->msg.nlmsg_type,
                                err->msg.nlmsg_seq,
                                err->msg.nlmsg_pid);
                log_netlink_event(nl_tmp);
#endif
                /* return if not a multipart message, otherwise continue */
                if (!(h->nlmsg_flags & NLM_F_MULTI))
                    return 0;
                continue;
            }

            if (h->nlmsg_len < NLMSG_LENGTH (sizeof (struct nlmsgerr))) {
                fprintf(stderr, "%s error: message truncated\n", nl->name);
                return -1;
            }
            
            memset(&nl_tmp, 0, MAX_SIZE);
            rc = snprintf(nl_tmp, sizeof(nl_tmp),
                        "%s error: %s, type=%s(%u), seq=%u, pid=%u",
                        nl->name, safe_strerror(-errnum),
                        lookup (nlmsg_str, msg_type),
                        msg_type, err->msg.nlmsg_seq, err->msg.nlmsg_pid);
            log_netlink_event(nl_tmp);

            return -1;
        }
#if 0
        /* OK we got netlink message. */           
        memset(&nl_tmp, 0, MAX_SIZE);
        rc = snprintf(nl_tmp, sizeof(nl_tmp),
                    "netlink_parse_info: %s type %s(%u), seq=%u, pid=%u",
                    nl->name,
                    lookup (nlmsg_str, h->nlmsg_type), h->nlmsg_type,
                    h->nlmsg_seq, h->nlmsg_pid);
        log_netlink_event(nl_tmp);
#endif
        
        /* Only process messages coming from ID 0 (Kernel) */
        if (snl.nl_pid != 0)
            continue;
        
        error = (*filter)(&snl, h);
        if (error < 0) {           
            fprintf(stderr, "%s filter function error\n", nl->name);
            ret = error;
        }
    }
  
    /* After error care. */
    if (msg.msg_flags & MSG_TRUNC)          
        fprintf(stderr, "%s error: message truncated\n", nl->name);
        
    if (status)         
        fprintf(stderr, "%s error: data remnant size %d\n", nl->name, status);

    return ret;
}

void kernel_finish(void)
{
    events_log_close();

    uloop_timeout_cancel(&hotplug_deferred);

    if (is_processing_hotplug)
        syslog(LOG_NOTICE, "Waiting for the current network task to finish...");

    if (netlink.sock > 0) {
        uloop_fd_delete(&netlink_fd);
        close(netlink.sock);
        netlink.sock = -1;
        syslog(LOG_INFO, "Netlink monitor closed.");
    }
}

void log_netlink_event(char *buffer)
{
    char time_str[32];
    time_t now = time(NULL);
    struct tm tm_buf;

    if (!buffer || *buffer == '\0')
        return;

    localtime_r(&now, &tm_buf);
    strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", &tm_buf);

    syslog(LOG_INFO, "[%s] %s", time_str, buffer);
    events_log_write(time_str, buffer);

    if (ctx) {
        blob_buf_init(&bb_t, 0);
        blobmsg_add_string(&bb_t, "timestamp", time_str);
        blobmsg_add_string(&bb_t, "message", buffer);
        blobmsg_add_u32(&bb_t, "epoch", (uint32_t)now);

        ubus_send_event(ctx, "network.log", bb_t.head);
    }

    netaid_ui_notify_schedule();
}
