text: /* MiniUPnP project
   * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
   *
   * Copyright (c) 2006, Thomas Bernard
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *     * Redistributions of source code must retain the above copyright
   *       notice, this list of conditions and the following disclaimer.
   *     * Redistributions in binary form must reproduce the above copyright
   *       notice, this list of conditions and the following disclaimer in the
   *       documentation and/or other materials provided with the distribution.
   *     * The name of the author may not be used to endorse or promote products
   *       derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   * POSSIBILITY OF SUCH DAMAGE.
   */
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
  #include <sys/ioctl.h>
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <net/if.h>
  #include <arpa/inet.h>
  #include <netinet/in.h>
  #include <netdb.h>
  #include <errno.h>
  #if defined(sun)
  #include <sys/sockio.h>
  #endif
  
  #include "config.h"
  #if HAVE_GETIFADDRS
  #include <ifaddrs.h>
  #ifdef __linux__
  #ifndef AF_LINK
  #define AF_LINK AF_INET
  #endif
  #else
  #include <net/if_dl.h>
  #endif
  #endif
  #ifdef HAVE_NETLINK
  #include <linux/rtnetlink.h>
  #include <linux/netlink.h>
  #endif
  #include "upnpglobalvars.h"
  #include "getifaddr.h"
  #include "minissdp.h"
  #include "log.h"
  
  static int
  getifaddr(const char *ifname, int notify)
  {
  #if HAVE_GETIFADDRS
  	struct ifaddrs *ifap, *p;
  	struct sockaddr_in *addr_in;
  
  	if (getifaddrs(&ifap) != 0)
  	{
  		DPRINTF(E_ERROR, L_GENERAL, "getifaddrs(): %s\n", strerror(errno));
  		return -1;
  	}
  
  	for (p = ifap; p != NULL; p = p->ifa_next)
  	{
  		if (!p->ifa_addr || p->ifa_addr->sa_family != AF_INET)
  			continue;
  		if (ifname && strcmp(p->ifa_name, ifname) != 0)
  			continue;
  		addr_in = (struct sockaddr_in *)p->ifa_addr;
  		if (!ifname && (p->ifa_flags & (IFF_LOOPBACK | IFF_SLAVE)))
  			continue;
  		memcpy(&lan_addr[n_lan_addr].addr, &addr_in->sin_addr, sizeof(lan_addr[n_lan_addr].addr));
  		if (!inet_ntop(AF_INET, &addr_in->sin_addr, lan_addr[n_lan_addr].str, sizeof(lan_addr[0].str)) )
  		{
  			DPRINTF(E_ERROR, L_GENERAL, "inet_ntop(): %s\n", strerror(errno));
  			continue;
  		}
  		addr_in = (struct sockaddr_in *)p->ifa_netmask;
  		memcpy(&lan_addr[n_lan_addr].mask, &addr_in->sin_addr, sizeof(lan_addr[n_lan_addr].mask));
  		lan_addr[n_lan_addr].snotify = OpenAndConfSSDPNotifySocket(lan_addr[n_lan_addr].addr.s_addr);
  		if (lan_addr[n_lan_addr].snotify >= 0)
  		{
  			if (notify)
  				SendSSDPNotifies(lan_addr[n_lan_addr].snotify, lan_addr[n_lan_addr].str,
  					runtime_vars.port, runtime_vars.notify_interval);
  			n_lan_addr++;
  		}
  		if (ifname || n_lan_addr >= MAX_LAN_ADDR)
  			break;
  	}
  	freeifaddrs(ifap);
  	if (ifname && !p)
  	{
  		DPRINTF(E_ERROR, L_GENERAL, "Network interface %s not found\n", ifname);
  		return -1;
  	}
  #else
  	int s = socket(PF_INET, SOCK_STREAM, 0);
  	struct sockaddr_in addr;
  	struct ifconf ifc;
  	struct ifreq *ifr;
  	char buf[8192];
  	int i, n;
  
  	memset(&ifc, '\0', sizeof(ifc));
  	ifc.ifc_buf = buf;
  	ifc.ifc_len = sizeof(buf);
  
  	if (ioctl(s, SIOCGIFCONF, &ifc) < 0)
  	{
  		DPRINTF(E_ERROR, L_GENERAL, "SIOCGIFCONF: %s\n", strerror(errno));
  		close(s);
  		return -1;
  	}
  
  	n = ifc.ifc_len / sizeof(struct ifreq);
  	for (i = 0; i < n; i++)
  	{
  		ifr = &ifc.ifc_req[i];
  		if (ifname && strcmp(ifr->ifr_name, ifname) != 0)
  			continue;
  		if (!ifname &&
  		    (ioctl(s, SIOCGIFFLAGS, ifr) < 0 || ifr->ifr_ifru.ifru_flags & IFF_LOOPBACK))
  			continue;
  		if (ioctl(s, SIOCGIFADDR, ifr) < 0)
  			continue;
  		memcpy(&addr, &(ifr->ifr_addr), sizeof(addr));
  		memcpy(&lan_addr[n_lan_addr].addr, &addr.sin_addr, sizeof(lan_addr[n_lan_addr].addr));
  		if (!inet_ntop(AF_INET, &addr.sin_addr, lan_addr[n_lan_addr].str, sizeof(lan_addr[0].str)))
  		{
  			DPRINTF(E_ERROR, L_GENERAL, "inet_ntop(): %s\n", strerror(errno));
  			close(s);
  			continue;
  		}
  		if (ioctl(s, SIOCGIFNETMASK, ifr) < 0)
  			continue;
  		memcpy(&addr, &(ifr->ifr_addr), sizeof(addr));
  		memcpy(&lan_addr[n_lan_addr].mask, &addr.sin_addr, sizeof(addr));
  		lan_addr[n_lan_addr].snotify = OpenAndConfSSDPNotifySocket(lan_addr[i].addr.s_addr);
  		if (lan_addr[n_lan_addr].snotify >= 0)
  		{
  			if (notify)
  				SendSSDPNotifies(lan_addr[n_lan_addr].snotify, lan_addr[n_lan_addr].str,
  					runtime_vars.port, runtime_vars.notify_interval);
  			n_lan_addr++;
  		}
  		if (ifname || n_lan_addr >= MAX_LAN_ADDR)
  			break;
  	}
  	close(s);
  	if (ifname && i == n)
  	{
  		DPRINTF(E_ERROR, L_GENERAL, "Network interface %s not found\n", ifname);
  		return -1;
  	}
  #endif
  	return n_lan_addr;
  }
  
  int
  getsyshwaddr(char *buf, int len)
  {
  	unsigned char mac[6];
  	int ret = -1;
  #if defined(HAVE_GETIFADDRS) && !defined (__linux__)
  	struct ifaddrs *ifap, *p;
  	struct sockaddr_in *addr_in;
  	uint8_t a;
  
  	if (getifaddrs(&ifap) != 0)
  	{
  		DPRINTF(E_ERROR, L_GENERAL, "getifaddrs(): %s\n", strerror(errno));
  		return -1;
  	}
  	for (p = ifap; p != NULL; p = p->ifa_next)
  	{
  		if (p->ifa_addr && p->ifa_addr->sa_family == AF_LINK)
  		{
  			addr_in = (struct sockaddr_in *)p->ifa_addr;
  			a = (htonl(addr_in->sin_addr.s_addr) >> 0x18) & 0xFF;
  			if (a == 127)
  				continue;
  #ifdef __linux__
  			struct ifreq ifr;
  			int fd;
  			fd = socket(AF_INET, SOCK_DGRAM, 0);
  			if (fd < 0)
  				continue;
  			strncpy(ifr.ifr_name, p->ifa_name, IFNAMSIZ);
  			if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0)
  			{
  				close(fd);
  				continue;
  			}
  			memcpy(mac, ifr.ifr_hwaddr.sa_data, 6);
  #else
  			struct sockaddr_dl *sdl;
  			sdl = (struct sockaddr_dl*)p->ifa_addr;
  			memcpy(mac, LLADDR(sdl), sdl->sdl_alen);
  #endif
  			if (MACADDR_IS_ZERO(mac))
  				continue;
  			ret = 0;
  			break;
  		}
  	}
  	freeifaddrs(ifap);
  #else
  	struct if_nameindex *ifaces, *if_idx;
  	struct ifreq ifr;
  	int fd;
  
  	memset(&mac, '\0', sizeof(mac));
  	/* Get the spatially unique node identifier */
  	fd = socket(AF_INET, SOCK_DGRAM, 0);
  	if (fd < 0)
  		return ret;
  
  	ifaces = if_nameindex();
  	if (!ifaces)
  		return ret;
  
  	for (if_idx = ifaces; if_idx->if_index; if_idx++)
  	{
  		strncpy(ifr.ifr_name, if_idx->if_name, IFNAMSIZ);
  		if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0)
  			continue;
  		if (ifr.ifr_ifru.ifru_flags & IFF_LOOPBACK)
  			continue;
  		if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0)
  			continue;
  		if (MACADDR_IS_ZERO(ifr.ifr_hwaddr.sa_data))
  			continue;
  		memcpy(mac, ifr.ifr_hwaddr.sa_data, 6);
  		ret = 0;
  		break;
  	}
  	if_freenameindex(ifaces);
  	close(fd);
  #endif
  	if (ret == 0)
  	{
  		if (len > 12)
  			sprintf(buf, "%02x%02x%02x%02x%02x%02x",
  			        mac[0]&0xFF, mac[1]&0xFF, mac[2]&0xFF,
  			        mac[3]&0xFF, mac[4]&0xFF, mac[5]&0xFF);
  		else if (len == 6)
  			memmove(buf, mac, 6);
  	}
  	return ret;
  }
  
  int
  get_remote_mac(struct in_addr ip_addr, unsigned char *mac)
  {
  	struct in_addr arp_ent;
  	FILE * arp;
  	char remote_ip[16];
  	int matches, hwtype, flags;
  	memset(mac, 0xFF, 6);
  
   	arp = fopen("/proc/net/arp", "r");
  	if (!arp)
  		return 1;
  	while (!feof(arp))
  	{
  	        matches = fscanf(arp, "%15s 0x%8X 0x%8X %2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx",
  		                      remote_ip, &hwtype, &flags,
  		                      &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
  		if (matches != 9)
  			continue;
  		inet_pton(AF_INET, remote_ip, &arp_ent);
  		if (ip_addr.s_addr == arp_ent.s_addr)
  			break;
  		mac[0] = 0xFF;
  	}
  	fclose(arp);
  
  	if (mac[0] == 0xFF)
  	{
  		memset(mac, 0xFF, 6);
  		return 1;
  	}
  
  	return 0;
  }
  
  void
  reload_ifaces(int notify)
  {
  	int i;
  
  	for (i = 0; i < n_lan_addr; i++)
  	{
  		close(lan_addr[i].snotify);
  	}
  	n_lan_addr = 0;
  
  	if (runtime_vars.ifaces[0])
  	{
  		for (i = 0; runtime_vars.ifaces[i]; i++)
  		{
  			getifaddr(runtime_vars.ifaces[i], notify);
  		}
  	}
  	else
  		getifaddr(NULL, notify);
  
  	for (i = 0; i < n_lan_addr; i++)
  	{
  		DPRINTF(E_INFO, L_GENERAL, "Enabled interface %s/%s\n",
  			lan_addr[i].str, inet_ntoa(lan_addr[i].mask));
  	}
  }
  
  int
  OpenAndConfMonitorSocket(void)
  {
  #ifdef HAVE_NETLINK
  	struct sockaddr_nl addr;
  	int s;
  	int ret;
  
  	s = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
  	if (s < 0)
  	{
  		perror("couldn't open NETLINK_ROUTE socket");
  		return -1;
  	}
  
  	memset(&addr, 0, sizeof(addr));
  	addr.nl_family = AF_NETLINK;
  	addr.nl_groups = RTMGRP_IPV4_IFADDR;
  
  	ret = bind(s, (struct sockaddr*)&addr, sizeof(addr));
  	if (ret < 0)
  	{
  		perror("couldn't bind");
  		return -1;
  	}
  
  	return s;
  #else
  	return -1;
  #endif
  }
  
  void
  ProcessMonitorEvent(int s)
  {
  #ifdef HAVE_NETLINK
  	int len;
  	char buf[4096];
  	struct nlmsghdr *nlh;
  	int changed = 0;
  
  	nlh = (struct nlmsghdr*)buf;
  
  	len = recv(s, nlh, sizeof(buf), 0);
  	if (len <= 0)
  		return;
  	while ((NLMSG_OK(nlh, len)) && (nlh->nlmsg_type != NLMSG_DONE))
  	{
  		if (nlh->nlmsg_type == RTM_NEWADDR ||
  		    nlh->nlmsg_type == RTM_DELADDR)
  		{
  			changed = 1;
  		}
  		nlh = NLMSG_NEXT(nlh, len);
  	}
  	if (changed)
  		reload_ifaces(1);
  #endif
  }
  