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>
255 lines
6.6 KiB
C
255 lines
6.6 KiB
C
// 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;
|
|
}
|