From a075778f4f1f24f0ebccaad410202eaed91dbbc8 Mon Sep 17 00:00:00 2001
From: Ralph Ronnquist <ralph.ronnquist@gmail.com>
Date: Tue, 8 Nov 2022 16:49:49 +1100
Subject: [PATCH] Add optional source address for UDP.

---
 rrqnet.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 63 insertions(+), 5 deletions(-)

diff --git a/rrqnet.c b/rrqnet.c
index efd7066..ea0a3af 100644
--- a/rrqnet.c
+++ b/rrqnet.c
@@ -188,6 +188,12 @@ static struct {
 // Flag to signal the UDP socket as being ipv6 or not (forced ipv4)
 static int udp6 = 1;
 
+// The given UDP source address, if any
+static struct {
+    int family;
+    unsigned char address[16];
+} udp_source;
+
 // Flag to indicate tpg transport patch = avoid UDP payload of 1470
 // bytes by adding 2 tag-along bytes
 static int tpg_quirk = 0;
@@ -728,6 +734,38 @@ static int parse_mcast(char *arg) {
     return 0;
 }
 
+//** IP address parsing utility for UDP source address
+// Return 0 if ok and 1 otherwise
+// Formats: <ipv4-address> or <ipv6-address>
+// The ipv4 address should be a multicast address in ranges
+// 224.0.0.0/22, 232.0.0.0/7, 234.0.0.0/8 or 239.0.0.0/8
+// though it's not checked here.
+static int parse_udp_source(char *arg) {
+    if ( inet_pton( AF_INET6, arg, udp_source.address ) ) {
+	// An ipv6 address is given.
+	if ( udp6 ) {
+	    udp_source.family = AF_INET6;
+	    return 0;
+	}
+	return 1;
+    }
+    if ( ! inet_pton( AF_INET, arg, udp_source.address ) ) {
+	return 1;
+    }
+
+    // An ipv4 address is given.
+    if ( udp6 ) {
+	// Translate into ipv6-encoded ipv4
+	memmove( udp_source.address + 12, udp_source.address, 4 );
+	memset( udp_source.address, 0, 10 );
+	memset( udp_source.address + 10, -1, 2 );
+	udp_source.family = AF_INET6;
+    } else {
+	udp_source.family = AF_INET;
+    }
+    return 0;
+}
+
 // Utility that sets upt the multicast socket, which is used for
 // receiving multicast packets.
 static void setup_mcast() {
@@ -1312,9 +1350,16 @@ static void usage(void) {
     fprintf( stderr, "Packet tunneling over UDP, multiple channels, " );
     fprintf( stderr, "version 1.5.3\n" );
     fprintf( stderr, "Usage: " );
-    fprintf( stderr,
- "%s [-v] [-tpg] [-4] [-B n] [-T n] [-m mcast] [-t tap] port [remote]+ \n",
-	     progname );
+    fprintf( stderr, "%s [options] port [remote]+ \n", progname );
+    fprintf( stderr, "** options must be given or omitted in order!!\n" );
+    fprintf( stderr, " -v        = verbose log, -vv or -vvv for more logs\n" );
+    fprintf( stderr, " -tpg      = UDP transport quirk: avoid bad sizes\n" );
+    fprintf( stderr, " -4        = use an ipv4 UDP socket\n" );
+    fprintf( stderr, " -B n      = use n buffers (2*threads) by default\n");
+    fprintf( stderr, " -T n      = use n delivery threads (5 bu default)\n" );
+    fprintf( stderr, " -m mcast  = allow remotes on multicast address\n" );
+    fprintf( stderr, " -t tap    = use the nominated tap (or - for stdio)\n" );
+    fprintf( stderr, " -I source = use given source address for UDP\n" );
     exit( 1 );
 }
 
@@ -1472,6 +1517,15 @@ int main(int argc, char *argv[]) {
 	i += 2;
 	ENSUREARGS( 1 );
     }
+    // Then optional source address for UDP
+    if ( strncmp( "-I", argv[i], 2 ) == 0 ) {
+	ENSUREARGS( 2 );
+	if ( parse_udp_source( argv[i+1] ) ) {
+	    usage();
+	}
+	i += 2;
+	ENSUREARGS( 1 );
+    }
     // then: required port
     if ( sscanf( argv[i++], "%d", &port ) != 1 ) {
 	fprintf( stderr, "Bad local port: %s\n", argv[i-1] );
@@ -1547,8 +1601,12 @@ int main(int argc, char *argv[]) {
 	struct sockaddr_in udp_addr = {
 	    .sin_family = AF_INET,
 	    .sin_port = htons( port ),
-	    .sin_addr.s_addr = htonl(INADDR_ANY),
 	};
+	if ( udp_source.family == 0 ) {
+	    udp_addr.sin_addr.s_addr = htonl( INADDR_ANY );
+	} else {
+	    udp_addr.sin_addr.s_addr = *((uint32_t*) udp_source.address); 
+	}
 	if ( bind( udp_fd, (struct sockaddr*) &udp_addr, sizeof(udp_addr))) {
 	    fprintf( stderr, "Error binding socket!\n");
 	    exit(1);
@@ -1563,8 +1621,8 @@ int main(int argc, char *argv[]) {
 	struct sockaddr_in6 udp6_addr = {
 	    .sin6_family = AF_INET6,
 	    .sin6_port = htons( port ),
-	    .sin6_addr = IN6ADDR_ANY_INIT,
 	};
+	memcpy( udp6_addr.sin6_addr.s6_addr, udp_source.address, 16 );
 	if ( bind( udp_fd, (struct sockaddr*) &udp6_addr, sizeof(udp6_addr))) {
 	    fprintf( stderr, "Error binding socket!\n");
 	    exit(1);
-- 
2.39.5