diff --git a/src/tests/add-ons/kernel/network/Jamfile b/src/tests/add-ons/kernel/network/Jamfile new file mode 100644 index 0000000000..f929119507 --- /dev/null +++ b/src/tests/add-ons/kernel/network/Jamfile @@ -0,0 +1,39 @@ +SubDir OBOS_TOP src tests add-ons kernel network ; + +UsePrivateHeaders net ; + +# the network stack tester app + +SEARCH_SOURCE += [ FDirName $(OBOS_TOP) src kernel libroot os ] ; +SimpleTest net_stack_tester : + net_stack_tester.cpp + userland_ipc.c + userland_modules.cpp + : be +; +LOCATE on net_stack_tester = [ on net_stack_tester return $(LOCATE) ] ; +Depends net_stack_tester : net_stack_tester ; + +# for convenience: this target builds all userland modules + +NotFile userland_network_modules ; +Depends userland_network_modules : + # the stack main module + core + + # interfaces modules + loopback + ethernet + # ppp + + # protocols modules + raw + ipv4 + icmp + udp + tcp +; + +SubInclude OBOS_TOP src tests add-ons kernel network core ; +SubInclude OBOS_TOP src tests add-ons kernel network interfaces ; +SubInclude OBOS_TOP src tests add-ons kernel network protocols ; diff --git a/src/tests/add-ons/kernel/network/core/Jamfile b/src/tests/add-ons/kernel/network/core/Jamfile new file mode 100644 index 0000000000..25ccf74679 --- /dev/null +++ b/src/tests/add-ons/kernel/network/core/Jamfile @@ -0,0 +1,27 @@ +SubDir OBOS_TOP src tests add-ons kernel network core ; + +UsePrivateHeaders net ; + +SEARCH_SOURCE += [ FDirName $(OBOS_TOP) src add-ons kernel network core ] ; + +Addon core : userland network : + cksum.c + core.c + if.c + ifq.c + in.c + inpcb.c + mbuf.c + misc.c + net_timer.c + nhash.c + pools.c + radix.c + route.c + sockbuf.c + socket.c +; + +LinkSharedOSLibs core : root net_stack_tester ; + + diff --git a/src/tests/add-ons/kernel/network/interfaces/Jamfile b/src/tests/add-ons/kernel/network/interfaces/Jamfile new file mode 100644 index 0000000000..20976e44a4 --- /dev/null +++ b/src/tests/add-ons/kernel/network/interfaces/Jamfile @@ -0,0 +1,5 @@ +SubDir OBOS_TOP src tests add-ons kernel network interfaces ; + +SubInclude OBOS_TOP src tests add-ons kernel network interfaces ethernet ; +SubInclude OBOS_TOP src tests add-ons kernel network interfaces loopback ; + diff --git a/src/tests/add-ons/kernel/network/interfaces/ethernet/Jamfile b/src/tests/add-ons/kernel/network/interfaces/ethernet/Jamfile new file mode 100644 index 0000000000..819e4392ba --- /dev/null +++ b/src/tests/add-ons/kernel/network/interfaces/ethernet/Jamfile @@ -0,0 +1,12 @@ +SubDir OBOS_TOP src tests add-ons kernel network interfaces ethernet ; + +UsePrivateHeaders net ; + +SEARCH_SOURCE += [ FDirName $(OBOS_TOP) src add-ons kernel network interfaces ethernet ] ; + +Addon ethernet : userland network interfaces : + ethernet.c +; + +LinkSharedOSLibs ethernet : root net_stack_tester ; + diff --git a/src/tests/add-ons/kernel/network/interfaces/loopback/Jamfile b/src/tests/add-ons/kernel/network/interfaces/loopback/Jamfile new file mode 100644 index 0000000000..68e69778ac --- /dev/null +++ b/src/tests/add-ons/kernel/network/interfaces/loopback/Jamfile @@ -0,0 +1,12 @@ +SubDir OBOS_TOP src tests add-ons kernel network interfaces loopback ; + +UsePrivateHeaders net ; + +SEARCH_SOURCE += [ FDirName $(OBOS_TOP) src add-ons kernel network interfaces loopback ] ; + +Addon loopback : userland network interfaces : + loopback.c +; + +LinkSharedOSLibs loopback : root net_stack_tester ; + diff --git a/src/tests/add-ons/kernel/network/net_stack_tester.cpp b/src/tests/add-ons/kernel/network/net_stack_tester.cpp new file mode 100644 index 0000000000..0ac231ab36 --- /dev/null +++ b/src/tests/add-ons/kernel/network/net_stack_tester.cpp @@ -0,0 +1,46 @@ +/* Network modules debug platform +*/ + +#include +#include +#include + +#include +#include + +#include +#include + +struct core_module_info * core = NULL; + +int main(int argc, char **argv) +{ + char buffer[8]; + int ret = -1; + + new BApplication("application/x-vnd-OBOS-net_server"); + + if (init_userland_ipc() < B_OK) + goto exit; + + if (get_module(NET_CORE_MODULE_NAME, (module_info **) &core) != B_OK) { + shutdown_userland_ipc(); + goto exit; + } + + puts("Starting core module..."); + core->start(); + + puts("Userland net stack (net_server) is running. Press to quit."); + fgets(buffer,sizeof(buffer), stdin); + + put_module(NET_CORE_MODULE_NAME);; + shutdown_userland_ipc(); + + ret = 0; + +exit:; + delete be_app; + return ret; +} + diff --git a/src/tests/add-ons/kernel/network/protocols/Jamfile b/src/tests/add-ons/kernel/network/protocols/Jamfile new file mode 100644 index 0000000000..7255cf3bf6 --- /dev/null +++ b/src/tests/add-ons/kernel/network/protocols/Jamfile @@ -0,0 +1,8 @@ +SubDir OBOS_TOP src tests add-ons kernel network protocols ; + +SubInclude OBOS_TOP src tests add-ons kernel network protocols icmp ; +SubInclude OBOS_TOP src tests add-ons kernel network protocols ipv4 ; +SubInclude OBOS_TOP src tests add-ons kernel network protocols raw ; +SubInclude OBOS_TOP src tests add-ons kernel network protocols udp ; +SubInclude OBOS_TOP src tests add-ons kernel network protocols tcp ; + diff --git a/src/tests/add-ons/kernel/network/protocols/icmp/Jamfile b/src/tests/add-ons/kernel/network/protocols/icmp/Jamfile new file mode 100644 index 0000000000..40005b0e7a --- /dev/null +++ b/src/tests/add-ons/kernel/network/protocols/icmp/Jamfile @@ -0,0 +1,11 @@ +SubDir OBOS_TOP src tests add-ons kernel network protocols icmp ; + +UsePrivateHeaders net ; + +SEARCH_SOURCE += [ FDirName $(OBOS_TOP) src add-ons kernel network protocols icmp ] ; + +Addon icmp : userland network protocols : + icmp.c +; + +LinkSharedOSLibs icmp : root net_stack_tester ; diff --git a/src/tests/add-ons/kernel/network/protocols/ipv4/Jamfile b/src/tests/add-ons/kernel/network/protocols/ipv4/Jamfile new file mode 100644 index 0000000000..0a94c47a95 --- /dev/null +++ b/src/tests/add-ons/kernel/network/protocols/ipv4/Jamfile @@ -0,0 +1,11 @@ +SubDir OBOS_TOP src tests add-ons kernel network protocols ipv4 ; + +UsePrivateHeaders net ; + +SEARCH_SOURCE += [ FDirName $(OBOS_TOP) src add-ons kernel network protocols ipv4 ] ; + +Addon ipv4 : userland network protocols : + ipv4.c +; + +LinkSharedOSLibs ipv4 : root net_stack_tester ; diff --git a/src/tests/add-ons/kernel/network/protocols/raw/Jamfile b/src/tests/add-ons/kernel/network/protocols/raw/Jamfile new file mode 100644 index 0000000000..d55c8ca0bd --- /dev/null +++ b/src/tests/add-ons/kernel/network/protocols/raw/Jamfile @@ -0,0 +1,11 @@ +SubDir OBOS_TOP src tests add-ons kernel network protocols raw ; + +UsePrivateHeaders net ; + +SEARCH_SOURCE += [ FDirName $(OBOS_TOP) src add-ons kernel network protocols raw ] ; + +Addon raw : userland network protocols : + raw.c +; + +LinkSharedOSLibs raw : root net_stack_tester ; diff --git a/src/tests/add-ons/kernel/network/protocols/tcp/Jamfile b/src/tests/add-ons/kernel/network/protocols/tcp/Jamfile new file mode 100644 index 0000000000..801b1633e4 --- /dev/null +++ b/src/tests/add-ons/kernel/network/protocols/tcp/Jamfile @@ -0,0 +1,16 @@ +SubDir OBOS_TOP src tests add-ons kernel network protocols tcp ; + +UsePrivateHeaders net ; + +SEARCH_SOURCE += [ FDirName $(OBOS_TOP) src add-ons kernel network protocols tcp ] ; + +Addon tcp : userland network protocols : + tcp.c + tcp_debug.c + tcp_input.c + tcp_output.c + tcp_timer.c + arc4random.c +; + +LinkSharedOSLibs tcp : root net_stack_tester ; diff --git a/src/tests/add-ons/kernel/network/protocols/udp/Jamfile b/src/tests/add-ons/kernel/network/protocols/udp/Jamfile new file mode 100644 index 0000000000..caa545165c --- /dev/null +++ b/src/tests/add-ons/kernel/network/protocols/udp/Jamfile @@ -0,0 +1,11 @@ +SubDir OBOS_TOP src tests add-ons kernel network protocols udp ; + +UsePrivateHeaders net ; + +SEARCH_SOURCE += [ FDirName $(OBOS_TOP) src add-ons kernel network protocols udp ] ; + +Addon udp : userland network protocols : + udp.c +; + +LinkSharedOSLibs udp : root net_stack_tester ; diff --git a/src/tests/add-ons/kernel/network/userland_ipc.c b/src/tests/add-ons/kernel/network/userland_ipc.c new file mode 100644 index 0000000000..ad7f02f62a --- /dev/null +++ b/src/tests/add-ons/kernel/network/userland_ipc.c @@ -0,0 +1,541 @@ +/* userland_ipc - Communication between the network driver +** and the userland stack. +** +** Initial version by Axel Dörfler, axeld@pinc-software.de +** This file may be used under the terms of the OpenBeOS License. +*/ + + +#include "userland_ipc.h" + +#include "sys/socket.h" +#include "net_misc.h" +#include "core_module.h" +#include "net_module.h" +#include "sys/sockio.h" + +#include +#include +#include + + +extern struct core_module_info *core; + +// installs a main() +//#define COMMUNICATION_TEST + +#define NUM_COMMANDS 32 +#define CONNECTION_BUFFER_SIZE (65536 + 4096 - CONNECTION_COMMAND_SIZE) + +#define ROUND_TO_PAGE_SIZE(x) (((x) + (B_PAGE_SIZE) - 1) & ~((B_PAGE_SIZE) - 1)) + +struct socket; /* forward declaration */ + +typedef struct { + port_id localPort,port; + area_id area; + struct socket * socket; + + uint8 *buffer; + net_command *commands; + sem_id commandSemaphore; + + int32 openFlags; + + thread_id runner; + + // for socket select events support + port_id socket_event_port; + void * notify_cookie; +} connection_cookie; + + +port_id gStackPort = -1; +thread_id gConnectionOpener = -1; + +// prototypes +static int32 connection_runner(void *_cookie); +static status_t init_connection(net_connection *connection, connection_cookie **_cookie); +static void shutdown_connection(connection_cookie *cookie); + + +static void +delete_cloned_areas(net_area_info *area) +{ + int32 i; + for (i = 0;i < MAX_NET_AREAS;i++) { + if (area[i].id == 0) + continue; + + delete_area(area[i].id); + } +} + + +static status_t +clone_command_areas(net_area_info *localArea,net_command *command) +{ + int32 i; + + memset(localArea,0,sizeof(net_area_info) * MAX_NET_AREAS); + + for (i = 0;i < MAX_NET_AREAS;i++) { + if (command->area[i].id <= 0) + continue; + + localArea[i].id = clone_area("net connection",(void **)&localArea[i].offset,B_ANY_ADDRESS, + B_READ_AREA | B_WRITE_AREA,command->area[i].id); + if (localArea[i].id < B_OK) + return localArea[i].id; + } + return B_OK; +} + + +static uint8 * +convert_address(net_area_info *fromArea,net_area_info *toArea,uint8 *data) +{ + if (data == NULL) + return NULL; + + if (data < fromArea->offset) { + printf("could not translate address: %p\n",data); + return data; + } + + return data - fromArea->offset + toArea->offset; +} + + +static inline void * +convert_to_local(net_area_info *foreignArea,net_area_info *localArea,void *data) +{ + return convert_address(foreignArea,localArea,data); +} + + +static void * +convert_to_foreign(net_area_info *foreignArea,net_area_info *localArea,void *data) +{ + return convert_address(localArea,foreignArea,data); +} + + +static void +on_socket_event(void * socket, uint32 event, void * cookie) +{ + connection_cookie * cc = (connection_cookie *) cookie; + struct socket_event_data sed; + status_t status; + + if (!cc) + return; + + if (cc->socket != socket) { + printf("on_socket_event(%p, %ld, %p): socket is higly suspect! Aborting.\n", socket, event, cookie); + return; + } + + printf("on_socket_event(%p, %ld, %p)\n", socket, event, cookie); + + sed.event = event; + sed.cookie = cc->notify_cookie; + + // TODO: don't block here => write_port_etc() ? + status = write_port(cc->socket_event_port, NET_STACK_SOCKET_EVENT_NOTIFICATION, + &sed, sizeof(sed)); + if (status != B_OK) + printf("write_port(NET_STACK_SOCKET_EVENT_NOTIFICATION) failure: %s\n", + strerror(status)); + return; +} + + + +static int32 +connection_runner(void *_cookie) +{ + connection_cookie *cookie = (connection_cookie *)_cookie; + bool run = true; + + while (run) { + net_area_info area[MAX_NET_AREAS]; + net_command *command; + status_t status = B_OK; + struct stack_driver_args *args; + int32 index; + ssize_t bytes = read_port(cookie->localPort,&index,NULL,0); + if (bytes < B_OK) + break; + + if (index >= NUM_COMMANDS || index < 0) { + printf("got bad command index: %lx\n",index); + continue; + } + command = cookie->commands + index; + if (clone_command_areas(area, command) < B_OK) { + printf("could not clone command areas!\n"); + continue; + } + + args = convert_to_local(&command->area[0],&area[0], command->data); + printf("command %lx (index = %ld), buffer = %p, length = %ld, result = %ld\n", + command->op, index, args, command->length, command->result); + + switch (command->op) { + case NET_STACK_OPEN: + cookie->openFlags = args->u.integer; + printf("opening socket, mode = %lx!\n", cookie->openFlags); + break; + + case NET_STACK_CLOSE: + printf("closing socket...\n"); + run = false; + break; + + case NET_STACK_SOCKET: + printf("open a socket... family = %d, type = %d, proto = %d\n", + args->u.socket.family, args->u.socket.type, args->u.socket.proto); + status = core->socket_init(&cookie->socket); + if (status == 0) + status = core->socket_create(cookie->socket, args->u.socket.family, args->u.socket.type, args->u.socket.proto); + break; + + case NET_STACK_GETSOCKOPT: + case NET_STACK_SETSOCKOPT: + if (command->op == NET_STACK_GETSOCKOPT) { + status = core->socket_getsockopt(cookie->socket, args->u.sockopt.level, args->u.sockopt.option, + convert_to_local(&command->area[1], &area[1], args->u.sockopt.optval), + (size_t *) &args->u.sockopt.optlen); + } else { + status = core->socket_setsockopt(cookie->socket, args->u.sockopt.level, args->u.sockopt.option, + (const void *) convert_to_local(&command->area[1], &area[1], args->u.sockopt.optval), + args->u.sockopt.optlen); + } + break; + + case NET_STACK_CONNECT: + case NET_STACK_BIND: + case NET_STACK_GETSOCKNAME: + case NET_STACK_GETPEERNAME: { + caddr_t addr = (caddr_t) convert_to_local(&command->area[1], &area[1], args->u.sockaddr.addr); + + switch (command->op) { + case NET_STACK_CONNECT: + status = core->socket_connect(cookie->socket, addr, args->u.sockaddr.addrlen); + break; + case NET_STACK_BIND: + status = core->socket_bind(cookie->socket, addr, args->u.sockaddr.addrlen); + break; + case NET_STACK_GETSOCKNAME: + status = core->socket_getsockname(cookie->socket, (struct sockaddr *) addr, &args->u.sockaddr.addrlen); + break; + case NET_STACK_GETPEERNAME: + status = core->socket_getpeername(cookie->socket,(struct sockaddr *) addr, &args->u.sockaddr.addrlen); + break; + } + break; + } + case NET_STACK_LISTEN: + status = core->socket_listen(cookie->socket, args->u.integer); + break; + + case NET_STACK_GET_COOKIE: + /* this is needed by accept() call, to be able to pass back + * in NET_STACK_ACCEPT opcode the cookie of the filedescriptor to + * use for the new accepted socket + */ + *((void **) args) = cookie; + break; + + case NET_STACK_ACCEPT: + { + connection_cookie *otherCookie = (connection_cookie *) args->u.accept.cookie; + status = core->socket_accept(cookie->socket, &otherCookie->socket, + convert_to_local(&command->area[1], &area[1], args->u.accept.addr), + &args->u.accept.addrlen); + } + case NET_STACK_SEND: + { + struct iovec iov; + int flags = 0; + + iov.iov_base = convert_to_local(&command->area[1], &area[1], args->u.transfer.data); + iov.iov_len = args->u.transfer.datalen; + + status = core->socket_writev(cookie->socket, &iov, flags); + break; + } + case NET_STACK_RECV: + { + struct iovec iov; + int flags = 0; + + iov.iov_base = convert_to_local(&command->area[1], &area[1], args->u.transfer.data); + iov.iov_len = args->u.transfer.datalen; + + /* flags gets ignored here... */ + status = core->socket_readv(cookie->socket, &iov, &flags); + break; + } + case NET_STACK_RECVFROM: + { + struct msghdr *msg = (struct msghdr *) args; + int received; + + msg->msg_name = convert_to_local(&command->area[1],&area[1],msg->msg_name); + msg->msg_iov = convert_to_local(&command->area[2],&area[2],msg->msg_iov); + msg->msg_control = convert_to_local(&command->area[3],&area[3],msg->msg_control); + + status = core->socket_recv(cookie->socket, msg, (caddr_t)&msg->msg_namelen,&received); + if (status == 0) + status = received; + + msg->msg_name = convert_to_foreign(&command->area[1],&area[1],msg->msg_name); + msg->msg_iov = convert_to_foreign(&command->area[2],&area[2],msg->msg_iov); + msg->msg_control = convert_to_foreign(&command->area[3],&area[3],msg->msg_control); + break; + } + case NET_STACK_SENDTO: + { + struct msghdr *msg = (struct msghdr *) args; + int sent; + + msg->msg_name = convert_to_local(&command->area[1],&area[1],msg->msg_name); + msg->msg_iov = convert_to_local(&command->area[2],&area[2],msg->msg_iov); + msg->msg_control = convert_to_local(&command->area[3],&area[3],msg->msg_control); + + status = core->socket_send(cookie->socket,msg,msg->msg_flags,&sent); + if (status == 0) + status = sent; + + msg->msg_name = convert_to_foreign(&command->area[1],&area[1],msg->msg_name); + msg->msg_iov = convert_to_foreign(&command->area[2],&area[2],msg->msg_iov); + msg->msg_control = convert_to_foreign(&command->area[3],&area[3],msg->msg_control); + break; + } + + case NET_STACK_NOTIFY_SOCKET_EVENT: + { + struct notify_socket_event_args *nsea = (struct notify_socket_event_args *) args; + + cookie->socket_event_port = nsea->notify_port; + cookie->notify_cookie = nsea->cookie; + + if (cookie->socket_event_port != -1) + // start notify socket event + status = core->socket_set_event_callback(cookie->socket, on_socket_event, cookie, 0); + else + // stop notify socket event + status = core->socket_set_event_callback(cookie->socket, NULL, NULL, 0); + break; + } + + case NET_STACK_SYSCTL: + { + status = core->net_sysctl(convert_to_local(&command->area[1],&area[1], args->u.sysctl.name), + args->u.sysctl.namelen, convert_to_local(&command->area[2],&area[2],args->u.sysctl.oldp), + convert_to_local(&command->area[3],&area[3],args->u.sysctl.oldlenp), + convert_to_local(&command->area[4],&area[4],args->u.sysctl.newp), + args->u.sysctl.newlen); + break; + } + + case NET_STACK_STOP: + core->stop(); + break; + + case B_SET_BLOCKING_IO: + cookie->openFlags &= ~O_NONBLOCK; + break; + + case B_SET_NONBLOCKING_IO: + cookie->openFlags |= O_NONBLOCK; + break; + + case OSIOCGIFCONF: + case SIOCGIFCONF: + { + struct ifconf *ifc = (struct ifconf *) args; + ifc->ifc_buf = convert_to_local(&command->area[1], &area[1], ifc->ifc_buf); + + status = core->socket_ioctl(cookie->socket, command->op, (char *) args); + + ifc->ifc_buf = convert_to_foreign(&command->area[1], &area[1], ifc->ifc_buf); + break; + } + + default: + status = core->socket_ioctl(cookie->socket,command->op, (char *) args); + break; + } + // mark the command as done + command->result = status; + command->op = 0; + delete_cloned_areas(area); + + // notify the command pipeline that we're done with the command + release_sem(cookie->commandSemaphore); + } + + cookie->runner = -1; + shutdown_connection(cookie); + + return 0; +} + + +static status_t +init_connection(net_connection *connection,connection_cookie **_cookie) +{ + connection_cookie *cookie; + net_command *commands; + + cookie = (connection_cookie *)malloc(sizeof(connection_cookie)); + if (cookie == NULL) { + fprintf(stderr,"couldn't allocate memory for cookie.\n"); + return B_NO_MEMORY; + } + + connection->area = create_area("net connection",(void *)&commands,B_ANY_ADDRESS, + CONNECTION_BUFFER_SIZE + CONNECTION_COMMAND_SIZE, + B_NO_LOCK,B_READ_AREA | B_WRITE_AREA); + if (connection->area < B_OK) { + fprintf(stderr,"couldn't create area: %s.\n",strerror(connection->area)); + free(cookie); + return connection->area; + } + memset(commands,0,NUM_COMMANDS * sizeof(net_command)); + + connection->port = create_port(CONNECTION_QUEUE_LENGTH,"net stack connection"); + if (connection->port < B_OK) { + fprintf(stderr,"couldn't create port: %s.\n",strerror(connection->port)); + delete_area(connection->area); + free(cookie); + return connection->port; + } + + connection->commandSemaphore = create_sem(0,"net command queue"); + if (connection->commandSemaphore < B_OK) { + fprintf(stderr,"couldn't create semaphore: %s.\n",strerror(connection->commandSemaphore)); + delete_area(connection->area); + delete_port(connection->port); + free(cookie); + return connection->commandSemaphore; + } + + cookie->runner = spawn_thread(connection_runner,"connection runner",B_NORMAL_PRIORITY,cookie); + if (cookie->runner < B_OK) { + fprintf(stderr,"couldn't create thread: %s.\n",strerror(cookie->runner)); + delete_sem(connection->commandSemaphore); + delete_area(connection->area); + delete_port(connection->port); + free(cookie); + return B_ERROR; + } + + connection->numCommands = NUM_COMMANDS; + connection->bufferSize = CONNECTION_BUFFER_SIZE; + + // setup connection cookie + cookie->area = connection->area; + cookie->commands = commands; + cookie->buffer = (uint8 *)commands + CONNECTION_COMMAND_SIZE; + cookie->commandSemaphore = connection->commandSemaphore; + cookie->localPort = connection->port; + cookie->openFlags = 0; + + cookie->socket_event_port = -1; + cookie->notify_cookie = NULL; + + resume_thread(cookie->runner); + + *_cookie = cookie; + return B_OK; +} + + +static void +shutdown_connection(connection_cookie *cookie) +{ + printf("free cookie: %p\n",cookie); + kill_thread(cookie->runner); + + delete_port(cookie->localPort); + delete_sem(cookie->commandSemaphore); + delete_area(cookie->area); + + free(cookie); +} + + +static int32 +connection_opener(void *_unused) +{ + while(true) { + port_id port; + int32 msg; + ssize_t bytes = read_port(gStackPort,&msg,&port,sizeof(port_id)); + if (bytes < B_OK) + return bytes; + + if (msg == NET_STACK_NEW_CONNECTION) { + net_connection connection; + connection_cookie *cookie; + + printf("incoming connection...\n"); + if (init_connection(&connection,&cookie) == B_OK) + write_port(port,NET_STACK_NEW_CONNECTION,&connection,sizeof(net_connection)); + } else + fprintf(stderr,"connection_opener: received unknown command: %lx (expected = %lx)\n",msg,(int32)NET_STACK_NEW_CONNECTION); + } + return 0; +} + + +status_t +init_userland_ipc(void) +{ + gStackPort = create_port(CONNECTION_QUEUE_LENGTH,NET_STACK_PORTNAME); + if (gStackPort < B_OK) + return gStackPort; + + gConnectionOpener = spawn_thread(connection_opener,"connection opener",B_NORMAL_PRIORITY,NULL); + if (resume_thread(gConnectionOpener) < B_OK) { + delete_port(gStackPort); + if (gConnectionOpener >= B_OK) { + kill_thread(gConnectionOpener); + return B_BAD_THREAD_STATE; + } + return gConnectionOpener; + } + + return B_OK; +} + + +void +shutdown_userland_ipc(void) +{ + delete_port(gStackPort); + kill_thread(gConnectionOpener); +} + + +#ifdef COMMUNICATION_TEST +int +main(void) +{ + char buffer[8]; + + if (init_userland_ipc() < B_OK) + return -1; + + puts("Userland_ipc - test is running. Press to quit."); + fgets(buffer,sizeof(buffer),stdin); + + shutdown_userland_ipc(); + + return 0; +} +#endif /* COMMUNICATION_TEST */ diff --git a/src/tests/add-ons/kernel/network/userland_modules.cpp b/src/tests/add-ons/kernel/network/userland_modules.cpp new file mode 100644 index 0000000000..d811326ea8 --- /dev/null +++ b/src/tests/add-ons/kernel/network/userland_modules.cpp @@ -0,0 +1,869 @@ +/* Userland modules emulation support +*/ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ASSERT(condition) if (!(condition)) { debugger("Assertion failed!"); } + +typedef enum { + MODULE_LOADED = 0, + MODULE_INITING, + MODULE_READY, + MODULE_UNINITING, + MODULE_ERROR +} module_state; + +typedef struct module { + struct module * next; + uint32 id; + char * name; + module_info * info; + struct module_addon * addon; // the module addon this module live in + // if NULL, builtin module addon + int32 ref_count; // reference count of get_module() made on this module + bool keep_loaded; + module_state state; +} module; + +typedef struct module_addon { + struct module_addon * next; + int32 ref_count; // reference count of get_module() made using this addon + bool keep_loaded; + char * path; + image_id addon_image; // if -1, not loaded in memory currently + module_info ** infos; // valid only when addon_image != -1 +} module_addon; + +typedef struct module_list_cookie { + char * prefix; + char * search_paths; + char * search_path; + char * next_path_token; + BList * dir_stack; + module_addon * ma; // current module addon looked up + module_info ** mi; // current module addon module info +} module_list_cookie; + +#define LOCK_MODULES acquire_sem(g_modules_lock) +#define UNLOCK_MODULES release_sem(g_modules_lock) + +// local prototypes +// ------------------ + +static module * search_module(const char * name); +static status_t init_module(module * m); +static status_t uninit_module(module * m); +static module * find_loaded_module_by_name(const char * name); +static module * find_loaded_module_by_id(uint32 id); + +static module_addon * load_module_addon(const char * path); +static status_t unload_module_addon(module_addon * ma); + +// globals +// ------------------ + +static sem_id g_modules_lock = -1; // One lock for rule them all, etc... +static module * g_modules = NULL; +static module_addon * g_module_addons = NULL; +static int32 g_next_module_id = 1; + + +// Public routines +// --------------- + +extern "C" { + +_EXPORT status_t get_module(const char * name, module_info ** mi) +{ + status_t status; + module * m; + + // printf("get_module(%s)\n", name); + + m = find_loaded_module_by_name(name); + if (!m) + m = search_module(name); + + if (!m) + return B_NAME_NOT_FOUND; + + *mi = m->info; + + status = B_OK; + + if (m->addon) // built-in modules don't comes from addon... + atomic_add(&m->addon->ref_count, 1); + + if (atomic_add(&m->ref_count, 1) == 0) { + // first time we reference this module, so let's init it: + status = init_module(m); + if (status != B_OK) { + printf("Failed to init module %s: %s.\n", m->name, strerror(status)); + unload_module_addon(m->addon); // unload the module addon... + }; + }; + + return status; +} + +_EXPORT status_t put_module(const char * name) +{ + module * m; + + // printf("put_module(%s)\n", name); + + m = find_loaded_module_by_name(name); + if (!m) + // Hum??? Sorry, this module name was never get_module()'d + return B_NAME_NOT_FOUND; + + if (atomic_add(&m->ref_count, -1) <= 1) + // this module is no more used... + uninit_module(m); + + if (!m->addon) + // built-in modules are module addon less... + return B_OK; + + if (atomic_add(&m->addon->ref_count, -1) > 1) + // Still other module(s) using this module addon + return B_OK; + + // okay, this module addon is no more used + // let's free up some memory + return unload_module_addon(m->addon); +} + + +_EXPORT status_t get_next_loaded_module_name(uint32 *cookie, char *buf, size_t *bufsize) +{ + module * m; + status_t status; + + if (buf == NULL && bufsize == NULL) + return B_BAD_VALUE; + + LOCK_MODULES; + + if (*cookie == 0) + // first call expected value + m = g_modules; + else { + // find last loaded module returned, and seek to next one + m = (module *) find_loaded_module_by_id((int) *cookie); + if (m) + m = m->next; + }; + + // find next loaded module + while (m) { + if (m->ref_count) + break; + m = m->next; + }; + + status = B_OK; + if (m) { + ASSERT(m->info); + if (buf != NULL) + strncpy(buf, m->info->name, *bufsize); + else + *bufsize = strlen(m->info->name + 1); + *cookie = m->id; + } else + status = B_BAD_INDEX; + + UNLOCK_MODULES; + + return status; +} + + +_EXPORT void * open_module_list(const char *prefix) +{ + module_list_cookie * mlc; + char * addon_path; + + if (prefix == NULL) + return NULL; + + mlc = (module_list_cookie *) malloc(sizeof(*mlc)); + mlc->prefix = strdup(prefix); + + addon_path = getenv("ADDON_PATH"); + mlc->search_paths = (addon_path ? strdup(addon_path) : NULL); + mlc->search_path = strtok_r(mlc->search_paths, ":", &mlc->next_path_token); + mlc->dir_stack = new BList(); + + mlc->ma = NULL; + mlc->mi = NULL; + + return mlc; +} + + +_EXPORT status_t read_next_module_name(void *cookie, char *buf, size_t *bufsize) +{ + module_list_cookie * mlc = (module_list_cookie *) cookie; + + if (!bufsize) + return B_BAD_VALUE; + + if (!mlc) + return B_BAD_VALUE; + + /* Okay, take some time to understand how this function works! + Basicly, we iterate thru: + - each searchable add-ons path root + - each (sub-)directory under the current add-ons path root + - each module add-on file in the current (sub-)directory + - each module name published by current module add-on + + As the iteration involve sub-directory walks, we use recursive calls. + Sorry if this code sounds too complex... + */ + + if (mlc->ma && mlc->mi) { + // we have a module addon still loaded from a last call + // so keep looking at his exported module names list + while (*mlc->mi) { + module_info * mi = *mlc->mi; + mlc->mi++; + if(strstr(mi->name, mlc->prefix)) { + // We find a matching module name. At least. Yeah!!! + if (buf) strncpy(buf, mi->name, *bufsize); + *bufsize = strlen(mi->name); + return B_OK; + }; + }; + + // We've iterate all module names of this module addon. Find another one... + atomic_add(&mlc->ma->ref_count, -1); + unload_module_addon(mlc->ma); + mlc->ma = NULL; + mlc->mi = NULL; + }; + + // Iterate all searchable add-ons paths + while (mlc->search_path) { + BDirectory * dir; + BEntry entry; + BPath path; + status_t status; + + // Get current directory + dir = (BDirectory *) mlc->dir_stack->LastItem(); + if (!dir) { + // find add-ons root directory in this search path + if (strncmp(mlc->search_path, "%A/", 3) == 0) { + // resolve "%A/..." path + app_info ai; + + be_app->GetAppInfo(&ai); + entry.SetTo(&ai.ref); + entry.GetPath(&path); + path.GetParent(&path); + path.Append(mlc->search_path + 3); + } else { + path.SetTo(mlc->search_path); + }; + + // We look *only* under prefix-matching sub-path + path.Append(mlc->prefix); + + // printf("Looking module(s) in %s/%s...\n", mlc->search_path, mlc->prefix); + + dir = new BDirectory(path.Path()); + if (dir) + mlc->dir_stack->AddItem(dir); + }; + + // Iterate current directory content + if (dir) { + while (dir->GetNextEntry(&entry) == B_OK) { + entry.GetPath(&path); + // printf(" %s ?\n", path.Path()); + + if (entry.IsDirectory()) { + BDirectory * subdir; + // push this directory on dir_stack + subdir = new BDirectory(path.Path()); + if (!subdir) + continue; + + mlc->dir_stack->AddItem(subdir); + // recursivly search this sub-directory + return read_next_module_name(cookie, buf, bufsize); + }; + + if (entry.IsFile() || entry.IsSymLink()) { + mlc->ma = load_module_addon(path.Path()); + if (!mlc->ma) + // Oh-oh, not a loadable module addon!? + // WTF it's doing there?!? + continue; + + atomic_add(&mlc->ma->ref_count, 1); + // call ourself to enter the module names list iteration at + // function begining code... + mlc->mi = mlc->ma->infos; + return read_next_module_name(cookie, buf, bufsize); + }; + }; + + // We walk thru all this directory content, go back to parent + status = mlc->dir_stack->RemoveItem(dir); + delete dir; + }; + + if (!mlc->dir_stack->IsEmpty()) + continue; + + // We walk thru all this search path content, next now + mlc->search_path = strtok_r(NULL, ":", &mlc->next_path_token); + }; + + // Module(s) list search done, ending... + return B_ERROR; +} + + +_EXPORT status_t close_module_list(void *cookie) +{ + module_list_cookie * mlc = (module_list_cookie *) cookie; + BDirectory * dir; + + ASSERT(mlc); + ASSERT(mlc->prefix); + + if (mlc->ma) { + atomic_add(&mlc->ma->ref_count, -1); + unload_module_addon(mlc->ma); + }; + + while((dir = (BDirectory *) mlc->dir_stack->FirstItem())) { + mlc->dir_stack->RemoveItem(dir); + delete dir; + }; + + delete mlc->dir_stack; + + free(mlc->search_paths); + free(mlc->prefix); + free(mlc); + + return B_ERROR; +} + +// #pragma mark - +// Some KernelExport.h support from userland + +_EXPORT void dprintf(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); +} + + +_EXPORT void kprintf(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); +} + + +_EXPORT status_t load_driver_symbols(char *driver_name) +{ + // Userland debugger will extract symbols itself... + return B_OK; +} + + +_EXPORT thread_id spawn_kernel_thread(thread_entry func, const char *name, long priority, void *arg) +{ + return spawn_thread(func, name, priority, arg); +} + + + +_EXPORT int send_signal_etc(pid_t thid, uint sig, uint32 flags) +{ + return send_signal(thid, sig); +} + + +} // extern "C" + + +// #pragma mark - +// Private routines + +static module_addon * load_module_addon(const char * path) +{ + module_addon * ma; + image_id addon_id; + module_info ** mi; + status_t status; + + ASSERT(path); + + addon_id = load_add_on(path); + if (addon_id < 0) { + printf("Failed to load %s addon: %s.\n", path, strerror(addon_id)); + return NULL; + }; + + // printf("Addon %s loaded.\n", path); + + ma = NULL; + + status = get_image_symbol(addon_id, "modules", B_SYMBOL_TYPE_DATA, (void **) &mi); + if (status != B_OK) { + // No "modules" symbol found in this addon + printf("Symbol \"modules\" not found in %s addon: not a module addon!\n", path); + goto error; + }; + + ma = (module_addon *) malloc(sizeof(*ma)); + if (!ma) + // Gasp: not enough memory! + goto error; + + LOCK_MODULES; + + ma->ref_count = 0; + ma->keep_loaded = false; + ma->path = strdup(path); + ma->addon_image = addon_id; + ma->infos = mi; + + while(*mi) { + module * m; + + m = (module *) malloc(sizeof(*m)); + if (!m) + // Gasp, again: not enough memory! + goto error; + + m->ref_count = 0; + m->id = atomic_add(&g_next_module_id, 1); + m->info = (*mi); + m->name = strdup(m->info->name); + m->addon = ma; + m->keep_loaded = (m->info->flags & B_KEEP_LOADED) ? true : false; + + m->state = MODULE_LOADED; + + m->next = g_modules; + g_modules = m; + + mi++; + }; + + // add this module addon to the list + ma->next = g_module_addons; + g_module_addons = ma; + + UNLOCK_MODULES; + + return ma; + +error: + printf("Error while load_module_addon(%s)\n", path); + + if (ma) { + // remove any appended modules by this module addon until we got error... + module * prev; + module * m; + + prev = NULL; + m = g_modules; + while (m) { + if (m->addon == ma) { + module * tmp = m; + + m = tmp->next; + + if (prev) + prev->next = tmp->next; + else + g_modules = tmp->next; + + if (tmp->name) + free(tmp->name); + free(tmp); + continue; + }; + + prev = m; + m = m->next; + }; + + + UNLOCK_MODULES; + + if (ma->path) + free(ma->path); + free(ma); + }; + + unload_add_on(addon_id); + // printf("Addon %s unloaded.\n", path); + return NULL; +} + +static status_t unload_module_addon(module_addon * ma) +{ + module * m; + module * prev; + status_t status; + + if (!ma) + // built-in modules are addon-less, so nothing to do... + return B_OK; + + if (ma->keep_loaded) { + printf("B_KEEP_LOADED flag set for %s module addon. Will be *never* unloaded!\n", + ma->path); + return B_OK; + }; + + if (ma->ref_count) + // still someone needing this module addon, it seems? + return B_OK; + + if (ma->addon_image < 0) + // built-in addon, it seems... + return B_OK; + + status = unload_add_on(ma->addon_image); + if (status != B_OK) { + printf("Failed to unload %s addon: %s.\n", ma->path, strerror(status)); + return status; + }; + // printf("Addon %s unloaded.\n", ma->path); + + LOCK_MODULES; + + // remove the modules coming from this module addon from g_modules list + prev = NULL; + m = g_modules; + while (m) { + if (m->addon == ma) { + module * tmp = m; + + m = tmp->next; + + if (prev) + prev->next = tmp->next; + else + g_modules = tmp->next; + + if (tmp->name) + free(tmp->name); + free(tmp); + continue; + }; + + prev = m; + m = m->next; + }; + + // remove the module addon from g_module_addons list: + if (g_module_addons == ma) + g_module_addons = ma->next; + else { + module_addon * tmp; + tmp = g_module_addons; + while (tmp && tmp->next != ma) + tmp = tmp->next; + + ASSERT(tmp); + tmp->next = ma->next; + }; + + if (ma->path) + free(ma->path); + free(ma); + + UNLOCK_MODULES; + + return B_OK; +} + + +static module * search_module(const char * name) +{ + BPath path; + BPath addons_path; + BEntry entry; + module * found_module; + char * search_paths; + char * search_path; + char * next_path_token; + + // printf("search_module(%s):\n", name); + + search_paths = getenv("ADDON_PATH"); + if (!search_paths) + // Nowhere to search addons!!! + return NULL; + + search_paths = strdup(search_paths); + search_path = strtok_r(search_paths, ":", &next_path_token); + + found_module = NULL; + while (search_path && found_module == NULL) { + if (strncmp(search_path, "%A/", 3) == 0) { + // compute "%A/..." path + app_info ai; + + be_app->GetAppInfo(&ai); + entry.SetTo(&ai.ref); + entry.GetPath(&addons_path); + addons_path.GetParent(&addons_path); + addons_path.Append(search_path + 3); + } else { + addons_path.SetTo(search_path); + }; + + // printf("Looking into %s\n", search_path); + + path.SetTo(addons_path.Path()); + path.Append(name); + + while(path != addons_path) { + // printf(" %s ?\n", path.Path()); + entry.SetTo(path.Path()); + if (entry.IsFile() || entry.IsSymLink()) { + module_addon * ma; + + // try to load the module addon + ma = load_module_addon(path.Path()); + if (ma) { + found_module = find_loaded_module_by_name(name); + if (found_module) + break; + + unload_module_addon(ma); + }; // if (ma) + }; // if (entry.IsFile() || entry.IsSymLink()) + + // okay, remove the current path leaf and try again... + path.GetParent(&path); + }; + + search_path = strtok_r(NULL, ":", &next_path_token); + }; + + free(search_paths); + +/* + if (found_module) + printf(" Found it in %s addon module!\n", + found_module->addon ? found_module->addon->path : "BUILTIN"); +*/ + + return found_module; +} + + +static status_t init_module(module * m) +{ + status_t status; + + ASSERT(m); + + switch (m->state) { + case MODULE_LOADED: + m->state = MODULE_INITING; + ASSERT(m->info); + // printf("Initing module %s... ", m->name); + status = m->info->std_ops(B_MODULE_INIT); + // printf("done (%s).\n", strerror(status)); + m->state = (status == B_OK) ? MODULE_READY : MODULE_LOADED; + + if (m->state == MODULE_READY && m->keep_loaded && m->addon) { + // one module (at least) was inited and request to never being + // unload from memory, so keep the corresponding addon loaded + // printf("module %s set B_KEEP_LOADED flag:\nmodule addon %s will never be unloaded!\n", + // m->name, m->addon->path); + m->addon->keep_loaded = true; + }; + break; + + case MODULE_READY: + status = B_OK; + break; + + case MODULE_INITING: // circular reference!!! + case MODULE_UNINITING: // initing a module currently unloading... + case MODULE_ERROR: // module failed to unload previously... + default: // Unknown module state!!! + status = B_ERROR; + break; + }; + + return status; +} + + +static status_t uninit_module(module * m) +{ + status_t status; + + ASSERT(m); + + switch (m->state) { + case MODULE_READY: + m->state = MODULE_UNINITING; + ASSERT(m->info); + // printf("Uniniting module %s... ", m->name); + status = m->info->std_ops(B_MODULE_UNINIT); + // printf("done (%s).\n", strerror(status)); + m->state = (status == B_OK) ? MODULE_LOADED : MODULE_ERROR; + break; + + case MODULE_LOADED: + // No need to uninit it, all is fine so. + status = B_OK; + break; + + case MODULE_INITING: // uniniting while initializing + case MODULE_UNINITING: // uniniting already pending + case MODULE_ERROR: // module failed previously... + default: // Unknown module state!!! + status = B_ERROR; + break; + }; + + return status; +} + + +static module * find_loaded_module_by_name(const char * name) +{ + module * m; + + LOCK_MODULES; + + m = g_modules; + while (m) { + if (strcmp(name, m->name) == 0) + break; + m = m->next; + }; + + UNLOCK_MODULES; + return m; +} + + +static module * find_loaded_module_by_id(uint32 id) +{ + module * m; + + LOCK_MODULES; + + m = g_modules; + while (m) { + if (m->id == id) + break; + m = m->next; + }; + + UNLOCK_MODULES; + return m; +} + +#if 0 +// #pragma mark - + +#define NET_CORE_MODULE_NAME "network/core/v1" +#define NET_ETHERNET_MODULE_NAME "network/interfaces/ethernet" +#define NET_IPV4_MODULE_NAME "network/protocols/ipv4/v1" + +#define MODULE_LIST_PREFIX "network" + +int main(int argc, char **argv) +{ + module_info * core; + module_info * ethernet; + module_info * ipv4; + char module_name[256]; + uint32 cookie; + size_t sz; + void * ml_cookie; + + new BApplication("application/x-vnd-OBOS-net_server"); + + printf("open_module_list(%s):\n", MODULE_LIST_PREFIX); + ml_cookie = open_module_list(MODULE_LIST_PREFIX); + sz = sizeof(module_name); + while(read_next_module_name(ml_cookie, module_name, &sz) == B_OK) { + if (strlen(module_name)) + printf(" %s\n", module_name); + sz = sizeof(module_name); + }; + close_module_list(ml_cookie); + printf("close_module_list()\n"); + // return 0; + + core = NULL; + get_module(NET_CORE_MODULE_NAME, (module_info **) &core); + + ethernet = NULL; + get_module(NET_ETHERNET_MODULE_NAME, (module_info **) ðernet); + + ipv4 = NULL; + get_module(NET_IPV4_MODULE_NAME, (module_info **) &ipv4); + + printf("get_next_loaded_module_name() test:\n"); + cookie = 0; + sz = sizeof(module_name); + while (get_next_loaded_module_name(&cookie, module_name, &sz) == B_OK) + printf("%ld: %s\n", cookie, module_name); + + if (ipv4) + put_module(NET_IPV4_MODULE_NAME); + + if (ethernet) + put_module(NET_ETHERNET_MODULE_NAME); + + if (core) + put_module(NET_CORE_MODULE_NAME); + + printf("get_next_loaded_module_name() test:\n"); + cookie = 0; + sz = sizeof(module_name); + while (get_next_loaded_module_name(&cookie, module_name, &sz) == B_OK) + printf("%ld: %s\n", cookie, module_name); + + delete be_app; + return 0; +} +#endif + +