diff --git a/.gitignore b/.gitignore index af9db6be..359ce90d 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ serval.c /tfw_createfile /fakeradio /config_test +/simulator *.so test.*.log testlog diff --git a/Makefile.in b/Makefile.in index c45c1414..9162b3df 100644 --- a/Makefile.in +++ b/Makefile.in @@ -29,6 +29,7 @@ SERVALD_OBJS= $(SERVALD_SRCS:.c=.o) SERVAL_DAEMON_OBJS= $(SERVAL_DAEMON_SOURCES:.c=.o) MONITOR_CLIENT_OBJS= $(MONITOR_CLIENT_SRCS:.c=.o) MDP_CLIENT_OBJS= $(MDP_CLIENT_SRCS:.c=.o) +SIMULATOR_OBJS= $(SIMULATOR_SOURCES:.c=.o) LDFLAGS=@LDFLAGS@ @LIBS@ @PTHREAD_LIBS@ @@ -58,7 +59,7 @@ DEFS= @DEFS@ all: servald libmonitorclient.so libmonitorclient.a test -test: tfw_createfile directory_service fakeradio config_test +test: tfw_createfile directory_service fakeradio config_test simulator sqlite-amalgamation-3070900/sqlite3.o: sqlite-amalgamation-3070900/sqlite3.c @echo CC $< @@ -83,6 +84,7 @@ configure: $(wildcard configure.in) $(SERVAL_DAEMON_OBJS): $(HDRS) $(MONITOR_CLIENT_OBJS): $(HDRS) $(MDP_CLIENT_OBJS): $(HDRS) +$(SIMULATOR_OBJS): $(HDRS) servald: $(SERVALD_OBJS) version.o @echo LINK $@ @@ -100,6 +102,10 @@ fakeradio: fakeradio.o @echo LINK $@ @$(CC) $(CFLAGS) -Wall -o $@ fakeradio.o +simulator: $(SIMULATOR_OBJS) + @echo LINK $@ + @$(CC) $(CFLAGS) -Wall -o $@ $(SIMULATOR_OBJS) $(LDFLAGS) + config_test: config_test.o conf_om.o conf_schema.o conf_parse.o str.o strbuf.o strbuf_helpers.o mem.o dataformats.o net.o log_util.o @echo LINK $@ @$(CC) $(CFLAGS) -Wall -o $@ config_test.o conf_om.o conf_schema.o conf_parse.o str.o strbuf.o strbuf_helpers.o mem.o dataformats.o net.o log_util.o $(LDFLAGS) diff --git a/main.c b/main.c index a95e8a81..ea4c93d3 100644 --- a/main.c +++ b/main.c @@ -68,21 +68,3 @@ static void crash_handler(int signal) exit(-signal); } -#if 0 -#include -#define MAX_DEPTH 64 -int printBackTrace() -{ - int i,depth=0; - void *functions[MAX_DEPTH]; - char **function_names; - - depth = backtrace (functions, MAX_DEPTH); - function_names = backtrace_symbols (functions, depth); - - for(i=0;ilocal.sun_path)) == NULL) { - WARNF_perror("opendir(%s)", alloca_str_toprint(address->local.sun_path)); + if ((dir = opendir(folder)) == NULL) { + WARNF_perror("opendir(%s)", alloca_str_toprint(folder)); return -1; } while ((dp = readdir(dir)) != NULL) { - struct socket_address addr; - - strbuf d = strbuf_local(addr.local.sun_path, sizeof addr.local.sun_path); - strbuf_path_join(d, address->local.sun_path, dp->d_name, NULL); - if (strbuf_overrun(d)){ - WHYF("interface file name overrun: %s", alloca_str_toprint(strbuf_str(d))); - continue; - } - - struct stat st; - if (lstat(addr.local.sun_path, &st)) { - WARNF_perror("stat(%s)", alloca_str_toprint(addr.local.sun_path)); - continue; - } - - if (S_ISSOCK(st.st_mode)){ - addr.local.sun_family = AF_UNIX; - addr.addrlen = sizeof(addr.local.sun_family) + strlen(addr.local.sun_path)+1; - - ssize_t sent = sendto(fd, bytes, len, 0, - &addr.addr, addr.addrlen); - if (sent == -1) - WHYF_perror("sendto(%d, %zu, %s)", fd, len, alloca_socket_address(&addr)); - } + send_local_packet(fd, bytes, len, folder, dp->d_name); } closedir(dir); return 0; @@ -915,7 +920,7 @@ int overlay_broadcast_ensemble(struct network_destination *destination, struct o && !destination->unicast){ // find all sockets in this folder and send to them send_local_broadcast(interface->alarm.poll.fd, - bytes, (size_t)len, &destination->address); + bytes, (size_t)len, destination->address.local.sun_path); }else{ ssize_t sent = sendto(interface->alarm.poll.fd, bytes, (size_t)len, 0, diff --git a/simulator.c b/simulator.c new file mode 100644 index 00000000..9bf60718 --- /dev/null +++ b/simulator.c @@ -0,0 +1,689 @@ +/* +Copyright (C) 2014 Serval Project Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +/* + Network simulator +*/ + +#include +#include + +#include "mem.h" +#include "socket.h" +#include "fdqueue.h" +#include "str.h" +#include "strbuf.h" +#include "strbuf_helpers.h" +#include "conf.h" +#include "net.h" +#include "console.h" +#include "limit.h" + +#define MTU 1600 +struct peer; + +/* + * we want to support simulating common wired, wireless & UHF network topologies + * eg layer 2 adhoc mesh, switched ethernet, AP's, openWRT's + * With topology changes over time + * + * Some kind of DSL will probably be required + * + * Node{ + * name "foo" + * type servald / switch ... + * Adapter{ + * socket "path" + * type adhoc / ap / client / ethernet / uhf + * queue_length 10 + * drop broadcast % + * drop unicast % + * } + * } + */ + +struct packet { + struct packet *_next; + time_ms_t recv_time; + struct peer *destination; + size_t len; + uint8_t tdma_order; + unsigned char buff[MTU]; +}; + +struct peer { + struct sched_ent alarm; + struct peer *_next; + struct network *network; + struct socket_address addr; + int packet_count; + int max_packets; + struct packet *_head, **_tail; + uint32_t rx_count; + uint32_t tx_count; +}; + +struct network { + struct sched_ent alarm; + char name[16]; + char path[256]; + struct limit_state limit; + long latency; + char drop_packets; + char drop_broadcast; + char drop_unicast; + uint8_t up; + uint8_t echo; + uint8_t drop_broadcast_collisions; // on wifi collisions cause dropped packets + uint32_t rx_count; + uint32_t tx_count; + struct peer *peer_list; + struct network *_next; +}; + +struct profile_total broadcast_stats= { + .name="sock_alarm" +}; +struct profile_total unicast_stats= { + .name="unicast_alarm" +}; + +struct command_state *stdin_state; + +struct network *networks=NULL; +static void unicast_alarm(struct sched_ent *alarm); + +const struct __sourceloc __whence = __NOWHERE__; + +static const char *_trimbuildpath(const char *path) +{ + /* Remove common path prefix */ + int lastsep = 0; + int i; + for (i = 0; __FILE__[i] && path[i]; ++i) { + if (i && path[i - 1] == '/') + lastsep = i; + if (__FILE__[i] != path[i]) + break; + } + return &path[lastsep]; +} + +void cf_on_config_change() +{ +} + +void logMessage(int level, struct __sourceloc whence, const char *fmt, ...) +{ + const char *levelstr = "UNKWN:"; + switch (level) { + case LOG_LEVEL_FATAL: + levelstr = "FATAL:"; + break; + case LOG_LEVEL_ERROR: + levelstr = "ERROR:"; + break; + case LOG_LEVEL_INFO: + levelstr = "INFO:"; + break; + case LOG_LEVEL_WARN: + levelstr = "WARN:"; + break; + case LOG_LEVEL_DEBUG: + levelstr = "DEBUG:"; + break; + } + + struct timeval tv; + struct tm tm; + gettimeofday(&tv, NULL); + localtime_r(&tv.tv_sec, &tm); + char buf[50]; + strftime(buf, sizeof buf, "%T", &tm); + fprintf(stderr, "%s.%03u ", buf, (unsigned int)tv.tv_usec / 1000); + + fprintf(stderr, "%s ", levelstr); + if (whence.file) { + fprintf(stderr, "%s", _trimbuildpath(whence.file)); + if (whence.line) + fprintf(stderr, ":%u", whence.line); + if (whence.function) + fprintf(stderr, ":%s()", whence.function); + fputc(' ', stderr); + } else if (whence.function) { + fprintf(stderr, "%s() ", whence.function); + } + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); +} + +void logFlush() +{ +} +void logConfigChanged() +{ +} + +int serverMode=0; + +static void recv_packet(int fd, struct network *network, struct peer *destination) +{ + struct socket_address addr; + struct packet *packet=emalloc_zero(sizeof(struct packet)); + if (!packet) + return; + network->rx_count++; + packet->recv_time = gettime_ms(); + packet->destination = destination; + struct iovec iov[]= { + { + .iov_base = (void*)&packet->buff, + .iov_len = sizeof(packet->buff) + } + }; + struct msghdr hdr= { + .msg_name=(void *)&addr.addr, + .msg_namelen=sizeof(addr.store), + .msg_iov=iov, + .msg_iovlen=1, + }; + ssize_t ret = recvmsg(fd, &hdr, 0); + if (ret==-1) { + free(packet); + WHYF_perror("recvmsg(%d,...)", fd); + return; + } + + addr.addrlen = hdr.msg_namelen; + packet->len = ret; + packet->tdma_order = rand()&31; + + struct peer *peer = network->peer_list; + while(peer) { + if (cmp_sockaddr(&addr, &peer->addr)==0) + break; + peer=peer->_next; + } + if (!peer) { + DEBUGF("New peer %s", alloca_socket_address(&addr)); + struct socket_address unicast_addr; + unicast_addr.local.sun_family=AF_UNIX; + strbuf d = strbuf_local(unicast_addr.local.sun_path, sizeof unicast_addr.local.sun_path); + static unsigned peerid=0; + strbuf_sprintf(d, "%s/peer%d", network->path, peerid++); + if (strbuf_overrun(d)) { + WHY("Path too long"); + free(packet); + return; + } + + unicast_addr.addrlen=sizeof unicast_addr.local.sun_family + strlen(unicast_addr.local.sun_path) + 1; + + peer = emalloc_zero(sizeof(struct peer)); + if (!peer) { + free(packet); + return; + } + peer->alarm.poll.fd=esocket(AF_UNIX, SOCK_DGRAM, 0); + if (peer->alarm.poll.fd==-1) { + free(packet); + free(peer); + return; + } + if (socket_bind(peer->alarm.poll.fd, &unicast_addr)==-1) { + free(packet); + free(peer); + return; + } + set_nonblock(peer->alarm.poll.fd); + peer->alarm.function=unicast_alarm; + peer->alarm.poll.events=POLLIN; + peer->alarm.context = peer; + peer->network = network; + peer->addr = addr; + peer->_next = network->peer_list; + peer->_tail = &peer->_head; + peer->max_packets = 100; + peer->alarm.stats=&unicast_stats; + watch(&peer->alarm); + network->peer_list = peer; + } + + peer->tx_count++; + + // drop packets if the network is "down" or the peer queue is full + if (!network->up || peer->packet_count >= peer->max_packets) { + free(packet); + return; + } + + *peer->_tail = packet; + peer->_tail = &packet->_next; + peer->packet_count++; + time_ms_t allowed = limit_next_allowed(&network->limit); + if (allowed < packet->recv_time + network->latency) + allowed = packet->recv_time + network->latency; + if (!is_scheduled(&network->alarm) || allowed < network->alarm.alarm){ + unschedule(&network->alarm); + network->alarm.alarm = allowed; + network->alarm.deadline = network->alarm.alarm; + schedule(&network->alarm); + } +} + +static void unicast_alarm(struct sched_ent *alarm) +{ + struct peer *peer = (struct peer*)alarm->context; + + if (alarm->poll.revents & POLLIN) { + recv_packet(alarm->poll.fd, peer->network, peer); + } +} + +static int should_drop(struct network *network, struct packet *packet){ + if (network->drop_packets>=100) + return 1; + if (packet->destination){ + if (network->drop_unicast) + return 1; + }else{ + if (network->drop_broadcast) + return 1; + } + if (network->drop_packets <= 0) + return 0; + if (rand()%100 >= network->drop_packets) + return 0; + return 1; +} + +static void sock_alarm(struct sched_ent *alarm) +{ + struct network *network = (struct network*)alarm->context; + + if (alarm->poll.revents & POLLIN) { + recv_packet(alarm->poll.fd, network, NULL); + } + + if (alarm->poll.revents == 0) { + time_ms_t allowed = limit_next_allowed(&network->limit); + time_ms_t now = gettime_ms(); + if (allowed > now){ + alarm->deadline = alarm->alarm = allowed; + schedule(alarm); + return; + } + + uint8_t tdma_order=127; + unsigned tdma_count=0; + struct packet *packet=NULL; + struct peer *sender=NULL; + + // what's the best TDMA value? + { + struct peer *peer = network->peer_list; + while(peer){ + struct packet *p = peer->_head; + if (p && p->recv_time + network->latency <= now){ + if (tdma_order > p->tdma_order){ + tdma_order=p->tdma_order; + tdma_count=1; + packet = p; + sender = peer; + }else if(tdma_order==p->tdma_order){ + tdma_count++; + packet = NULL; + sender = NULL; + } + } + peer = peer->_next; + } + } + + if (tdma_count!=0){ + limit_is_allowed(&network->limit); + + if (packet && tdma_count==1 && should_drop(network, packet)==0){ + // deliver the packet + struct iovec iov[]= { + { + .iov_base = (void*)&packet->buff, + .iov_len = packet->len + } + }; + struct msghdr hdr= { + .msg_iov=iov, + .msg_iovlen=1, + }; + + network->tx_count++; + struct peer *peer = network->peer_list; + while(peer) { + if ((packet->destination == peer || !packet->destination) + && (network->echo || peer !=sender)) { + hdr.msg_name=(void *)&peer->addr.addr; + hdr.msg_namelen=peer->addr.addrlen; + // failure isn't fatal... + if (sendmsg(sender->alarm.poll.fd, &hdr, 0)==-1) + WARN_perror("sendmsg()"); + peer->rx_count++; + } + peer = peer->_next; + } + } + + if (tdma_count>1){ + // collision! + struct peer *peer = network->peer_list; + while(peer){ + struct packet *p = peer->_head; + if (p + && p->recv_time + network->latency <= now + && tdma_order==p->tdma_order){ + if (!p->destination && network->drop_broadcast_collisions){ + if (p==packet) // NOOP? + packet = NULL; + peer->_head = p->_next; + if (!peer->_head) + peer->_tail = &peer->_head; + peer->packet_count --; + free(p); + }else{ + p->tdma_order = rand()&31; + } + } + peer = peer->_next; + } + } + + // free the sent packet + if (sender && packet){ + sender->_head = packet->_next; + if (!sender->_head) + sender->_tail = &sender->_head; + sender->packet_count --; + free(packet); + } + } + + // when is the next packet allowed? + { + time_ms_t next = TIME_MS_NEVER_WILL; + struct peer *peer = network->peer_list; + while(peer){ + struct packet *p = peer->_head; + if (p && next > p->recv_time + network->latency){ + next = p->recv_time + network->latency; + } + peer = peer->_next; + } + time_ms_t allowed = limit_next_allowed(&network->limit); + if (next < allowed) + next = allowed; + alarm->deadline = alarm->alarm = next; + schedule(alarm); + } + } +} + +void signal_handler(int UNUSED(signal)) +{ + command_close(stdin_state); +} + +static void crash_handler(int signal) +{ + LOGF(LOG_LEVEL_FATAL, "Caught signal %s", alloca_signal_name(signal)); + dump_stack(LOG_LEVEL_FATAL); +// TODO Move logBackTrace to log utils? +// BACKTRACE; + // Now die of the same signal, so that our exit status reflects the cause. + INFOF("Re-sending signal %d to self", signal); + kill(getpid(), signal); + // If that didn't work, then die normally. + INFOF("exit(%d)", -signal); + exit(-signal); +} + +static struct network *find_network(const char *name) +{ + struct network *n = networks; + while(n) { + if (strcasecmp(name, n->name)==0) + return n; + n=n->_next; + } + WHYF("Network %s not found\n", n->name); + return NULL; +} + +static int console_create(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) +{ + const char *name, *path; + if ( cli_arg(parsed, "name", &name, NULL, NULL) == -1 + || cli_arg(parsed, "path", &path, NULL, NULL) == -1) + return -1; + + struct socket_address addr; + addr.local.sun_family=AF_UNIX; + + strbuf b = strbuf_local(addr.local.sun_path, sizeof addr.local.sun_path); + strbuf_path_join(b, path, "broadcast", NULL); + if (strbuf_overrun(b)) + return WHY("Path too long"); + + addr.addrlen=sizeof addr.local.sun_family + strlen(addr.local.sun_path) + 1; + int fd = esocket(AF_UNIX, SOCK_DGRAM, 0); + if (fd==-1) + return -1; + if (socket_bind(fd, &addr)==-1) { + close(fd); + return -1; + } + set_nonblock(fd); + + struct network *n = emalloc_zero(sizeof(struct network)); + if (!n) + return -1; + + strbuf_init(b, n->name, sizeof n->name); + strbuf_puts(b, name); + if (strbuf_overrun(b)) { + socket_unlink_close(fd); + free(n); + return WHY("Name is too long"); + } + strbuf_init(b, n->path, sizeof n->path); + strbuf_puts(b, path); + if (strbuf_overrun(b)) { + socket_unlink_close(fd); + free(n); + return WHY("Path is too long"); + } + + n->_next = networks; + networks = n; + + limit_init(&n->limit, 0); + n->alarm.poll.fd = fd; + n->alarm.function=sock_alarm; + n->alarm.poll.events=POLLIN; + n->alarm.stats=&broadcast_stats; + n->alarm.context=n; + watch(&n->alarm); + + INFOF("Created socket %s for network %s", alloca_socket_address(&addr), name); + return 0; +} + +static int console_variable(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) +{ + const char *name; + if (cli_arg(parsed, "name", &name, NULL, NULL) == -1) + return -1; + struct network *n = find_network(name); + if (!n) + return -1; + + unsigned i; + for (i = 2; i+1 < parsed->argc; i+=2) { + const char *arg = parsed->args[i]; + const char *value = parsed->args[i+1]; + + if (strcmp(arg, "latency") == 0) { + n->latency = atol(value); + }else if (strcmp(arg, "echo") == 0) { + n->echo = atoi(value) != 0; + }else if (strcmp(arg, "rate") == 0) { + uint32_t rate = atoi(value); + limit_init(&n->limit, rate); + }else if (strcmp(arg, "drop_packets") == 0){ + n->drop_packets= atol(value); + }else if (strcmp(arg, "drop_broadcast") == 0){ + n->drop_broadcast=atol(value)!=0; + }else if (strcmp(arg, "drop_unicast") == 0){ + n->drop_unicast=atol(value)!=0; + }else + return WHYF("Unknown variable %s", arg); + } + return 0; +} + +static int console_up(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) +{ + unsigned i; + for (i = 1; i < parsed->argc; i++) { + struct network *n = find_network(parsed->args[i]); + if (!n) + return -1; + + n->up = 1; + INFOF("Network %s is now up", n->name); + DEBUGF("Minimum latency %dms", (int)n->latency); + DEBUGF("Will drop %d%% of packets", n->drop_packets); + DEBUGF("Will %s broadcast packets", n->drop_broadcast?"drop":"allow"); + DEBUGF("Will %s unicast packets", n->drop_unicast?"drop":"allow"); + DEBUGF("Allowing a maximum of %d packets every %"PRId64"ms", + n->limit.burst_size, + n->limit.burst_length); + } + return 0; +} + +static int console_down(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) +{ + unsigned i; + for (i = 1; i < parsed->argc; i++) { + struct network *n = find_network(parsed->args[i]); + if (!n) + return -1; + + // free any pending packets + struct peer *peer = n->peer_list; + while(peer) { + struct packet *p = peer->_head; + while(p) { + struct packet *f = p; + p = p->_next; + free(f); + } + peer->_head=NULL; + peer=peer->_next; + } + + n->up = 0; + unschedule(&n->alarm); + + INFOF("Network %s is now down", n->name); + } + return 0; +} + +static int console_quit(const struct cli_parsed *UNUSED(parsed), struct cli_context *UNUSED(context)) +{ + command_close(stdin_state); + return 0; +} + +struct cli_schema console_commands[]= { + {console_create,{"create","","",NULL},0,"Create a named network"}, + {console_variable,{"set", "", "","","...",NULL},0,"Set a property of the network"}, + {console_up,{"up", "", "...", NULL},0,"Bring a network up"}, + {console_down,{"down","","...",NULL},0,"Bring a network down"}, + {console_quit,{"quit",NULL},0,"Exit the simulator"}, + {NULL, {NULL, NULL, NULL}, 0, NULL}, +}; + +int main() +{ + cf_init(); + + /* Catch crash signals so that we can log a backtrace before expiring. */ + struct sigaction sig; + sig.sa_handler = crash_handler; + sigemptyset(&sig.sa_mask); // Don't block any signals during handler + sig.sa_flags = SA_NODEFER | SA_RESETHAND; // So the signal handler can kill the process by re-sending the same signal to itself + sigaction(SIGSEGV, &sig, NULL); + sigaction(SIGFPE, &sig, NULL); + sigaction(SIGILL, &sig, NULL); + sigaction(SIGBUS, &sig, NULL); + sigaction(SIGABRT, &sig, NULL); + + /* Catch SIGHUP, SIGINT so we can shutdown gracefully */ + sig.sa_handler = signal_handler; + sigemptyset(&sig.sa_mask); + sigaddset(&sig.sa_mask, SIGHUP); + sigaddset(&sig.sa_mask, SIGINT); + sig.sa_flags = 0; + sigaction(SIGHUP, &sig, NULL); + sigaction(SIGINT, &sig, NULL); + + stdin_state = command_register(console_commands, STDIN_FILENO); + + while(!is_command_closed(stdin_state) && fd_poll()) + ; + + INFO("Shutting down"); + command_free(stdin_state); + + { + struct network *n = networks; + while(n) { + DEBUGF("Closing network %s, TX %d RX %d", n->name, n->tx_count, n->rx_count); + unwatch(&n->alarm); + socket_unlink_close(n->alarm.poll.fd); + struct peer *p = n->peer_list; + while(p) { + DEBUGF("Closing peer proxy socket, TX %d RX %d", p->tx_count, p->rx_count); + unwatch(&p->alarm); + socket_unlink_close(p->alarm.poll.fd); + struct peer *f = p; + p=p->_next; + free(f); + } + struct network *f=n; + n=n->_next; + free(f); + } + } + + return 0; +} diff --git a/simulator.h b/simulator.h new file mode 100644 index 00000000..0f8511f0 --- /dev/null +++ b/simulator.h @@ -0,0 +1,4 @@ + +struct { + +} \ No newline at end of file diff --git a/sourcefiles.mk b/sourcefiles.mk index 497ab3cc..d104ac02 100644 --- a/sourcefiles.mk +++ b/sourcefiles.mk @@ -97,3 +97,9 @@ SERVAL_DAEMON_SOURCES = \ fec-3.0.1/encode_rs_8.c \ fec-3.0.1/init_rs_char.c \ context1.c + +SIMULATOR_SOURCES = cli.c conf.c conf_om.c conf_parse.c conf_schema.c \ + console.c simulator.c socket.c fdqueue.c performance_timing.c \ + str.c os.c mem.c net.c log_util.c strbuf.c strbuf_helpers.c \ + dataformats.c xprintf.c instance.c limit.c version.c + diff --git a/tests/routing b/tests/routing index 9fd41203..57364c67 100755 --- a/tests/routing +++ b/tests/routing @@ -308,29 +308,60 @@ teardown_simulate_extender() { tfw_cat "$SERVALD_VAR/radioerr" } +_simulator() { + # TODO timeout & failure reporting? + executeOk --error-on-fail $servald_build_root/simulator <$SIM_IN + tfw_cat --stdout --stderr + rm $SIM_IN +} +start_simulator() { + SIM_IN="$PWD/SIM_IN" + mkfifo "$SIM_IN" + exec 8<>"$SIM_IN" # stop fifo from blocking + fork %simulator _simulator +} +simulator_command() { + tfw_log "$@" + assert_fork_is_running %simulator + echo "$@" >>"$SIM_IN" +} +simulator_quit() { + simulator_command quit + fork_wait %simulator +} + doc_multiple_nodes="Multiple nodes on one link" setup_multiple_nodes() { setup_servald assert_no_servald_processes foreach_instance +A +B +C +D create_single_identity foreach_instance +A +B +C +D add_servald_interface 1 + start_simulator + simulator_command create "net" "$SERVALD_VAR/dummy1/" foreach_instance +A +B +C +D start_servald_server } test_multiple_nodes() { - wait_until path_exists +A +B - wait_until path_exists +A +C - wait_until path_exists +A +D - wait_until path_exists +B +A - wait_until path_exists +C +A - wait_until path_exists +D +A + simulator_command set "net" "latency" "100" + simulator_command up "net" + wait_until --timeout=10 path_exists +A +B + wait_until --timeout=5 path_exists +A +C + wait_until --timeout=5 path_exists +A +D + wait_until --timeout=5 path_exists +B +A + wait_until --timeout=5 path_exists +C +A + wait_until --timeout=5 path_exists +D +A set_instance +A executeOk_servald mdp ping --timeout=3 $SIDB 1 tfw_cat --stdout --stderr + simulator_command set "net" "latency" "150" executeOk_servald mdp ping --timeout=3 $SIDC 1 tfw_cat --stdout --stderr + simulator_command set "net" "latency" "200" executeOk_servald mdp ping --timeout=3 $SIDD 1 tfw_cat --stdout --stderr } +finally_multiple_nodes() { + simulator_quit +} doc_scan="Network scan with isolated clients" setup_scan() { @@ -410,12 +441,14 @@ setup_broadcast_only() { setup_servald assert_no_servald_processes foreach_instance +A +B create_single_identity - foreach_instance +A +B add_servald_interface --file 1 - foreach_instance +A +B \ - executeOk_servald config set interfaces.1.drop_unicasts 1 + foreach_instance +A +B add_servald_interface 1 + start_simulator + simulator_command create "net" "$SERVALD_VAR/dummy1/" + simulator_command set "net" "drop_unicast" "1" foreach_instance +A +B start_servald_server } test_broadcast_only() { + simulator_command up "net" set_instance +A wait_until has_link --broadcast $SIDB set_instance +B @@ -424,6 +457,9 @@ test_broadcast_only() { executeOk_servald mdp ping --timeout=3 $SIDB 1 tfw_cat --stdout --stderr } +finally_broadcast_only() { + simulator_quit +} doc_prefer_unicast="Prefer unicast packets" setup_prefer_unicast() { @@ -535,27 +571,37 @@ doc_lose_neighbours="Lose and regain neighbours" setup_lose_neighbours() { setup_servald assert_no_servald_processes + start_simulator + simulator_command create "net1" "$SERVALD_VAR/dummy1/" + simulator_command create "net2" "$SERVALD_VAR/dummy2/" foreach_instance +A +B +C create_single_identity foreach_instance +A +B add_servald_interface 1 foreach_instance +B +C add_servald_interface 2 foreach_instance +A +B +C start_servald_server } test_lose_neighbours() { + simulator_command up "net1" "net2" wait_until path_exists +A +B +C wait_until path_exists +C +B +A - stop_servald_server +B + simulator_command down "net1" "net2" foreach_instance +A +C \ wait_until --timeout=30 instance_offline +B set_instance +A wait_until --timeout=30 instance_offline +C - start_servald_server +B + simulator_command up "net1" "net2" wait_until --timeout=20 path_exists +A +B +C wait_until --timeout=20 path_exists +C +B +A } +finally_lose_neighbours() { + simulator_quit +} setup_multi_interface() { setup_servald assert_no_servald_processes + start_simulator + simulator_command create "wlan0" "$SERVALD_VAR/dummy1/" + simulator_command create "eth0" "$SERVALD_VAR/dummy2/" foreach_instance +A +B create_single_identity foreach_instance +A +B add_servald_interface --wifi 1 foreach_instance +A +B add_servald_interface --ethernet 2 @@ -564,36 +610,41 @@ setup_multi_interface() { doc_multi_interface="Multiple links of different types to the same neighbour" test_multi_interface() { + simulator_command up "wlan0" "eth0" set_instance +A wait_until has_link --interface "dummy2.*" $SIDB set_instance +B wait_until has_link --interface "dummy2.*" $SIDA set_instance +A - executeOk_servald config set interfaces.2.exclude 1 + simulator_command down "eth0" wait_until has_link --interface "dummy1.*" $SIDB set_instance +B wait_until has_link --interface "dummy1.*" $SIDA set_instance +A - executeOk_servald config del interfaces.2.exclude + simulator_command up "eth0" wait_until has_link --interface "dummy2.*" $SIDB set_instance +B wait_until has_link --interface "dummy2.*" $SIDA } +finally_multi_interface() { + simulator_quit +} doc_ping_unreliable_1hop="Ping over a 1-hop unreliable link" setup_ping_unreliable_1hop() { setup_servald assert_no_servald_processes foreach_instance +A +B create_single_identity - foreach_instance +A +B add_servald_interface --file 1 - foreach_instance +A +B \ - executeOk_servald config \ - set interfaces.1.drop_packets 40 + foreach_instance +A +B add_servald_interface 1 + start_simulator + simulator_command create "net" "$SERVALD_VAR/dummy1/" + simulator_command set "net" "drop_packets" "40" foreach_instance +A +B start_servald_server } test_ping_unreliable_1hop() { - wait_until path_exists +A +B - wait_until path_exists +B +A + simulator_command up "net" + wait_until --timeout=10 path_exists +A +B + wait_until --timeout=5 path_exists +B +A set_instance +A executeOk_servald mdp ping --interval=0.100 --timeout=3 --wait-for-duplicates $SIDB 100 tfw_cat --stdout --stderr @@ -602,23 +653,26 @@ test_ping_unreliable_1hop() { assert [ "$received" -ge 98 ] assert [ "$duplicates" -le 2 ] } +finally_ping_unreliable_1hop() { + simulator_quit +} doc_ping_unreliable_2hop="Ping over a 2-hop unreliable link" setup_ping_unreliable_2hop() { setup_servald assert_no_servald_processes foreach_instance +A +B +C create_single_identity - foreach_instance +A +B add_servald_interface --file 1 - foreach_instance +A +B \ - executeOk_servald config \ - set interfaces.1.drop_packets 40 - foreach_instance +B +C add_servald_interface --file 2 - foreach_instance +B +C \ - executeOk_servald config \ - set interfaces.2.drop_packets 40 + foreach_instance +A +B add_servald_interface 1 + foreach_instance +B +C add_servald_interface 2 + start_simulator + simulator_command create "net1" "$SERVALD_VAR/dummy1/" + simulator_command set "net1" "drop_packets" "40" + simulator_command create "net2" "$SERVALD_VAR/dummy2/" + simulator_command set "net2" "drop_packets" "40" foreach_instance +A +B +C start_servald_server } test_ping_unreliable_2hop() { + simulator_command up "net1" "net2" wait_until path_exists +A +B +C wait_until path_exists +C +B +A set_instance +A @@ -629,49 +683,79 @@ test_ping_unreliable_2hop() { assert [ "$received" -ge 98 ] assert [ "$duplicates" -le 5 ] } +finally_ping_unreliable_2hop() { + simulator_quit +} + +# TODO implement congestion control, use this test as a basis for improving... +doc_ping_congested="Ping flood over an unreliable and congested link" +setup_ping_congested() { + setup_servald + assert_no_servald_processes + foreach_instance +A +B create_single_identity + foreach_instance +A +B add_servald_interface 1 + start_simulator + simulator_command create "net1" "$SERVALD_VAR/dummy1/" + simulator_command set "net1" \ + "drop_packets" "20" \ + "latency" "20" \ + "rate" "20000" + foreach_instance +A +B +C start_servald_server +} +test_ping_congested() { + simulator_command up "net1" + wait_until --timeout=10 path_exists +A +B + wait_until --timeout=5 path_exists +B +A + set_instance +A + executeOk_servald mdp ping --interval=0.100 --timeout=3 $SIDB 100 + tfw_cat --stdout --stderr +} +finally_ping_congested() { + simulator_quit +} doc_brping_unreliable_1hop="Broadcast ping over a 1-hop unreliable link" setup_brping_unreliable_1hop() { setup_servald assert_no_servald_processes foreach_instance +A +B create_single_identity - foreach_instance +A +B add_servald_interface --file 1 - foreach_instance +A +B \ - executeOk_servald config \ - set interfaces.1.drop_packets 20 + foreach_instance +A +B add_servald_interface 1 + start_simulator + simulator_command create "net" "$SERVALD_VAR/dummy1/" + simulator_command set "net" "drop_packets" "20" foreach_instance +A +B start_servald_server } test_brping_unreliable_1hop() { + simulator_command up "net" wait_until path_exists +A +B wait_until path_exists +B +A set_instance +A executeOk_servald mdp ping --interval=0.100 --timeout=2 --wait-for-duplicates broadcast 100 tfw_cat --stdout --stderr } +finally_brping_unreliable_1hop() { + simulator_quit +} doc_unreliable_links="Prefer a longer, better path vs an unreliable link" setup_unreliable_links() { setup_servald assert_no_servald_processes foreach_instance +A +B +C create_single_identity - foreach_instance +A +B add_servald_interface --file 1 - foreach_instance +B +C add_servald_interface --file 2 - foreach_instance +A +C add_servald_interface --file 3 - set_instance +A - executeOk_servald config \ - set interfaces.1.drop_packets 5 \ - set interfaces.3.drop_packets 70 - set_instance +B - executeOk_servald config \ - set interfaces.1.drop_packets 5 \ - set interfaces.2.drop_packets 5 - set_instance +C - executeOk_servald config \ - set interfaces.2.drop_packets 5 \ - set interfaces.3.drop_packets 70 + foreach_instance +A +B add_servald_interface 1 + foreach_instance +B +C add_servald_interface 2 + foreach_instance +A +C add_servald_interface 3 + start_simulator + simulator_command create "net1" "$SERVALD_VAR/dummy1/" + simulator_command create "net2" "$SERVALD_VAR/dummy2/" + simulator_command create "net3" "$SERVALD_VAR/dummy3/" + simulator_command set "net1" "drop_packets" "5" + simulator_command set "net2" "drop_packets" "5" + simulator_command set "net3" "drop_packets" "70" foreach_instance +A +B +C start_servald_server } test_unreliable_links() { + simulator_command up "net1" "net2" "net3" wait_until path_exists +A +B +C wait_until path_exists +C +B +A set_instance +A @@ -685,43 +769,40 @@ test_unreliable_links() { wait_until path_exists +A +B +C wait_until path_exists +C +B +A } +finally_unreliable_links() { + simulator_quit +} doc_unreliable_links2="Choose the best multihop path with some unreliable links" setup_unreliable_links2() { setup_servald assert_no_servald_processes foreach_instance +A +B +C +D create_single_identity - foreach_instance +A +B add_servald_interface --file 1 - foreach_instance +A +C add_servald_interface --file 2 - foreach_instance +A +D add_servald_interface --file 3 - foreach_instance +B +C add_servald_interface --file 4 - foreach_instance +B +D add_servald_interface --file 5 - foreach_instance +C +D add_servald_interface --file 6 - set_instance +A - executeOk_servald config \ - set interfaces.1.drop_packets 5 \ - set interfaces.2.drop_packets 40 \ - set interfaces.3.drop_packets 90 - set_instance +B - executeOk_servald config \ - set interfaces.1.drop_packets 5 \ - set interfaces.4.drop_packets 5 \ - set interfaces.5.drop_packets 40 - set_instance +C - executeOk_servald config \ - set interfaces.2.drop_packets 40 \ - set interfaces.4.drop_packets 5 \ - set interfaces.6.drop_packets 5 - set_instance +D - executeOk_servald config \ - set interfaces.3.drop_packets 90 \ - set interfaces.5.drop_packets 40 \ - set interfaces.6.drop_packets 5 + foreach_instance +A +B add_servald_interface 1 + foreach_instance +A +C add_servald_interface 2 + foreach_instance +A +D add_servald_interface 3 + foreach_instance +B +C add_servald_interface 4 + foreach_instance +B +D add_servald_interface 5 + foreach_instance +C +D add_servald_interface 6 + start_simulator + simulator_command create "net1" "$SERVALD_VAR/dummy1/" + simulator_command create "net2" "$SERVALD_VAR/dummy2/" + simulator_command create "net3" "$SERVALD_VAR/dummy3/" + simulator_command create "net4" "$SERVALD_VAR/dummy4/" + simulator_command create "net5" "$SERVALD_VAR/dummy5/" + simulator_command create "net6" "$SERVALD_VAR/dummy6/" + simulator_command set "net1" "drop_packets" "5" + simulator_command set "net2" "drop_packets" "60" + simulator_command set "net3" "drop_packets" "90" + simulator_command set "net4" "drop_packets" "5" + simulator_command set "net5" "drop_packets" "60" + simulator_command set "net6" "drop_packets" "5" foreach_instance +A +B +C +D start_servald_server } test_unreliable_links2() { - wait_until path_exists +A +B +C +D - wait_until path_exists +D +C +B +A + simulator_command up "net1" "net2" "net3" "net4" "net5" "net6" + wait_until --timeout=20 path_exists +A +B +C +D + wait_until --timeout=20 path_exists +D +C +B +A set_instance +A executeOk_servald mdp ping --interval=0.100 --timeout=3 --wait-for-duplicates $SIDD 100 tfw_cat --stdout --stderr @@ -732,6 +813,9 @@ test_unreliable_links2() { wait_until --timeout=20 path_exists +A +B +C +D wait_until --timeout=20 path_exists +D +C +B +A } +finally_unreliable_links2() { + simulator_quit +} setup_circle() { setup_servald