Subject: pkgsrc/wakeup with multiple interfaces
To: None <current-users@netbsd.org>
From: John F <from_netbsd5@frear.com>
List: current-users
Date: 12/12/2005 00:18:35
Hi all!

 	I'm trying to figure out how to make a C program send out a udp 
packet to the broadcast address and select which interface is used.

 	I'm trying to modify 'hpwake.c' from the wakeup pkgsrc package 
because I noticed that hpwake.c has no mechanism to allow you to select 
which interface is used to transmit the wake-on-lan "magic packet".  As 
shipped, the To: address is INADDR_BROADCAST and the From: address is 
INADDR_ANY.

 	I want to do this because if you have a multihomed system it seems 
that the program selects an arbitrary interface to send its udp packet 
down.  My neb 1.6.2 system has an rtk and an fxp and the default gateway 
is reached via the rtk.  The 'wakeup' program has always sent its magic 
packet down the fxp, which is exactly what I want.  One day, I modified 
the netmask of the rtk and 'wakeup' decided to send its packets down that 
interface instead.  A reboot of the system (while keeping the updated 
netmask) caused 'wakeup' to revert to its orignal behavior of using the 
fxp.  So I decided that I have no clue how the program decided to use the 
fxp and also that 'wakeup' needs a command line option to allow you to 
select the interface that it uses.

 	I decided to use the getifaddrs function and have had two 
approaches at this.

 	My first try was to change the From: address to be the IP of a 
local interface. I had high hopes for this method because lsof showed the 
udp socket attached to the correct IP address after the bind call.  But, 
it didn't work - packets always left via the fxp but they were labeled 
with the IP of the fxp, the rtk, or the loopback interface, depending on 
which interface I gave on the command line (that is, which local address 
was passed to the bind() call).

 	My second try was based on the idea that, IP routing is based on 
To: addresses, not From:.  So, instead of modifying the packet source, I 
pulled the broadcast field from the getifaddrs result and used that as my 
destination.  This resulted in the same behavior as the stock program had.

 	So, that's the problem I'm trying to solve and the approach I've 
taken.  I'm very new to socket programming in C, so I'm still working at 
it and perhaps I may solve this yet.  I just want to share my efforts and 
see if anyone sees the flaw in my approach or my code.  Your comments are 
greatly appriceated.

 	Thank you muchly,

 	- John F.
 	from_netbsd5@frear.com


-- The patch for try #1

--- hpwake.c	Wed Dec  7 00:05:07 2005
+++ hpwake.c.new	Sun Dec 11 20:19:06 2005
@@ -25,11 +25,68 @@
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
+#include <ifaddrs.h>
+#include <net/if.h>
+#include <arpa/inet.h>

  u_char magicpacket[500];

  int get_magicpacket(unsigned char *,char *);
  int main(int,char *[]);
+int select_if( char *, struct sockaddr_in *);
+void usage(char *);
+
+void
+usage(name)
+
+char *name;
+
+{
+	fprintf(stderr, "Usage: %s [-i ifname] xx:xx:xx:xx:xx:xx\n", name);
+	exit(1);
+}
+
+int
+select_if(if_name,sa)
+
+char *if_name;
+struct sockaddr_in *sa;
+
+{
+	struct ifaddrs *ifaddrs, *cur_if;
+	int matched = 0;
+
+	if( getifaddrs(&ifaddrs) != 0 ) {
+		perror( "getifaddrs" );
+		return NULL;
+	}
+	for( cur_if=ifaddrs; cur_if != NULL; cur_if=cur_if->ifa_next ) {
+		if( (cur_if->ifa_flags & IFF_UP) &&
+			(cur_if->ifa_addr->sa_family == PF_INET) &&
+			strcmp( cur_if->ifa_name, if_name ) == 0 )
+		{
+			matched = 1;
+			memcpy(sa, cur_if->ifa_addr, sizeof(struct sockaddr));
+			break;
+		}
+	}
+	freeifaddrs( ifaddrs );
+	if( !matched )
+	{
+		fprintf(stderr, "Fatal: Couldn't find interface %s.\n",if_name);
+		return NULL;
+	}
+	printf("Using interface %s, %s\n", if_name, inet_ntoa(sa->sin_addr) );
+	/*
+	printf("sa.sin_len=%d,sa.sin_family=%s,sa.sin_port=%d,sa.sin_addr=%s\n",
+		sa->sin_len,
+		sa->sin_family == PF_INET ? "PF_INET" : "Not pf_inet",
+		sa->sin_port,
+		inet_ntoa( sa->sin_addr ) );
+	 */
+
+	return 1;
+}

  int get_magicpacket(packet,arg)

