summaryrefslogtreecommitdiff
path: root/net/mwan3/src
diff options
context:
space:
mode:
authorAaron Goodman <aaronjg@stanford.edu>2020-10-11 18:37:25 -0400
committerAaron Goodman <aaronjg@stanford.edu>2020-10-16 09:54:48 -0400
commitbbbc6127abf132a92cc73dd23fa88409dc2b9abd (patch)
treeb0e0de516771222523a601f4cd6c1063e30bacb7 /net/mwan3/src
parentfb4a2d99eff3af53d4eea2db36d270a140bde6bf (diff)
mwan3: use helper library for mwan3track
Rather than using a special mwan3 user to manage mwan3track's tracking packets, this commit implements a small helper library to bind to device and to set a fwmark so that the tracking packets can be routed out of the correct interface. This provides a consistent method for binding to a device rather than relying on various packages potentially buggy implementations. For example: #8139 and #12836 This helper issue also allows for more tracking methods to be added even if they do not have a command line option to bind to device, such as iperf3 (eg #13050). Signed-off-by: Aaron Goodman <aaronjg@stanford.edu>
Diffstat (limited to 'net/mwan3/src')
-rw-r--r--net/mwan3/src/sockopt_wrap.c255
1 files changed, 255 insertions, 0 deletions
diff --git a/net/mwan3/src/sockopt_wrap.c b/net/mwan3/src/sockopt_wrap.c
new file mode 100644
index 000000000..695e57502
--- /dev/null
+++ b/net/mwan3/src/sockopt_wrap.c
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Aaron Goodman <aaronjg@alumni.stanford.edu>. All Rights Reserved.
+ */
+
+/*
+ * sockopt_wrap.c provides a shared library that intercepts syscalls to various
+ * networking functions to bind the sockets a source IP address and network device
+ * and to set the firewall mark on otugoing packets. Parameters are set using the
+ * DEVICE, SRCIP, FWMARK environment variables.
+ *
+ * Additionally the FAMILY environment variable can be set to either 'ipv4' or
+ * 'ipv6' to cause sockets opened with ipv6 or ipv4 to fail, respectively.
+ *
+ * Each environment variable is optional, and if not set, the library will not
+ * enforce the particular parameter.
+ */
+
+#define _GNU_SOURCE
+#include <dlfcn.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <net/ethernet.h>
+#include <linux/if_packet.h>
+#include <net/if.h>
+
+static int (*next_socket)(int domain, int type, int protocol);
+static int (*next_setsockopt)(int sockfd, int level, int optname,
+ const void *optval, socklen_t optlen);
+static int (*next_bind)(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
+static int (*next_close)(int fd);
+static ssize_t (*next_send)(int sockfd, const void *buf, size_t len, int flags);
+static ssize_t (*next_sendto)(int sockfd, const void *buf, size_t len, int flags,
+ const struct sockaddr *dest_addr, socklen_t addrlen);
+static ssize_t (*next_sendmsg)(int sockfd, const struct msghdr *msg, int flags);
+static int (*next_connect)(int sockfd, const struct sockaddr *addr,
+ socklen_t addrlen);
+static int device=0;
+static struct sockaddr_in source4 = {0};
+#ifdef CONFIG_IPV6
+static struct sockaddr_in6 source6 = {0};
+#endif
+static struct sockaddr * source = 0;
+static int sockaddr_size = 0;
+static int is_bound [1024] = {0};
+
+#define next_func(x)\
+void set_next_##x(){\
+ if (next_##x) return;\
+ next_##x = dlsym(RTLD_NEXT, #x);\
+ dlerror_handle();\
+ return;\
+}
+
+void dlerror_handle()
+{
+ char *msg;
+ if ((msg = dlerror()) != NULL) {
+ fprintf(stderr, "socket: dlopen failed : %s\n", msg);
+ fflush(stderr);
+ exit(EXIT_FAILURE);
+ }
+}
+
+next_func(bind);
+next_func(close);
+next_func(setsockopt);
+next_func(socket);
+next_func(send);
+next_func(sendto);
+next_func(sendmsg);
+next_func(connect);
+
+void dobind(int sockfd)
+{
+ if (source && sockfd < 1024 && !is_bound[sockfd]) {
+ set_next_bind();
+ if (next_bind(sockfd, source, sockaddr_size)) {
+ perror("failed to bind to ip address");
+ next_close(sockfd);
+ exit(EXIT_FAILURE);
+ }
+ is_bound[sockfd] = 1;
+ }
+}
+
+int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
+{
+ set_next_connect();
+ dobind(sockfd);
+ return next_connect(sockfd, addr, addrlen);
+}
+
+ssize_t send(int sockfd, const void *buf, size_t len, int flags)
+{
+ set_next_send();
+ dobind(sockfd);
+ return next_send(sockfd, buf, len, flags);
+}
+
+ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
+ const struct sockaddr *dest_addr, socklen_t addrlen)
+{
+ set_next_sendto();
+ dobind(sockfd);
+ return next_sendto(sockfd, buf, len, flags, dest_addr, addrlen);
+}
+
+ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags)
+{
+ set_next_sendmsg();
+ dobind(sockfd);
+ return next_sendmsg(sockfd, msg, flags);
+}
+
+int bind (int sockfd, const struct sockaddr *addr, socklen_t addrlen)
+{
+ set_next_bind();
+ if (device && addr->sa_family == AF_PACKET) {
+ ((struct sockaddr_ll*)addr)->sll_ifindex=device;
+ }
+ else if (source && addr->sa_family == AF_INET) {
+ ((struct sockaddr_in*)addr)->sin_addr = source4.sin_addr;
+ }
+#ifdef CONFIG_IPV6
+ else if (source && addr->sa_family == AF_INET6) {
+ ((struct sockaddr_in6*)addr)->sin6_addr = source6.sin6_addr;
+ }
+#endif
+ if (sockfd < 1024)
+ is_bound[sockfd] = 1;
+ return next_bind(sockfd, addr, addrlen);
+}
+
+int close (int sockfd)
+{
+ set_next_close();
+ if (sockfd < 1024)
+ is_bound[sockfd]=0;
+ return next_close(sockfd);
+}
+
+int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen)
+{
+ set_next_setsockopt();
+ if (level == SOL_SOCKET && (optname == SO_MARK || optname == SO_BINDTODEVICE))
+ return 0;
+ return next_setsockopt(sockfd, level, optname, optval, optlen);
+}
+
+int socket(int domain, int type, int protocol)
+{
+ int handle;
+
+ const char *socket_str = getenv("DEVICE");
+ const char *srcip_str = getenv("SRCIP");
+ const char *fwmark_str = getenv("FWMARK");
+ const char *family_str = getenv("FAMILY");
+ const int iface_len = socket_str ? strnlen(socket_str, IFNAMSIZ) : 0;
+ int has_family = family_str && *family_str != 0;
+ int has_srcip = srcip_str && *srcip_str != 0;
+ const int fwmark = fwmark_str ? (int)strtol(fwmark_str, NULL, 0) : 0;
+
+ set_next_close();
+ set_next_socket();
+ set_next_send();
+ set_next_setsockopt();
+ set_next_sendmsg();
+ set_next_sendto();
+ set_next_connect();
+ if(has_family) {
+#ifdef CONFIG_IPV6
+ if(domain == AF_INET && strncmp(family_str,"ipv6",4) == 0)
+ return -1;
+#endif
+ if(domain == AF_INET6 && strncmp(family_str,"ipv4",4) == 0)
+ return -1;
+ }
+
+ if (domain != AF_INET
+#ifdef CONFIG_IPV6
+ && domain != AF_INET6
+#endif
+ ) {
+ return next_socket(domain, type, protocol);
+ }
+
+
+ if (iface_len > 0) {
+ if (iface_len == IFNAMSIZ) {
+ fprintf(stderr,"socket: Too long iface name\n");
+ fflush(stderr);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (has_srcip) {
+ int s;
+ void * addr_buf;
+ if (domain == AF_INET) {
+ addr_buf = &source4.sin_addr;
+ sockaddr_size=sizeof source4;
+ memset(&source4, 0, sockaddr_size);
+ source4.sin_family = domain;
+ source = (struct sockaddr*)&source4;
+ }
+#ifdef CONFIG_IPV6
+ else {
+ addr_buf = &source6.sin6_addr;
+ sockaddr_size=sizeof source6;
+ memset(&source6, 0, sockaddr_size);
+ source6.sin6_family=domain;
+ source = (struct sockaddr*)&source6;
+ }
+#endif
+ s = inet_pton(domain, srcip_str, addr_buf);
+ if (s == 0) {
+ fprintf(stderr, "socket: ip address invalid format for family %s\n",
+ domain == AF_INET ? "AF_INET" : domain == AF_INET6 ?
+ "AF_INET6" : "unknown");
+ return -1;
+ }
+ if (s < 0) {
+ perror("inet_pton");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ handle = next_socket(domain, type, protocol);
+ if (handle == -1 ) {
+ return handle;
+ }
+
+ if (iface_len > 0) {
+ device=if_nametoindex(socket_str);
+ if (next_setsockopt(handle, SOL_SOCKET, SO_BINDTODEVICE,
+ socket_str, iface_len + 1)) {
+ perror("socket: setting interface name failed with error");
+ next_close(handle);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (fwmark > 0) {
+ if (next_setsockopt(handle, SOL_SOCKET, SO_MARK,
+ &fwmark, sizeof fwmark)) {
+ perror("failed setting mark for socket");
+ next_close(handle);
+ exit(EXIT_FAILURE);
+ }
+ }
+ return handle;
+}