Browse Source

core:socket_info Added automatic discovery of IPv6 interfaces on Linux using RT_NETLINK socks

The behavior now is identical for IPv4 or IPv6 interfaces : if no listen directives are given,
either via command line or config file, Kamailio will automatically detect IPv4/IPv6 interfaces and
bind to them (well, it will not bind to link-local prefixes)
Marius Zbihlei 14 years ago
parent
commit
c6b62bf872
1 changed files with 403 additions and 12 deletions
  1. 403 12
      socket_info.c

+ 403 - 12
socket_info.c

@@ -1,3 +1,4 @@
+
 /* $Id$
  *
  * find & manage listen addresses 
@@ -713,7 +714,369 @@ error:
 	return -1;
 }
 
+#ifdef __OS_linux
+
+#include "linux/netlink.h"
+#include "linux/rtnetlink.h"
+#include "arpa/inet.h"
+
+
+#define MAX_IF_LEN 64
+struct idx
+{
+	struct idx * 	next;
+	int 		family;
+	unsigned	ifa_flags;
+	char		addr[MAX_IF_LEN];
+
+};
+
+struct idxlist{
+	struct idx* 	addresses;
+	int 		index;
+	char 		name[MAX_IF_LEN];
+	unsigned 	flags;
+};
+
+#define MAX_IFACE_NO 32
+
+static struct idxlist *ifaces = NULL;
+static int seq = 0;
+
+#define SADDR(s) ((struct sockaddr_in*)s)->sin_addr.s_addr
+
+#define NLMSG_TAIL(nmsg) \
+	((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+
+int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data,
+	      int alen)
+{
+	int len = RTA_LENGTH(alen);
+	struct rtattr *rta;
+
+	if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) {
+		fprintf(stderr, "addattr_l ERROR: message exceeded bound of %d\n",maxlen);
+		return -1;
+	}
+	rta = NLMSG_TAIL(n);
+	rta->rta_type = type;
+	rta->rta_len = len;
+	memcpy(RTA_DATA(rta), data, alen);
+	n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
+	return 0;
+}
+
+
+
+static int nl_bound_sock(void)
+{
+	int sock;
+	struct sockaddr_nl la;
+
+	sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+	if(sock <= 0){
+		LM_ERR("could not create NETLINK sock to get interface list");
+		goto error;
+	}
+
+	/* bind NETLINK socket to pid */
+	bzero(&la, sizeof(la));
+	la.nl_family = AF_NETLINK;
+	la.nl_pad = 0;
+	la.nl_pid = getpid();
+	la.nl_groups = 0;
+	if ( bind(sock, (struct sockaddr*) &la, sizeof(la)) < 0){
+		LM_ERR("could not bind NETLINK sock to sockaddr_nl\n");
+		goto error;
+	}
+
+	return sock;
+error:
+	if(sock > 0) close(sock);
+	return -1;
+}
+
+#define fill_nl_req(req, type, family) do {\
+	memset(&req, 0, sizeof(req));\
+	req.nlh.nlmsg_len = sizeof(req);\
+	req.nlh.nlmsg_type = type;\
+	req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST|NLM_F_DUMP;\
+	req.nlh.nlmsg_pid = getpid();\
+	req.nlh.nlmsg_seq = seq++;\
+	req.g.rtgen_family = family;\
+	} while(0);
+
+	
+static int get_flags(int family){
+	struct {
+		struct nlmsghdr nlh;
+		struct rtgenmsg g;
+	} req;
+	int rtn = 0;
+	struct nlmsghdr*  nlp;
+	struct ifinfomsg *ifi;
+	char buf[8192];
+	char *p = buf;
+	int nll = 0;
+        int nl_sock = 0;
+
+	fill_nl_req(req, RTM_GETLINK, AF_INET);
+
+	if((nl_sock = nl_bound_sock()) < 0) return -1;
+
+	if(send(nl_sock, (void*)&req, sizeof(req), 0) < 0)
+	{
+		LM_ERR("error sending NETLINK request\n");
+		goto error;
+	}
+
+	while(1) {
+		rtn = recv(nl_sock, p, sizeof(buf) - nll, 0);
+		nlp = (struct nlmsghdr *) p;
+		if(nlp->nlmsg_type == NLMSG_DONE){
+			LM_DBG("done\n");
+			 break;
+		}
+		if(nlp->nlmsg_type == NLMSG_ERROR){
+			 LM_DBG("Error on message to netlink");
+			 break;
+		}
+		p += rtn;
+
+		nll += rtn;
+	}
+
+	nlp = (struct nlmsghdr *) buf;
+	for(;NLMSG_OK(nlp, nll);nlp=NLMSG_NEXT(nlp, nll)){
+		ifi = NLMSG_DATA(nlp);
+
+		if (nlp->nlmsg_len < NLMSG_LENGTH(sizeof(ifi)))
+			goto error;
+
+		LM_ERR("Interface with index %d has flags %d\n", ifi->ifi_index, ifi->ifi_flags);
+		if(ifaces == NULL){
+			LM_ERR("get_flags must not be called on empty interface list");
+			goto error;
+		}
+		if(ifi->ifi_index >= MAX_IFACE_NO){
+			LM_ERR("invalid network interface index returned %d", ifi->ifi_index);
+			goto error;
+		}
+		ifaces[ifi->ifi_index].flags = ifi->ifi_flags;
+	}
+
+	if(nl_sock>0) close(nl_sock);
+	return 0;
+
+error:
+	if(nl_sock>0) close(nl_sock);
+	return -1;
+}
+
+static int build_iface_list(void)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct rtgenmsg g;
+	} req;
+
+	int seq = 0;
+	int rtn = 0;
+	struct nlmsghdr*  nlp;
+	struct ifaddrmsg *ifi;
+	int rtl;
+	char buf[8192];
+	char *p = buf;
+	int nll = 0;
+	struct rtattr * rtap;
+	int index, i;
+	struct idx* entry;
+	struct idx* tmp;
+        int nl_sock = 0;
+        int families[] = {AF_INET, AF_INET6};
+        char name[MAX_IF_LEN];
+	int is_link_local = 0;
+
+	if(ifaces == NULL){
+		if((ifaces = (struct idxlist*)pkg_malloc(MAX_IFACE_NO*sizeof(struct idxlist))) == NULL){
+			LM_ERR("No more pkg memory\n");
+			return -1;
+		}
+		memset(ifaces, 0, sizeof(struct idxlist)*MAX_IFACE_NO);
+	}
+
+	/* bind netlink socket */
+	if((nl_sock = nl_bound_sock()) < 0) return -1;
 
