Add IPv4 addresses to dummy interface

This commit is contained in:
Jeremy Lakeman 2012-12-08 15:09:41 +10:30
parent b7d4fa478e
commit 2932544eb8
9 changed files with 213 additions and 149 deletions

View File

@ -285,7 +285,7 @@ ATOM(sid_t, service, SID_ANY, cf_opt_sid,, "Subscriber ID of
END_STRUCT
STRUCT(host)
STRING(INTERFACE_NAME_STRLEN, interface, "", cf_opt_str_nonempty, MANDATORY, "Interface name")
STRING(INTERFACE_NAME_STRLEN, interface, "", cf_opt_str_nonempty,, "Interface name")
ATOM(struct in_addr, address, (struct in_addr){htonl(INADDR_NONE)}, cf_opt_in_addr, MANDATORY, "Host IP address")
ATOM(uint16_t, port, PORT_DNA, cf_opt_uint16_nonzero,, "Port number")
END_STRUCT
@ -299,6 +299,9 @@ STRUCT(network_interface, vld_network_interface)
ATOM(int, exclude, 0, cf_opt_boolean,, "If true, do not use matching interfaces")
ATOM(struct pattern_list, match, PATTERN_LIST_EMPTY, cf_opt_pattern_list,, "Names that match network interface")
STRING(256, dummy, "", cf_opt_str_nonempty,, "Path of dummy file, absolute or relative to server.dummy_interface_dir")
ATOM(struct in_addr, dummy_address, (struct in_addr){htonl(0x7F000001)}, cf_opt_in_addr,, "Dummy interface address")
ATOM(struct in_addr, dummy_netmask, (struct in_addr){htonl(0xFFFFFF00)}, cf_opt_in_addr,, "Dummy interface netmask")
ATOM(int, dummy_filter_broadcasts, 0, cf_opt_boolean,, "If true, drop all incoming broadcast packets")
ATOM(short, type, OVERLAY_INTERFACE_WIFI, cf_opt_interface_type,, "Type of network interface")
ATOM(uint16_t, port, RHIZOME_HTTP_PORT, cf_opt_uint16_nonzero,, "Port number for network interface")
ATOM(uint64_t, speed, 1000000, cf_opt_uint64_scaled,, "Speed in bits per second")

View File

@ -64,7 +64,6 @@ static unsigned char get_nibble(const unsigned char *sid, int pos){
}
// find a subscriber struct from a whole or abbreviated subscriber id
// TODO find abreviated sid's
struct subscriber *find_subscriber(const unsigned char *sid, int len, int create){
struct tree_node *ptr = &root;
int pos=0;
@ -263,13 +262,16 @@ int load_subscriber_address(struct subscriber *subscriber)
if (i == -1)
return 1;
const struct config_host *hostc = &config.hosts.av[i].value;
overlay_interface *interface = overlay_interface_find_name(hostc->interface);
if (!interface)
return -1;
overlay_interface *interface = NULL;
if (*hostc->interface){
interface = overlay_interface_find_name(hostc->interface);
if (!interface)
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr = hostc->address;
addr.sin_port = hostc->port;
addr.sin_port = htons(hostc->port);
return overlay_send_probe(subscriber, addr, interface);
}
@ -374,13 +376,11 @@ static int add_explain_response(struct subscriber *subscriber, void *context){
static int find_subscr_buffer(struct decode_context *context, struct overlay_buffer *b, int len, struct subscriber **subscriber){
if (len<=0 || len>SID_SIZE){
dump_stack();
return WHY("Invalid abbreviation length");
}
unsigned char *id = ob_get_bytes_ptr(b, len);
if (!id){
dump_stack();
return WHY("Not enough space in buffer to parse address");
}
@ -428,6 +428,8 @@ int overlay_broadcast_parse(struct overlay_buffer *b, struct broadcast *broadcas
int overlay_address_parse(struct decode_context *context, struct overlay_buffer *b, struct subscriber **subscriber)
{
int len = ob_get(b);
if (len<0)
return WHY("Buffer too small");
switch(len){
case OA_CODE_SELF:

View File

@ -384,14 +384,15 @@ overlay_interface_init(const char *name, struct in_addr src_addr, struct in_addr
}
interface->address.sin_family=AF_INET;
interface->address.sin_port = 0;
interface->address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
interface->address.sin_port = htons(PORT_DNA);
interface->address.sin_addr = ifconfig->dummy_address;
interface->netmask.s_addr=0xFFFFFF00;
interface->netmask=ifconfig->dummy_netmask;
interface->broadcast_address.sin_family=AF_INET;
interface->broadcast_address.sin_port = 0;
interface->broadcast_address.sin_port = htons(PORT_DNA);
interface->broadcast_address.sin_addr.s_addr = interface->address.sin_addr.s_addr | ~interface->netmask.s_addr;
interface->drop_broadcasts = ifconfig->dummy_filter_broadcasts;
/* Seek to end of file as initial reading point */
interface->recv_offset = lseek(interface->alarm.poll.fd,0,SEEK_END);
@ -408,6 +409,9 @@ overlay_interface_init(const char *name, struct in_addr src_addr, struct in_addr
interface->state=INTERFACE_STATE_UP;
INFOF("Dummy interface %s is up",interface->name);
DEBUGF("Address %s",inet_ntoa(interface->address.sin_addr));
DEBUGF("Netmask %s",inet_ntoa(interface->netmask));
DEBUGF("Broadcast %s",inet_ntoa(interface->broadcast_address.sin_addr));
directory_registration();
@ -489,21 +493,34 @@ static void overlay_interface_poll(struct sched_ent *alarm)
}
}
struct dummy_packet{
struct sockaddr_in src_addr;
struct sockaddr_in dst_addr;
int pid;
int payload_length;
/* TODO ? ;
half-power beam height (uint16)
half-power beam width (uint16)
range in metres, centre beam (uint32)
latitude (uint32)
longitude (uint32)
X/Z direction (uint16)
Y direction (uint16)
speed in metres per second (uint16)
TX frequency in Hz, uncorrected for doppler (which must be done at the receiving end to take into account
relative motion)
coding method (use for doppler response etc) null terminated string
*/
unsigned char payload[1400];
};
void overlay_dummy_poll(struct sched_ent *alarm)
{
overlay_interface *interface = (overlay_interface *)alarm;
/* Grab packets, unpackage and dispatch frames to consumers */
/* XXX Okay, so how are we managing out-of-process consumers?
They need some way to register their interest in listening to a port.
*/
unsigned char packet[2048];
int plen=0;
struct sockaddr_in src_addr={
.sin_family = AF_INET,
.sin_port = 0,
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
};
size_t addrlen = sizeof(src_addr);
struct dummy_packet packet;
time_ms_t now = gettime_ms();
/* Read from dummy interface file */
@ -522,31 +539,38 @@ void overlay_dummy_poll(struct sched_ent *alarm)
alarm->alarm = interface->last_tick_ms + interface->tick_ms;
alarm->deadline = alarm->alarm + 10000;
} else {
if (lseek(alarm->poll.fd,interface->recv_offset,SEEK_SET) == -1)
if (lseek(alarm->poll.fd,interface->recv_offset,SEEK_SET) == -1){
WHY_perror("lseek");
else {
if (debug&DEBUG_OVERLAYINTERFACES)
DEBUGF("Read interface %s (size=%lld) at offset=%d",interface->name, length, interface->recv_offset);
ssize_t nread = read(alarm->poll.fd, packet, sizeof packet);
if (nread == -1)
WHY_perror("read");
else {
if (nread == sizeof packet) {
interface->recv_offset += nread;
plen = packet[110] + (packet[111] << 8);
if (plen > nread - 128)
plen = -1;
if (debug&DEBUG_PACKETRX)
DEBUG_packet_visualise("Read from dummy interface", &packet[128], plen);
if (packetOkOverlay(interface, &packet[128], plen, -1, (struct sockaddr*)&src_addr, addrlen)) {
WARN("Unsupported packet from dummy interface");
}
return;
}
if (debug&DEBUG_OVERLAYINTERFACES)
DEBUGF("Read interface %s (size=%lld) at offset=%d",interface->name, length, interface->recv_offset);
ssize_t nread = read(alarm->poll.fd, &packet, sizeof packet);
if (nread == -1){
WHY_perror("read");
return;
}
if (nread == sizeof packet) {
interface->recv_offset += nread;
if (debug&DEBUG_PACKETRX)
DEBUG_packet_visualise("Read from dummy interface", packet.payload, packet.payload_length);
if (memcmp(&packet.dst_addr, &interface->address, sizeof(packet.dst_addr))==0 ||
((!interface->drop_broadcasts) &&
memcmp(&packet.dst_addr, &interface->broadcast_address, sizeof(packet.dst_addr))==0)){
if (packetOkOverlay(interface, packet.payload, packet.payload_length, -1,
(struct sockaddr*)&packet.src_addr, sizeof(packet.src_addr))) {
WARN("Unsupported packet from dummy interface");
}
else
WARNF("Read %lld bytes from dummy interface", nread);
}
}
/* keep reading new packets as fast as possible,
but don't completely prevent other high priority alarms */
if (interface->recv_offset >= length)
@ -589,42 +613,19 @@ overlay_broadcast_ensemble(int interface_number,
if (interface->fileP)
{
char buf[2048];
bzero(&buf[0],128);
/* Version information */
buf[0]=1; buf[1]=0;
buf[2]=0; buf[3]=0;
/* PID of creator */
buf[4]=getpid()&0xff; buf[5]=getpid()>>8;
/* TODO make a structure for all this stuff */
/* bytes 4-5 = half-power beam height (uint16) */
/* bytes 6-7 = half-power beam width (uint16) */
/* bytes 8-11 = range in metres, centre beam (uint32) */
/* bytes 16-47 = sender */
/* bytes 48-79 = next hop */
/* bytes 80-83 = latitude (uint32) */
/* bytes 84-87 = longitude (uint32) */
/* bytes 88-89 = X/Z direction (uint16) */
/* bytes 90-91 = Y direction (uint16) */
/* bytes 92-93 = speed in metres per second (uint16) */
/* bytes 94-97 = TX frequency in Hz, uncorrected for doppler (which must be done at the receiving end to take into account
relative motion) */
/* bytes 98-109 = coding method (use for doppler response etc) null terminated string */
/* bytes 110-111 = length of packet body in bytes */
/* bytes 112-127 reserved for future use */
if (len>2048-128) {
WARN("Truncating long packet to fit within 1920 byte limit for dummy interface");
len=2048-128;
struct dummy_packet packet={
.src_addr = interface->address,
.dst_addr = *recipientaddr,
.pid = getpid(),
};
if (len > sizeof(packet.payload)){
WARN("Truncating long packet to fit within MTU byte limit for dummy interface");
len = sizeof(packet.payload);
}
/* Record length of packet */
buf[110]=len&0xff;
buf[111]=(len>>8)&0xff;
bzero(&buf[128+len],2048-(128+len));
bcopy(bytes,&buf[128],len);
packet.payload_length=len;
bcopy(bytes, packet.payload, len);
/* This lseek() is unneccessary because the dummy file is opened in O_APPEND mode. It's
only purpose is to find out the offset to print in the DEBUG statement. It is vulnerable
to a race condition with other processes appending to the same file. */
@ -633,11 +634,11 @@ overlay_broadcast_ensemble(int interface_number,
return WHY_perror("lseek");
if (debug&DEBUG_OVERLAYINTERFACES)
DEBUGF("Write to interface %s at offset=%d", interface->name, fsize);
ssize_t nwrite = write(interface->alarm.poll.fd, buf, 2048);
ssize_t nwrite = write(interface->alarm.poll.fd, &packet, sizeof(packet));
if (nwrite == -1)
return WHY_perror("write");
if (nwrite != 2048)
return WHYF("only wrote %lld of %lld bytes", nwrite, 2048);
if (nwrite != sizeof(packet))
return WHYF("only wrote %lld of %lld bytes", nwrite, sizeof(packet));
return 0;
}
else

View File

@ -879,8 +879,10 @@ static void overlay_mdp_scan(struct sched_ent *alarm)
while(state->current <= state->last){
addr.sin_addr.s_addr=htonl(state->current);
if (overlay_send_probe(NULL, addr, state->interface))
break;
if (addr.sin_addr.s_addr != state->interface->address.sin_addr.s_addr){
if (overlay_send_probe(NULL, addr, state->interface))
break;
}
state->current++;
}
@ -888,6 +890,7 @@ static void overlay_mdp_scan(struct sched_ent *alarm)
alarm->alarm=gettime_ms()+500;
schedule(alarm);
}else{
DEBUG("Scan completed");
state->interface=NULL;
state->current=0;
state->last=0;

View File

@ -307,7 +307,11 @@ overlay_mdp_service_probe(overlay_mdp_frame *mdp)
RETURN(WHY("Unsupported address family"));
if (peer->reachable == REACHABLE_NONE || peer->reachable == REACHABLE_INDIRECT || (peer->reachable & REACHABLE_ASSUMED)){
reachable_unicast(peer, &overlay_interfaces[probe.interface], probe.addr.sin_addr, probe.addr.sin_port);
peer->interface = &overlay_interfaces[probe.interface];
peer->address.sin_family = AF_INET;
peer->address.sin_addr = probe.addr.sin_addr;
peer->address.sin_port = probe.addr.sin_port;
set_reachable(peer, REACHABLE_UNICAST);
}
RETURN(0);
}
@ -336,12 +340,10 @@ int overlay_send_probe(struct subscriber *peer, struct sockaddr_in addr, overlay
frame->flags=PACKET_UNICAST;
frame->interface=interface;
frame->payload = ob_new();
frame->send_copies=3;
// TODO call mdp payload encryption / signing without calling overlay_mdp_dispatch...
if ((!peer) || !(peer->reachable&REACHABLE))
my_subscriber->send_full=1;
my_subscriber->send_full=1;
if (peer)
peer->last_probe=gettime_ms();
@ -365,17 +367,19 @@ int overlay_send_probe(struct subscriber *peer, struct sockaddr_in addr, overlay
op_free(frame);
return -1;
}
DEBUGF("Queued probe packet on interface %s to %s", interface->name, inet_ntoa(addr.sin_addr));
DEBUGF("Queued probe packet on interface %s to %s for %s",
interface->name, inet_ntoa(addr.sin_addr), peer?alloca_tohex_sid(peer->sid):"ANY");
return 0;
}
// append the address of a unicast link into a packet buffer
static int overlay_append_unicast_address(struct subscriber *subscriber, struct overlay_buffer *buff)
{
if (!(subscriber->reachable & REACHABLE_UNICAST))
return 0;
if (subscriber->reachable & REACHABLE_ASSUMED)
if (subscriber->reachable & REACHABLE_ASSUMED || !(subscriber->reachable & REACHABLE_UNICAST)){
DEBUGF("Unable to give address of %s, %d", alloca_tohex_sid(subscriber->sid),subscriber->reachable);
return 0;
}
if (overlay_address_append(NULL, buff, subscriber))
return -1;
if (ob_append_ui32(buff, subscriber->address.sin_addr.s_addr))
@ -387,6 +391,22 @@ static int overlay_append_unicast_address(struct subscriber *subscriber, struct
return 0;
}
// append the address of all neighbour unicast links into a packet buffer
/*
static int overlay_append_local_unicasts(struct subscriber *subscriber, void *context)
{
struct overlay_buffer *buff = context;
if ((!subscriber->interface) ||
(!(subscriber->reachable & REACHABLE_UNICAST)) ||
(subscriber->reachable & REACHABLE_ASSUMED))
return 0;
if ((subscriber->address.sin_addr.s_addr & subscriber->interface->netmask.s_addr) !=
(subscriber->interface->address.sin_addr.s_addr & subscriber->interface->netmask.s_addr))
return 0;
return overlay_append_unicast_address(subscriber, buff);
}
*/
static int overlay_mdp_service_stun_req(overlay_mdp_frame *mdp)
{
DEBUGF("Processing STUN request from %s", alloca_tohex_sid(mdp->out.src.sid));
@ -412,8 +432,10 @@ static int overlay_mdp_service_stun_req(overlay_mdp_frame *mdp)
if (overlay_address_parse(NULL, payload, &subscriber))
break;
if (!subscriber)
if (!subscriber){
DEBUGF("Unknown subscriber");
continue;
}
if (overlay_append_unicast_address(subscriber, replypayload))
break;
@ -422,10 +444,11 @@ static int overlay_mdp_service_stun_req(overlay_mdp_frame *mdp)
ob_rewind(replypayload);
reply.out.payload_length=ob_position(replypayload);
if (reply.out.payload_length)
if (reply.out.payload_length){
DEBUGF("Sending reply");
overlay_mdp_dispatch(&reply,0 /* system generated */,
NULL,0);
}
ob_free(replypayload);
ob_free(payload);
return 0;

View File

@ -340,6 +340,7 @@ typedef struct overlay_interface {
char name[256];
int recv_offset;
int fileP; // dummyP
int drop_broadcasts;
int bits_per_second;
int port;
int type;

View File

@ -637,9 +637,12 @@ start_servald_instances() {
set_instance $I
# These config settings can be overridden in a caller-supplied configure_servald_server().
# They are extremely useful for the majority of fixtures.
executeOk_servald config set interfaces "+>$DUMMYNET"
executeOk_servald config set monitor.socket "org.servalproject.servald.monitor.socket.$TFWUNIQUE.$instance_name"
executeOk_servald config set mdp.socket "org.servalproject.servald.mdp.socket.$TFWUNIQUE.$instance_name"
executeOk_servald config \
set interfaces.1.dummy "$DUMMYNET" \
set interfaces.1.dummy_address 127.0.0.1 \
set interfaces.1.dummy_netmask 255.255.255.0 \
set monitor.socket "org.servalproject.servald.monitor.socket.$TFWUNIQUE.$instance_name" \
set mdp.socket "org.servalproject.servald.mdp.socket.$TFWUNIQUE.$instance_name"
configure_servald_server
start_servald_server
eval DUMMY$instance_name="\$DUMMYNET"

View File

@ -27,24 +27,6 @@ configure_servald_server() {
set debug.mdprequests Yes
}
setup() {
setup_servald
assert_no_servald_processes
foreach_instance +A +B +C +D create_single_identity
set_instance +D
executeOk_servald set did $SIDD $DIDC "Agent D Smith"
DIDD1=$DIDC
NAMED1="Agent D Smith"
DIDD=$DIDC1
NAMED=$NAMED1
set_instance +A
executeOk_servald config \
set dna.helper.executable "$servald_build_root/directory_service" \
set debug.dnahelper on
foreach_instance +B +C +D executeOk_servald config set directory.service $SIDA
start_servald_instances +A +B +C +D
}
teardown() {
kill_all_servald_processes
assert_no_servald_processes
@ -63,6 +45,24 @@ sent_directory_request() {
return 0
}
setup_publish() {
setup_servald
assert_no_servald_processes
foreach_instance +A +B +C +D create_single_identity
set_instance +D
executeOk_servald set did $SIDD $DIDC "Agent D Smith"
DIDD1=$DIDC
NAMED1="Agent D Smith"
DIDD=$DIDC1
NAMED=$NAMED1
set_instance +A
executeOk_servald config \
set dna.helper.executable "$servald_build_root/directory_service" \
set debug.dnahelper on
foreach_instance +B +C +D executeOk_servald config set directory.service $SIDA
start_servald_instances +A +B +C +D
}
doc_publish="Publish and retrieve a directory entry"
test_publish() {
wait_until sent_directory_request $LOGB
@ -99,39 +99,36 @@ start_routing_instance() {
setup_routing() {
setup_servald
assert_no_servald_processes
# create three nodes, on two dummy interfaces, with no interface ticks or routing
foreach_instance +A +B +C create_single_identity
>$SERVALD_VAR/dummyB
>$SERVALD_VAR/dummyC
>$SERVALD_VAR/dummy1
foreach_instance +A +B +C \
executeOk_servald config \
set interfaces.0.dummy dummy1 \
set interfaces.0.mdp_tick_ms 0 \
set interfaces.0.default_route 1 \
set interfaces.0.dummy_netmask 255.255.255.0
set_instance +A
executeOk_servald config \
set interfaces.0.dummy dummyB \
set interfaces.0.mdp_tick_ms 0 \
set interfaces.1.dummy dummyC \
set interfaces.1.mdp_tick_ms 0 \
set interfaces.0.dummy_address 10.0.${instance_number}.1 \
set dna.helper.executable "$servald_build_root/directory_service" \
set debug.dnahelper on
set_instance +B
executeOk_servald config \
set interfaces.0.dummy dummyB \
set interfaces.0.mdp_tick_ms 0 \
set interfaces.0.dummy_address 10.0.${instance_number}.1 \
set directory.service $SIDA \
set hosts.$SIDA.address 127.0.0.1 \
set hosts.$SIDA.interface "dummyB"
set hosts.$SIDA.address 10.0.1.1
set_instance +C
executeOk_servald config \
set interfaces.1.dummy dummyC \
set interfaces.1.mdp_tick_ms 0 \
set interfaces.0.dummy_address 10.0.${instance_number}.1 \
set directory.service $SIDA \
set hosts.$SIDA.address 127.0.0.1 \
set hosts.$SIDA.interface "dummyC"
set hosts.$SIDA.address 10.0.1.1
foreach_instance +A +B +C start_routing_instance
}
doc_routing="Ping via relay node"
test_routing() {
wait_until sent_directory_request $LOGB
wait_until sent_directory_request $LOGC
foreach_instance +B +C \
wait_until sent_directory_request $instance_servald_log
wait_until is_published $SIDB
wait_until is_published $SIDC
set_instance +B

View File

@ -28,10 +28,16 @@ configure_servald_server() {
}
add_interface() {
>$SERVALD_VAR/$1
executeOk_servald config get interfaces
extract_stdout_keyvalue_optional EXISTING 'interfaces' '=' '.*'
executeOk_servald config set interfaces "+>$1,$EXISTING"
>$SERVALD_VAR/dummy$1
executeOk_servald config \
set interfaces.$1.dummy dummy$1 \
set interfaces.$1.dummy_address 127.0.$1.$instance_number \
set interfaces.$1.dummy_netmask 255.255.255.224
}
interface_up() {
grep "Dummy interface .* is up" $instance_servald_log || return 1
return 0
}
start_routing_instance() {
@ -43,6 +49,7 @@ start_routing_instance() {
set log.show_time on \
set debug.mdprequests Yes
start_servald_server
wait_until interface_up
}
log_routing_table() {
@ -62,7 +69,7 @@ setup_single_link() {
setup_servald
assert_no_servald_processes
foreach_instance +A +B create_single_identity
foreach_instance +A +B add_interface dummy1
foreach_instance +A +B add_interface 1
foreach_instance +A +B start_routing_instance
}
@ -77,7 +84,7 @@ 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_interface dummy1
foreach_instance +A +B +C +D add_interface 1
foreach_instance +A +B +C +D start_routing_instance
}
@ -92,13 +99,37 @@ test_multiple_nodes() {
tfw_cat --stdout --stderr
}
setup_scan() {
setup_servald
assert_no_servald_processes
foreach_instance +A +B create_single_identity
foreach_instance +A +B add_interface 1
foreach_instance +A +B \
executeOk_servald config \
set interfaces.1.dummy_filter_broadcasts 1
foreach_instance +A +B start_routing_instance
}
scan_completed() {
grep "Scan completed" $LOG||return1
return 0
}
doc_scan="Simulate isolated clients"
test_scan() {
set_instance +A
executeOk_servald scan
wait_until scan_completed
executeOk_servald mdp ping $SIDB 3
}
setup_multihop_linear() {
setup_servald
assert_no_servald_processes
foreach_instance +A +B +C +D create_single_identity
foreach_instance +A +B add_interface dummy1
foreach_instance +B +C add_interface dummy2
foreach_instance +C +D add_interface dummy3
foreach_instance +A +B add_interface 1
foreach_instance +B +C add_interface 2
foreach_instance +C +D add_interface 3
foreach_instance +A +B +C +D start_routing_instance
}
@ -114,13 +145,13 @@ setup_crowded_mess() {
assert_no_servald_processes
# BCDE & DEFG form squares, ABC & FGH form triangles
foreach_instance +A +B +C +D +E +F +G +H create_single_identity
foreach_instance +A +B +C add_interface dummy1
foreach_instance +B +D add_interface dummy2
foreach_instance +C +E add_interface dummy3
foreach_instance +D +E add_interface dummy4
foreach_instance +D +F add_interface dummy5
foreach_instance +E +G add_interface dummy6
foreach_instance +F +G +H add_interface dummy7
foreach_instance +A +B +C add_interface 1
foreach_instance +B +D add_interface 2
foreach_instance +C +E add_interface 3
foreach_instance +D +E add_interface 4
foreach_instance +D +F add_interface 5
foreach_instance +E +G add_interface 6
foreach_instance +F +G +H add_interface 7
foreach_instance +A +B +C +D +E +F +G +H start_routing_instance
}