@@ -101,13 +158,26 @@
          int packetsize;
          int s;
          int i;
+	int doselect_if;

-	if (argc < 2) {
-		fprintf(stderr, "Usage: %s xx:xx:xx:xx:xx:xx\n", argv[0]);
-		exit(1);
+	switch( argc )
+	{
+	case 2:
+		doselect_if = 0;
+		break;
+	case 4:
+		if( strcmp( argv[1], "-i" ) == 0 ) {
+			doselect_if = 1;
+			break;
+		}
+	default:
+		usage(argv[0]);
  	}

-        packetsize = get_magicpacket(magicpacket, argv[1]);
+	if( doselect_if )
+		packetsize = get_magicpacket(magicpacket, argv[3]);
+	else
+		packetsize = get_magicpacket(magicpacket, argv[1]);

          if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
                  perror("socket");
@@ -115,9 +185,15 @@
          if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char *)&one, sizeof(one)) < 0)
                  perror("setsockopt");

-        server.sin_family = AF_INET;
-        server.sin_port = 0;
-        server.sin_addr.s_addr = INADDR_ANY;
+	if( doselect_if ) {
+		if( select_if( argv[2], &server ) == NULL )
+			return 1;
+		server.sin_port = 0;
+	} else {
+		server.sin_family = AF_INET;
+		server.sin_port = 0;
+		server.sin_addr.s_addr = INADDR_ANY;
+	}

          client.sin_family = AF_INET;
          client.sin_port = 32768+666;		/* invalid port */


-- Try #2. This patch was generated from try #1 not from the orig hpwake.c

--- try1/hpwake.c	Sun Dec 11 20:19:06 2005
+++ try2/hpwake.c	Sun Dec 11 23:37:42 2005
@@ -25,8 +25,8 @@
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
-#include <ifaddrs.h>
  #include <net/if.h>
+#include <ifaddrs.h>
  #include <arpa/inet.h>

  u_char magicpacket[500];
@@ -62,11 +62,12 @@
  	}
  	for( cur_if=ifaddrs; cur_if != NULL; cur_if=cur_if->ifa_next ) {
  		if( (cur_if->ifa_flags & IFF_UP) &&
-			(cur_if->ifa_addr->sa_family == PF_INET) &&
+			(cur_if->ifa_broadaddr != NULL) &&
+			(cur_if->ifa_broadaddr->sa_family == PF_INET) &&
  			strcmp( cur_if->ifa_name, if_name ) == 0 )
  		{
  			matched = 1;
-			memcpy(sa, cur_if->ifa_addr, sizeof(struct sockaddr));
+			memcpy(sa, cur_if->ifa_broadaddr, sizeof(struct sockaddr));
  			break;
  		}
  	}
@@ -77,13 +78,11 @@
  		return NULL;
  	}
  	printf("Using interface %s, %s\n", if_name, inet_ntoa(sa->sin_addr) );
-	/*
  	printf("sa.sin_len=%d,sa.sin_family=%s,sa.sin_port=%d,sa.sin_addr=%s\n",
  		sa->sin_len,
  		sa->sin_family == PF_INET ? "PF_INET" : "Not pf_inet",
  		sa->sin_port,
  		inet_ntoa( sa->sin_addr ) );
-	 */

  	return 1;
  }
@@ -186,18 +185,18 @@
                  perror("setsockopt");

  	if( doselect_if ) {
-		if( select_if( argv[2], &server ) == NULL )
+		if( select_if( argv[2], &client ) == NULL )
  			return 1;
-		server.sin_port = 0;
+		client.sin_port = 32768+666;		/* invalid port */
  	} else {
-		server.sin_family = AF_INET;
-		server.sin_port = 0;
-		server.sin_addr.s_addr = INADDR_ANY;
+		client.sin_family = AF_INET;
+		client.sin_port = 32768+666;		/* invalid port */
+		client.sin_addr.s_addr = INADDR_BROADCAST;
  	}

-        client.sin_family = AF_INET;
-        client.sin_port = 32768+666;		/* invalid port */
-        client.sin_addr.s_addr = INADDR_BROADCAST;
+	server.sin_family = AF_INET;
+	server.sin_port = 0;
+	server.sin_addr.s_addr = INADDR_ANY;

          if (bind(s, (struct sockaddr *)&server, sizeof(server)) < 0)
                  perror("bind");