+	for (i = 0 ; i < sizeof(families)/sizeof(int); i++) {
+		fill_nl_req(req, RTM_GETADDR, families[i]);
+
+		if(send(nl_sock, (void*)&req, sizeof(req), 0) < 0){
+			LM_ERR("error sending NETLINK request\n");
+			goto error;
+		};
+
+		memset(buf, 0, sizeof(buf));
+		nll = 0;
+		p = buf;
+		while(1) {
+			rtn = recv(nl_sock, p, sizeof(buf) - nll, 0);
+			LM_DBG("received %d byles \n", rtn);
+			nlp = (struct nlmsghdr *) p;
+			if(nlp->nlmsg_type == NLMSG_DONE){
+				LM_DBG("done receiving netlink info \n");
+				 break;
+			}
+			if(nlp->nlmsg_type == NLMSG_ERROR){
+				 LM_ERR("Error on message to netlink");
+				 break;
+			}
+			p += rtn;
+
+			nll += rtn;
+		}
+
+		nlp = (struct nlmsghdr *) buf;
+		for(;NLMSG_OK(nlp, nll);nlp=NLMSG_NEXT(nlp, nll)){
+			ifi = NLMSG_DATA(nlp);
+
+			if (nlp->nlmsg_len < NLMSG_LENGTH(sizeof(ifi)))
+				continue;
+			// init all the strings
+			// inner loop: loop thru all the attributes of
+			// one route entry
+			rtap = (struct rtattr *) IFA_RTA(ifi);
+
+			rtl = IFA_PAYLOAD(nlp);
+
+			index = ifi->ifa_index;
+			if(index >= MAX_IFACE_NO){
+				LM_ERR("Invalid interface index returned: %d\n", index);
+				goto error;
+			}
+
+			entry = (struct idx*)pkg_malloc(sizeof(struct idx));
+			if(entry == 0)
+			{
+				LM_ERR("could not allocate memory\n");
+				goto error;
+			}
+
+			entry->next = 0;
+			entry->family = families[i];
+			entry->ifa_flags = ifi->ifa_flags;
+                        is_link_local = 0;
+
+			for(;RTA_OK(rtap, rtl);rtap=RTA_NEXT(rtap,rtl)){
+				switch(rtap->rta_type){
+					case IFA_ADDRESS:
+						if((*(int*)RTA_DATA(rtap))== htons(0xfe80)){
+							LM_DBG("Link Local Address, ignoring ...\n");
+							is_link_local = 1;
+							break;
+						}
+						inet_ntop(families[i], RTA_DATA(rtap), entry->addr, MAX_IF_LEN);
+						LM_DBG("iface <IFA_ADDRESS> addr is  %s\n", entry->addr);
+						break;
+					case IFA_LOCAL:
+						if((*(int*)RTA_DATA(rtap))== htons(0xfe80)){
+							LM_DBG("Link Local Address, ignoring ...\n");
+							is_link_local = 1;
+						}
+						inet_ntop(families[i], RTA_DATA(rtap), entry->addr, MAX_IF_LEN);
+						LM_DBG("iface <IFA_LOCAL> addr is %s\n", entry->addr);
+						break;
+					case IFA_LABEL:
+						LM_DBG("iface name is %s\n", (char*)RTA_DATA(rtap));
+						strncpy(name, (char*)RTA_DATA(rtap), MAX_IF_LEN);
+						break;
+					case IFA_BROADCAST:
+					case IFA_ANYCAST:
+					case IFA_UNSPEC:
+					case IFA_CACHEINFO:
+					default:
+						break;
+				}
+			}
+			if(is_link_local) continue;    /* link local addresses are not bindable */
+
+			if(strlen(ifaces[index].name)==0)
+				strncpy(ifaces[index].name, name, MAX_IF_LEN);
+
+			ifaces[index].index = index;
+
+			if(ifaces[index].addresses == 0 )
+				ifaces[index].addresses = entry;
+			else {
+				for(tmp = ifaces[index].addresses; tmp->next ; tmp = tmp->next)/*empty*/;
+				tmp->next = entry;
+			}
+		}
+	}
+	if(nl_sock>0) close(nl_sock);
+	/* the socket should be closed so we can bind again */
+	for(i = 0; i < sizeof(families)/sizeof(int); i++){
+		/* get device flags */
+		get_flags(families[i]); /* AF_INET or AF_INET6 */
+	}
+
+	return 0;
+error:
+	if(nl_sock>0) close(nl_sock);
+	return -1;
+
+}
+/* add all family type addresses of interface if_to the socket_info array
+ * if if_name==0, adds all addresses on all interfaces
+ * uses RTNETLINK sockets to get addresses on the present interface on LINUX
+ * return: -1 on error, 0 on success
+ */
+int add_interfaces_via_netlink(char* if_name, int family, unsigned short port,
+					unsigned short proto,
+					struct addr_info** ai_l)
+{
+	int i;
+	struct idx* tmp;
+	enum si_flags flags;
+
+	if(ifaces == NULL && (build_iface_list()!=0)){
+		LM_ERR("Could not get network interface list\n");
+		return -1;
+	}
+
+	flags=SI_NONE;
+	for(i=0; i< MAX_IFACE_NO; ++i){
+		if(ifaces[i].addresses == NULL) continue; /* not present/configured */
+		if ((if_name==0)||
+			(strncmp(if_name, ifaces[i].name, strlen(ifaces[i].name))==0)){
+
+			/* check if iface is up */
+			//if(! (ifaces[i].flags & IFF_UP) ) continue;
+
+			for(tmp = ifaces[i].addresses; tmp; tmp = tmp->next){
+				LM_DBG("\t in add_iface_via_netlink Name %s Adress %s\n", ifaces[i].name, tmp->addr);
+		                /* match family */
+                                if (family == tmp->family){
+					/* check if loopback */
+					if (ifaces[i].flags & IFF_LOOPBACK){
+						LM_DBG("INTERFACE %s is loopback", ifaces[i].name);
+						flags|=SI_IS_LO;
+					}
+					/* save the info */
+					if (new_addr_info2list(tmp->addr, flags, ai_l)!=0){
+						LOG(L_ERR, "ERROR: add_interfaces: "
+							"new_addr_info2list failed\n");
+						goto error;
+			    		}
+				}
+			}
+		}
+	}
+	return 0;
+error:
+	return -1;
+}
+#endif /* __OS_linux */
 
 /* add all family type addresses of interface if_name to the socket_info array
  * if if_name==0, adds all addresses on all interfaces
@@ -736,7 +1099,7 @@ int add_interfaces(char* if_name, int family, unsigned short port,
 	struct ip_addr addr;
 	int ret;
 	enum si_flags flags;
-	
+
 #ifdef HAVE_SOCKADDR_SA_LEN
 	#ifndef MAX
 		#define MAX(a,b) ( ((a)>(b))?(a):(b))
@@ -1345,25 +1708,46 @@ int fix_all_socket_lists()
 			&& (sctp_listen==0)
 #endif
 		){
-		/* get all listening ipv4 interfaces */
-		if ((add_interfaces(0, AF_INET, 0,  PROTO_UDP, &ai_lst)==0) &&
-			(addr_info_to_si_lst(ai_lst, 0, PROTO_UDP, 0, &udp_listen)==0)){
+		/* get all listening ipv4/ipv6 interfaces */
+		if ( ( (add_interfaces(0, AF_INET, 0,  PROTO_UDP, &ai_lst)==0)
+#ifdef USE_IPV6
+#ifdef __OS_linux
+		&& (add_interfaces_via_netlink(0, AF_INET6, 0, PROTO_UDP, &ai_lst) == 0)
+#else
+		&& (add_interfaces(0, AF_INET6, 0,  PROTO_UDP, &ai_lst)==0) /* add_interface does not work for IPv6 on Linux */
+#endif /* __OS_linux */
+#endif /* USE_IPV6 */
+			 ) && (addr_info_to_si_lst(ai_lst, 0, PROTO_UDP, 0, &udp_listen)==0)){
 			free_addr_info_lst(&ai_lst);
 			ai_lst=0;
 			/* if ok, try to add the others too */
 #ifdef USE_TCP
 			if (!tcp_disable){
-				if ((add_interfaces(0, AF_INET, 0,  PROTO_TCP, &ai_lst)!=0) ||
-					(addr_info_to_si_lst(ai_lst, 0, PROTO_TCP, 0,
+				if ( ((add_interfaces(0, AF_INET, 0,  PROTO_TCP, &ai_lst)!=0)
+#ifdef USE_IPV6
+#ifdef __OS_linux
+    				|| (add_interfaces_via_netlink(0, AF_INET6, 0, PROTO_TCP, &ai_lst) != 0)
+#else
+				|| (add_interfaces(0, AF_INET6, 0,  PROTO_TCP, &ai_lst)!=0)
+#endif /* __OS_linux */
+#endif /* USE_IPV6 */
+				)|| (addr_info_to_si_lst(ai_lst, 0, PROTO_TCP, 0,
 										 				&tcp_listen)!=0))
 					goto error;
 				free_addr_info_lst(&ai_lst);
 				ai_lst=0;
 #ifdef USE_TLS
 				if (!tls_disable){
-					if ((add_interfaces(0, AF_INET, 0, PROTO_TLS,
-										&ai_lst)!=0) ||
-						(addr_info_to_si_lst(ai_lst, 0, PROTO_TLS, 0,
+					if (((add_interfaces(0, AF_INET, 0, PROTO_TLS,
+										&ai_lst)!=0)
+#ifdef USE_IPV6
+#ifdef __OS_linux
+    				|| (add_interfaces_via_netlink(0, AF_INET6, 0, PROTO_TLS, &ai_lst) != 0)
+#else
+				|| (add_interfaces(0, AF_INET6, 0,  PROTO_TLS, &ai_lst)!=0)
+#endif /* __OS_linux */
+#endif /* USE_IPV6 */
+					) || (addr_info_to_si_lst(ai_lst, 0, PROTO_TLS, 0,
 										 				&tls_listen)!=0))
 						goto error;
 				}
@@ -1374,9 +1758,16 @@ int fix_all_socket_lists()
 #endif
 #ifdef USE_SCTP
 			if (!sctp_disable){
-				if ((add_interfaces(0, AF_INET, 0,  PROTO_SCTP, &ai_lst)!=0)||
-					(addr_info_to_si_lst(ai_lst, 0, PROTO_SCTP, 0,
-										 				&sctp_listen)!=0))
+				if (((add_interfaces(0, AF_INET, 0,  PROTO_SCTP, &ai_lst)!=0)
+#ifdef USE_IPV6
+#ifdef __OS_linux
+    				|| (add_interfaces_via_netlink(0, AF_INET6, 0, PROTO_SCTP, &ai_lst) != 0)
+#else
+				|| (add_interfaces(0, AF_INET6, 0,  PROTO_SCTP, &ai_lst)!=0)
+#endif /* __OS_linux */
+#endif /* USE_IPV6 */
+					) || (addr_info_to_si_lst(ai_lst, 0, PROTO_SCTP, 0,
+							 				&sctp_listen)!=0))
 					goto error;
 				free_addr_info_lst(&ai_lst);
 				ai_lst=0;