From e2e3cc304f24cf8be2de525f7b4b765e2d00f590 Mon Sep 17 00:00:00 2001
From: Pablo Neira Ayuso <pablo@netfilter.org>
Date: Thu, 15 Feb 2024 21:08:26 +0100
Subject: [PATCH libgtnl] gtp-link: allow to specify listener address

   gtp-link add <device> <family> [--sgsn] [<address>]

Allow to specify listener address, if not specified use ANY_ADDRESS.
---
 tools/gtp-link.c | 69 ++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 55 insertions(+), 14 deletions(-)

diff --git a/tools/gtp-link.c b/tools/gtp-link.c
index 325a45f795ac..0a474eec9fdb 100644
--- a/tools/gtp-link.c
+++ b/tools/gtp-link.c
@@ -39,6 +39,7 @@
 #include <linux/if_link.h>
 
 #include <libgtpnl/gtpnl.h>
+#include <errno.h>
 
 struct gtp_server_sock {
 	int			family;
@@ -55,20 +56,26 @@ struct gtp_server_sock {
 			struct sockaddr_in6	in6;
 		} fd2;
 	} sockaddr;
+	union {
+		struct in_addr v4;
+		struct in6_addr v6;
+	} addr;
 };
 
-static void setup_sockaddr_in(struct sockaddr_in *sockaddr, uint16_t port)
+static void setup_sockaddr_in(struct sockaddr_in *sockaddr, struct in_addr *in,
+			      uint16_t port)
 {
 	sockaddr->sin_family = AF_INET;
 	sockaddr->sin_port = htons(port);
-	sockaddr->sin_addr.s_addr = INADDR_ANY;
+	sockaddr->sin_addr = *in;
 }
 
-static void setup_sockaddr_in6(struct sockaddr_in6 *sockaddr, uint16_t port)
+static void setup_sockaddr_in6(struct sockaddr_in6 *sockaddr, struct in6_addr *in6,
+			       uint16_t port)
 {
 	sockaddr->sin6_family = AF_INET6;
 	sockaddr->sin6_port = htons(port);
-	sockaddr->sin6_addr = in6addr_any;
+	sockaddr->sin6_addr = *in6;
 }
 
 static int setup_socket(struct gtp_server_sock *gtp_sock, int family)
@@ -80,8 +87,6 @@ static int setup_socket(struct gtp_server_sock *gtp_sock, int family)
 	if (fd1 < 0 || fd2 < 0)
 		return -1;
 
-	memset(gtp_sock, 0, sizeof(*gtp_sock));
-
 	gtp_sock->family = family;
 	gtp_sock->fd1 = fd1;
 	gtp_sock->fd2 = fd2;
@@ -89,13 +94,17 @@ static int setup_socket(struct gtp_server_sock *gtp_sock, int family)
 	switch (family) {
 	case AF_INET:
 		gtp_sock->len = sizeof(struct sockaddr_in);
-		setup_sockaddr_in(&gtp_sock->sockaddr.fd1.in, 3386);
-		setup_sockaddr_in(&gtp_sock->sockaddr.fd2.in, 2152);
+		setup_sockaddr_in(&gtp_sock->sockaddr.fd1.in,
+				  &gtp_sock->addr.v4, 3386);
+		setup_sockaddr_in(&gtp_sock->sockaddr.fd2.in,
+				  &gtp_sock->addr.v4, 2152);
 		break;
 	case AF_INET6:
 		gtp_sock->len = sizeof(struct sockaddr_in6);
-		setup_sockaddr_in6(&gtp_sock->sockaddr.fd1.in6, 3386);
-		setup_sockaddr_in6(&gtp_sock->sockaddr.fd2.in6, 2152);
+		setup_sockaddr_in6(&gtp_sock->sockaddr.fd1.in6,
+				   &gtp_sock->addr.v6, 3386);
+		setup_sockaddr_in6(&gtp_sock->sockaddr.fd2.in6,
+				   &gtp_sock->addr.v6, 2152);
 		if (setsockopt(fd1, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)) < 0)
 			perror("setsockopt IPV6_V6ONLY: ");
 		if (setsockopt(fd2, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)) < 0)
@@ -111,9 +120,12 @@ int main(int argc, char *argv[])
 	char buf[MNL_SOCKET_BUFFER_SIZE];
 	struct gtp_server_sock gtp_sock;
 	int ret, sgsn_mode = 0, family;
+	const char *addr = NULL;
+
+	memset(&gtp_sock, 0, sizeof(gtp_sock));
 
 	if (argc < 3) {
-		printf("Usage: %s add <device> <family> [--sgsn]\n", argv[0]);
+		printf("Usage: %s add <device> <family> [--sgsn] <address>\n", argv[0]);
 		printf("       %s del <device>\n", argv[0]);
 		exit(EXIT_FAILURE);
 	}
@@ -126,12 +138,24 @@ int main(int argc, char *argv[])
 		return 0;
 	} else if (!strcmp(argv[1], "add")) {
 		if (argc < 4) {
-			printf("Usage: %s add <device> <family> [--sgsn]\n", argv[0]);
+			printf("Usage: %s add <device> <family> [--sgsn] <device>\n", argv[0]);
 			exit(EXIT_FAILURE);
 		}
 
-		if (argc == 5 && !strcmp(argv[4], "--sgsn"))
-			sgsn_mode = 1;
+		if (argc == 5) {
+			if (!strcmp(argv[4], "--sgsn")) {
+				sgsn_mode = 1;
+			} else {
+				addr = argv[4];
+			}
+		}
+
+		if (argc == 6) {
+			if (!strcmp(argv[4], "--sgsn"))
+				sgsn_mode = 1;
+
+			addr = argv[5];
+		}
 	}
 
 	if (!strcmp(argv[3], "ip"))
@@ -144,6 +168,23 @@ int main(int argc, char *argv[])
 		exit(EXIT_FAILURE);
 	}
 
+	if (addr) {
+		if (!inet_pton(family, addr, &gtp_sock.addr)) {
+			fprintf(stderr, "invalid listener address: %s\n",
+				strerror(errno));
+			exit(EXIT_FAILURE);
+		}
+	} else {
+		switch (family) {
+		case AF_INET:
+			gtp_sock.addr.v4.s_addr = INADDR_ANY;
+			break;
+		case AF_INET6:
+			gtp_sock.addr.v6 = in6addr_any;
+			break;
+		}
+	}
+
 	if (setup_socket(&gtp_sock, family) < 0) {
 		perror("socket");
 		exit(EXIT_FAILURE);
-- 
2.30.2

