diff options
| author | Aaron Goodman <aaronjg@stanford.edu> | 2020-10-11 18:37:25 -0400 |
|---|---|---|
| committer | Aaron Goodman <aaronjg@stanford.edu> | 2020-10-16 09:54:48 -0400 |
| commit | bbbc6127abf132a92cc73dd23fa88409dc2b9abd (patch) | |
| tree | b0e0de516771222523a601f4cd6c1063e30bacb7 /net/mwan3/src | |
| parent | fb4a2d99eff3af53d4eea2db36d270a140bde6bf (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.c | 255 |
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; +} |
