Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- * threesome.c -- test how different protocols manage with veth
- *
- * This program creates a pair of veth network devices, configures them,
- * and optionally puts one of the pair into an isolated network space.
- * Then it either pings one side from the other or sends messages over
- * TCP or SCTP likewise. Both IPv4 and IPv6 are supported. You need
- * to run this program with superuser privileges.
- *
- * Compile as: cc -Wall threesome.c -lsctp -o threesome
- *
- * Usage:
- * threesome [-N46i] [-p {ping|tcp|sctp}]
- * [-m <master-address>] [-s <slave-address>]
- * [-P <port>]
- *
- * Options:
- * -N Do not put one of the veth devices
- * to another network space.
- * -4 Use IPv4 (default).
- * -6 Use IPv6.
- * -m <master-address> IP address of veth0. You can only specify
- * IPv4 address, because the IPv6 address is
- * generated by veth at the time both devices
- * are pulled up. The default is 1.2.3.4.
- * -s <slave-address> IP address of the device which is put into
- * a separate namespace. Default is 1.2.3.5.
- * -P <port> The port of the server side.
- * -i By default, we communicate from the master
- * side (eg. we ping the slave from the master).
- * With this flag you can switch the roles.
- */
- /* Include files */
- #include <stdlib.h>
- #include <stdarg.h>
- #include <unistd.h>
- #include <assert.h>
- #include <stdio.h>
- #include <string.h>
- #include <sched.h>
- #include <arpa/inet.h>
- #include <netinet/in.h>
- #include <net/if.h>
- #include <sys/time.h>
- #include <sys/wait.h>
- #include <sys/ioctl.h>
- // Maximum size of an IPv6 address in hexadecimal representation.
- #define IPV6_ADDRLEN (4*8 + 7 + 4 + 1)
- // Either "main", "master" or "slave".
- static const char *Whoami = "main";
- /* Program code */
- // Like popen(3), except that it has an execlp(3)-like interface.
- // NOTE the child is not reaped when @st is closed.
- static FILE *__attribute__((sentinel))
- cmd_pipe(char *prg, ...)
- {
- int childs_stdout[2];
- unsigned n, i;
- va_list args;
- char **argv;
- FILE *st;
- // Make new file descriptor.
- assert(!pipe(childs_stdout));
- assert((st = fdopen(childs_stdout[0], "r")) != NULL);
- if (fork())
- {
- close(childs_stdout[1]);
- return st;
- } else
- {
- fclose(st);
- assert(dup2(childs_stdout[1], STDOUT_FILENO)
- == STDOUT_FILENO);
- }
- // Count the number of parameters.
- va_start(args, prg);
- for (n = 0; va_arg(args, char *); n++)
- ;
- va_end(args);
- // Copy the arguments to argv.
- assert((argv = malloc(sizeof(*argv) * (1+n+1))) != NULL);
- va_start(args, prg);
- argv[0] = prg;
- printf("%6s: %s", Whoami, argv[0]);
- for (i = 1; n > 0; n--, i++)
- { // Print the command line.
- argv[i] = va_arg(args, char *);
- printf(" %s", argv[i]);
- }
- puts(" |");
- argv[i] = NULL;
- va_end(args);
- assert(!execvp(argv[0], argv));
- abort();
- }
- // Just like system(3) but with execlp(3) syntax.
- static void __attribute__((sentinel))
- cmd(char *prg, ...)
- {
- pid_t child;
- if (!(child = fork()))
- {
- unsigned n, i;
- va_list args;
- char **argv;
- // Count the number of parameters.
- va_start(args, prg);
- for (n = 0; va_arg(args, char *); n++)
- ;
- va_end(args);
- // Copy the arguments to argv.
- assert((argv = malloc(sizeof(*argv) * (1+n+1))) != NULL);
- va_start(args, prg);
- argv[0] = prg;
- printf("%6s: %s", Whoami, argv[0]);
- for (i = 1; n > 0; n--, i++)
- { // Print the command line.
- argv[i] = va_arg(args, char *);
- printf(" %s", argv[i]);
- }
- putchar('\n');
- argv[i] = NULL;
- va_end(args);
- assert(!execvp(argv[0], argv));
- } else
- {
- int status;
- // Wait until the child finishes.
- while (waitpid(child, &status, 0) > 0)
- assert(WIFEXITED(status) && WEXITSTATUS(status) == 0);
- }
- }
- // Logging
- static void __attribute__((noreturn, format(printf, 1, 2)))
- error(const char *fmt, ...)
- {
- va_list args;
- fprintf(stderr, "%6s: ", Whoami);
- va_start(args, fmt);
- vfprintf(stderr, fmt, args);
- va_end(args);
- fputc('\n', stderr);
- exit(1);
- }
- static void __attribute__((format(printf, 1, 2)))
- logit(const char *fmt, ...)
- {
- va_list args;
- printf("%6s: ", Whoami);
- va_start(args, fmt);
- vprintf(fmt, args);
- va_end(args);
- putchar('\n');
- }
- static void __attribute__((noreturn))
- usage(const char *prg)
- {
- const char *fname;
- if (!(fname = strrchr(prg, '/')))
- fname = prg;
- else
- fname = prg+1;
- printf("%s [-N] [-46] [-p {ping|tcp|sctp}] [-m <master's-ip>] "
- "[-s <slave's ip>] [-i]\n", fname);
- exit(1);
- }
- // Retrieve the IPv6 address of @iface.
- static void get_ipv6_address(char ip[IPV6_ADDRLEN], const char *iface)
- {
- FILE *st;
- char line[128];
- st = cmd_pipe("ifconfig", iface, NULL);
- while (fgets(line, sizeof(line), st))
- {
- char *p;
- // inet6 addr: fe80::243a:1aff:fe7d:4cf0/64 Scope:Link
- if (sscanf(line, " inet6 addr: %s", ip) < 1)
- continue;
- if ((p = strrchr(ip, '/')) != NULL)
- *p = '\0';
- fclose(st);
- return;
- }
- abort();
- }
- // Populate a sockaddr_in with @ip and @port.
- static void fill_saddr4(struct sockaddr_in *saddr,
- const char *ip, unsigned port)
- {
- memset(saddr, 0, sizeof(*saddr));
- saddr->sin_family = AF_INET;
- assert(inet_pton(AF_INET, ip, &saddr->sin_addr));
- saddr->sin_port = htons(port);
- }
- // Populate a sockaddr_in6 with @ip and @port.
- static void fill_saddr6(int sfd, struct sockaddr_in6 *saddr,
- const char *iface, const char *ip, unsigned port)
- {
- struct ifreq iface_info;
- memset(saddr, 0, sizeof(*saddr));
- saddr->sin6_family = AF_INET6;
- assert(inet_pton(AF_INET6, ip, &saddr->sin6_addr) == 1);
- saddr->sin6_port = htons(port);
- // Bind to @iface once again (for SCTP).
- logit("setting scope id to %s", iface);
- memset(&iface_info, 0, sizeof(iface_info));
- strcpy(iface_info.ifr_name, iface);
- assert(!ioctl(sfd, SIOCGIFINDEX, &iface_info));
- saddr->sin6_scope_id = iface_info.ifr_ifindex;
- }
- // Prepare a socket: create it, set socket options, bind() and listen()
- // if !@is_client.
- static int prepare(int is_master, unsigned ip_version, const char *proto,
- const char *ip, unsigned dst_port, int is_client)
- {
- int sfd, on;
- const char *iface;
- // The purpose of sleeping here is unknown, but if omitted,
- // IPv6 doesn't work properly.
- if (ip_version == 6 && !is_client)
- {
- struct timeval tv;
- gettimeofday(&tv, NULL);
- logit("sleeping for 2 seconds (%lu.%6lu)",
- tv.tv_sec, tv.tv_usec);
- sleep(2);
- gettimeofday(&tv, NULL);
- logit("woken up (%lu.%6lu)", tv.tv_sec, tv.tv_usec);
- }
- if (!strcmp(proto, "ping"))
- return -1;
- iface = is_master ? "veth0" : "veth1";
- // Create the socket.
- logit("socket(%s, SOCK_STREAM, %s)",
- ip_version == 4 ? "PF_INET" : "PF_INET6",
- !strcmp(proto, "tcp") ? "IPPROTO_TCP" : "IPPROTO_SCTP");
- if ((sfd = socket(ip_version == 4 ? PF_INET : PF_INET6, SOCK_STREAM,
- !strcmp(proto, "sctp") ? IPPROTO_SCTP : 0)) < 0)
- error("socket(): %m");
- // veth generates link-local IPv6 addresses, so we must bind
- // to the interface.
- if (ip_version == 6)
- {
- logit("binding socket to %s", iface);
- assert(!setsockopt(sfd, SOL_SOCKET, SO_BINDTODEVICE,
- iface, strlen(iface) + 1));
- }
- if (is_client)
- return sfd;
- // bind()
- on = 1;
- logit("setsockopt(SO_REUSEADDR)");
- assert(!setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)));
- if (ip_version == 4)
- {
- struct sockaddr_in saddr;
- // bind() to @ip:@dst_port.
- logit("bind(%s:%u)", ip, dst_port);
- fill_saddr4(&saddr, ip, dst_port);
- if (bind(sfd, (const struct sockaddr *)&saddr,
- sizeof(saddr)) < 0)
- error("bind(%s:%u): %m", ip, dst_port);
- } else
- {
- char ip6[IPV6_ADDRLEN];
- struct sockaddr_in6 saddr;
- // Bind to the IPv6 address of @iface and @dst_port.
- get_ipv6_address(ip6, iface);
- fill_saddr6(sfd, &saddr, iface, ip6, dst_port);
- logit("bind([%s]:%u)", ip6, dst_port);
- if (bind(sfd, (const struct sockaddr *)&saddr,
- sizeof(saddr)) < 0)
- error("bind([%s]:%u): %m", ip6, dst_port);
- }
- logit("listen()");
- assert(!listen(sfd, 1));
- return sfd;
- }
- // connect() if necessary and communicate with @dst_ip and @dst_port.
- static void __attribute__((noreturn))
- run(int is_master, int sfd, unsigned ip_version, const char *proto,
- const char *dst_ip, unsigned dst_port, int is_client)
- {
- const char *iface;
- iface = is_master ? "veth0" : "veth1";
- if (!is_client)
- { // Server role
- if (!strcmp(proto, "ping"))
- // We'll be pinged, nothing to do anymore.
- for (;;)
- pause();
- // TCP or SCTP
- for (;;)
- {
- int cfd;
- ssize_t len;
- char line[128];
- // Read the socket line by line and print it.
- printf("%6s: accept()...", Whoami);
- fflush(stdout);
- assert((cfd = accept(sfd, NULL, NULL)) >= 0);
- putchar('\n');
- while ((len = read(cfd, line, sizeof(line))) > 0)
- {
- line[len] = '\0';
- printf("%s <- %s\n", Whoami, line);
- }
- close(cfd);
- }
- } else if (!strcmp(proto, "ping"))
- { // ping @dst_ip.
- if (ip_version == 4)
- cmd("ping", dst_ip, NULL);
- else
- cmd("ping6", "-I", iface, dst_ip, NULL);
- abort();
- } else
- { // Client role TCP/SCTP
- static char const *const alpha[] =
- {
- "alpha", "beta", "gamma", "delta", "epsilon",
- "zeta", "eta", "theta", "iota", "kappa", "lambda",
- "mu", "nu", "xi", "omicron", "pi", "rho", "sigma",
- "tau", "upsilon", "phi", "chi", "psi", "omega", NULL
- };
- const char *const *letter;
- // connect()
- if (ip_version == 4)
- {
- struct sockaddr_in saddr;
- fill_saddr4(&saddr, dst_ip, dst_port);
- printf("%6s: Connecting to %s:%u...",
- Whoami, dst_ip, dst_port);
- fflush(stdout);
- assert(!connect(sfd, (const struct sockaddr *)&saddr,
- sizeof(saddr)));
- } else
- {
- struct sockaddr_in6 saddr;
- fill_saddr6(sfd, &saddr, iface, dst_ip, dst_port);
- printf("%6s: Connecting to %s:%u...",
- Whoami, dst_ip, dst_port);
- fflush(stdout);
- assert(!connect(sfd, (const struct sockaddr *)&saddr,
- sizeof(saddr)));
- }
- putchar('\n');
- // Send @alpha element by element.
- letter = &alpha[0];
- for (;;)
- {
- printf("%s -> %s", Whoami, *letter);
- assert(write(sfd, *letter, strlen(*letter))
- > 0);
- sleep(1);
- letter++;
- if (!*letter)
- letter = &alpha[0];
- }
- }
- }
- /* The main function */
- int main(int argc, char *argv[])
- {
- FILE *st;
- char c, line[16];
- int optchar, m2s[2], s2m[2];
- int opt_master_connects, opt_new_namespace;
- unsigned opt_ip_version, opt_server_port;
- const char *opt_proto, *opt_master_ip, *opt_slave_ip;
- // Parse the command line.
- opt_ip_version = 4;
- opt_proto = "tcp";
- opt_master_ip = "1.2.3.4";
- opt_slave_ip = "1.2.3.5";
- opt_server_port = 2222;
- opt_master_connects = 1;
- opt_new_namespace = 1;
- while ((optchar = getopt(argc, argv, "N46p:m:s:P:i")) != EOF)
- switch (optchar)
- {
- case 'N':
- opt_new_namespace = 0;
- break;
- case '4':
- opt_ip_version = 4;
- break;
- case '6':
- opt_ip_version = 6;
- break;
- case 'p':
- opt_proto = optarg;
- if (strcmp(opt_proto, "ping")
- && strcmp(opt_proto, "tcp")
- && strcmp(opt_proto, "sctp"))
- usage(argv[0]);
- break;
- case 'm':
- opt_master_ip = optarg;
- break;
- case 's':
- opt_slave_ip = optarg;
- break;
- case 'P':
- opt_server_port = atoi(optarg);
- break;
- case 'i':
- opt_master_connects = !opt_master_connects;
- break;
- default:
- usage(argv[0]);
- };
- setvbuf(stdout, NULL, _IOLBF, 0);
- if (geteuid() != 0)
- error("you need to be superuser to use this program.");
- logit("configuring veth0");
- cmd("modprobe", "veth", NULL);
- // Clean up: delete all existing veth devices.
- st = cmd_pipe("ip", "link", NULL);
- while (fgets(line, sizeof(line), st))
- {
- char dev[8];
- unsigned vethn;
- if (sscanf(line, "%*u: veth%u:", &vethn) != 1 || vethn % 2)
- continue;
- snprintf(dev, sizeof(dev), "veth%u", vethn);
- cmd("ip", "link", "del", dev, NULL);
- }
- fclose(st);
- // Prepare veth0.
- cmd("ip", "link", "add", "type", "veth", NULL);
- cmd("ip", "addr", "add", opt_master_ip, "dev", "veth0", NULL);
- cmd("ifconfig", "veth0", "up", NULL);
- // Add routes to the destination addresses.
- cmd("ip", "route", "add", "to", opt_slave_ip, "dev", "veth0", NULL);
- logit("veth0 configured");
- // Fork to master (outside the namespace) and slave (contained
- // in a network namespace) branches.
- assert(!pipe(s2m));
- assert(!pipe(m2s));
- if (!fork())
- { // Master
- int sfd;
- char slave_ip6[IPV6_ADDRLEN];
- Whoami = "master";
- close(m2s[0]);
- close(s2m[1]);
- if (opt_new_namespace)
- {
- char ppid[8];
- // Wait until the slave has unshare()d,
- // then give it veth1.
- logit("waiting for slave to unshare()");
- read(s2m[0], &c, 1);
- logit("slave has unshared, giving veth1 away");
- snprintf(ppid, sizeof(ppid), "%u", getppid());
- cmd("ip", "link", "set", "veth1", "netns", ppid,
- NULL);
- write(m2s[1], "x", 1);
- logit("veth1 has been given away");
- // We've transferred veth1 to the slave.
- }
- // Wait for slave to configure, then we can acquire
- // the veth-generated IPv6 addresses.
- logit("waiting for slave to configure veth1");
- read(s2m[0], &c, 1);
- logit("veth1 configured, preparing socket");
- sfd = prepare(1, opt_ip_version, opt_proto,
- opt_master_ip, opt_server_port,
- opt_master_connects);
- logit("prepared socket");
- if (opt_master_connects)
- { // Wait until slave prepares.
- if (opt_ip_version == 4)
- {
- logit("waiting for slave to prepare");
- read(s2m[0], &c, 1);
- } else
- { // Retrieve the slave's IPv6 address.
- logit("waiting for slave's IPv6 address");
- assert(read(s2m[0], slave_ip6,
- sizeof(slave_ip6))
- == sizeof(slave_ip6));
- }
- } else if (opt_ip_version == 6)
- { // Send out IPv6 address to the slave.
- char master_ip6[IPV6_ADDRLEN];
- logit("sending our IPv6 address to slave");
- get_ipv6_address(master_ip6, "veth0");
- write(m2s[1], master_ip6, sizeof(master_ip6));
- } else
- {
- logit("informing slave that we're prepared");
- write(m2s[1], "x", 1);
- }
- logit("running...");
- run(1, sfd, opt_ip_version, opt_proto,
- opt_ip_version == 4 ? opt_slave_ip : slave_ip6,
- opt_server_port, opt_master_connects);
- } else
- { // Slave
- int sfd;
- char master_ip6[IPV6_ADDRLEN];
- Whoami = "slave";
- close(m2s[1]);
- close(s2m[0]);
- // Let's have our own network space.
- if (opt_new_namespace)
- {
- logit("going into new network space");
- assert(!unshare(CLONE_NEWNET));
- // We've unshare()d.
- logit("residing in new network space, waiting for "
- "master to give veth1 to us");
- write(s2m[1], "x", 1);
- read(m2s[0], &c, 1);
- // We've got veth1.
- }
- // Configure veth1.
- logit("configuring veth1");
- cmd("ifconfig", "lo", "up", NULL);
- cmd("ifconfig", "veth1", "up", NULL);
- cmd("ip", "route", "add", "to", opt_master_ip,
- "dev", "veth1", NULL);
- cmd("ip", "addr", "add", opt_slave_ip, "dev", "veth1", NULL);
- logit("veth1 configured");
- write(s2m[1], "x", 1);
- // Finished configuration.
- logit("preparing, socket");
- sfd = prepare(0, opt_ip_version, opt_proto,
- opt_slave_ip, opt_server_port,
- !opt_master_connects);
- logit("prepared socket");
- if (opt_master_connects)
- {
- if (opt_ip_version == 6)
- { // Send our IPv6 address to the master.
- char ip6[IPV6_ADDRLEN];
- logit("sending master our IPv6 address");
- get_ipv6_address(ip6, "veth1");
- write(s2m[1], ip6, sizeof(ip6));
- } else
- {
- logit("informing master that we're prepared");
- write(s2m[1], "x", 1);
- }
- } else if (opt_ip_version == 6)
- {
- // Get the master's IPv6 address.
- logit("waiting for master's IPv6 address");
- assert(read(m2s[0], master_ip6, sizeof(master_ip6))
- == sizeof(master_ip6));
- } else
- {
- logit("waiting for master to prepare");
- read(m2s[0], &c, 1);
- }
- logit("running...");
- run(0, sfd, opt_ip_version, opt_proto,
- opt_ip_version == 4 ? opt_master_ip : master_ip6,
- opt_server_port, !opt_master_connects);
- }
- /* Not reached */
- }
- /* threesome.c */
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement