Create new AF_UNIX based network simulator

This commit is contained in:
Jeremy Lakeman 2014-05-02 14:02:25 +09:30
parent 33bbd7b52e
commit 7420f46653
8 changed files with 898 additions and 121 deletions

1
.gitignore vendored
View File

@ -23,6 +23,7 @@ serval.c
/tfw_createfile
/fakeradio
/config_test
/simulator
*.so
test.*.log
testlog

View File

@ -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)

18
main.c
View File

@ -68,21 +68,3 @@ static void crash_handler(int signal)
exit(-signal);
}
#if 0
#include <execinfo.h>
#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;i<depth;i++)
fprintf(stderr,"%s\n", function_names[i]);
return 0;
}
#endif

View File

@ -789,39 +789,44 @@ static void overlay_interface_poll(struct sched_ent *alarm)
}
}
static int send_local_broadcast(int fd, const uint8_t *bytes, size_t len, struct socket_address *address)
static int send_local_packet(int fd, const uint8_t *bytes, size_t len, const char *folder, const char *file)
{
struct socket_address addr;
strbuf d = strbuf_local(addr.local.sun_path, sizeof addr.local.sun_path);
strbuf_path_join(d, folder, file, NULL);
if (strbuf_overrun(d))
return WHYF("interface file name overrun: %s", alloca_str_toprint(strbuf_str(d)));
struct stat st;
if (lstat(addr.local.sun_path, &st))
return 1;
if (!S_ISSOCK(st.st_mode))
return 1;
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)
return WHYF_perror("sendto(%d, %zu, %s)", fd, len, alloca_socket_address(&addr));
return 0;
}
static int send_local_broadcast(int fd, const uint8_t *bytes, size_t len, const char *folder)
{
if (send_local_packet(fd, bytes, len, folder, "broadcast")==0)
return 0;
DIR *dir;
struct dirent *dp;
if ((dir = opendir(address->local.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,

689
simulator.c Normal file
View File

@ -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 <signal.h>
#include <time.h>
#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","<name>","<path>",NULL},0,"Create a named network"},
{console_variable,{"set", "<name>", "<variable>","<value>","...",NULL},0,"Set a property of the network"},
{console_up,{"up", "<name>", "...", NULL},0,"Bring a network up"},
{console_down,{"down","<name>","...",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;
}

4
simulator.h Normal file
View File

@ -0,0 +1,4 @@
struct {
}

View File

@ -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

View File

@ -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