Commit Diff


commit - 074798ed626bafae8efcea1413bf3f9571338d70
commit + 48f773b3abbc8524ddb57b4a6a6d37a0749deac6
blob - 5cf184209f166fd81670f72bb612aa7a92629697
blob + 288ac9dbb72a00d89672c8a551b4ce624a7268c2
--- .gitea/workflows/jobs.yaml
+++ .gitea/workflows/jobs.yaml
@@ -14,7 +14,7 @@ jobs:
       - name: Install dependencies
         run: |
           sudo apt-get update
-          sudo apt-get install -y libbsd-dev cmake build-essential
+          sudo apt-get install -y libbsd-dev cmake build-essential netcat
 
       - name: Build and test
         run: |
blob - c3157f7e765fc8a62f20693def316be0b5739d91
blob + 221cf5558f79da78e54a00bb1e33cb3b7b840279
--- .gitignore
+++ .gitignore
@@ -5,4 +5,7 @@ compile_commands.json
 site
 libflint.so
 test
+tcptest
+testrunner
 .idea
+netmanual
blob - a8dc82399ae04425657d6b38ea7d34122bda06b6
blob + fa09448298f620bf1b6c9500a71ad2f59b8ff92e
--- CMakeLists.txt
+++ CMakeLists.txt
@@ -2,6 +2,10 @@ cmake_minimum_required(VERSION 3.17)
 project(flint C)
 set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
 
+if ((${CMAKE_SYSTEM_NAME} STREQUAL "Linux"))
+    add_compile_definitions(flint __USE_XOPEN_EXTENDED)
+endif()
+
 set(CMAKE_C_STANDARD 99)
 include_directories(include)
 
@@ -17,16 +21,17 @@ set(SOURCES
         src/utility.c
         src/crypto.c
         src/parsing.c
+        src/network.c
 )
 
 if ((${CMAKE_SYSTEM_NAME} STREQUAL "Darwin"))
-    add_library(flint ${SOURCES} src/macos/macos.c)
+    add_library(flint ${SOURCES} src/macos.c)
 else()
     add_library(flint ${SOURCES})
 endif()
 
 if ((${CMAKE_SYSTEM_NAME} STREQUAL "Linux"))
-    target_link_libraries(flint bsd)
+    target_link_libraries(flint pthread bsd)
 endif()
 
 if(${CMAKE_PROJECT_NAME} STREQUAL flint)
@@ -34,8 +39,17 @@ if(${CMAKE_PROJECT_NAME} STREQUAL flint)
     target_include_directories(tests PRIVATE include)
 
     if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
-        target_link_libraries(tests flint bsd)
+        target_link_libraries(tests flint pthread bsd)
     else()
-        target_link_libraries(tests flint)
+        target_link_libraries(tests flint pthread)
     endif()
+
+    add_executable(netmanual tests/netmanual.c)
+    target_include_directories(netmanual PRIVATE include)
+
+    if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+        target_link_libraries(netmanual flint pthread bsd)
+    else()
+        target_link_libraries(netmanual flint pthread)
+    endif()
 endif()
blob - e4a25820995867a5ef353a5e246145fb255afef8 (mode 644)
blob + /dev/null
--- Makefile
+++ /dev/null
@@ -1,32 +0,0 @@
-.PHONY : clean
-
-CFLAGS =		-std=c99 -Iinclude -pedantic -Wall -Wextra
-LDFLAGS =		-fPIC -shared
-
-TARGET = 	libflint.so
-SRC !=		ls src/*.c
-OBJ =			$(SRC:./src/$.c=./obj/%.o)
-
-PREFIX =	$(DESTDIR)/usr/local
-LIBDIR =	$(PREFIX)/lib
-
-all: $(TARGET)
-
-$(TARGET): $(OBJ)
-	cc $(CFLAGS) $(LDFLAGS) -o $(TARGET) $(OBJ)
-
-./obj/%.o: ./src/%.c
-	cc $(CFLAGS) -c $< -o $@
-
-install: $(TARGET)
-	cp $(TARGET) $(LIBDIR)
-
-uninstall:
-	rm -f $(LIBDIR)/$(TARGET)
-
-clean:
-	rm -f $(TARGET)
-	rm -f test
-
-test:
-	cc $(CFLAGS) -o test tests/tests.c src/*.c
blob - /dev/null
blob + fbcb53112ec7ad10d51ee0c8136c63e0daa44f90 (mode 755)
--- /dev/null
+++ clanggen.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env sh
+
+set -e
+
+CFLAGS="-I./include -I/usr/local/include"
+
+rm -f compile_commands.json
+
+for f in $(find ./src -type f | grep -v macos); do
+  n=$(echo $f | grep -o '[^/]*$' | sed 's/c$/json/')
+  cc -MJ $n $CFLAGS -c $f
+done
+
+rm *.o
+sed -e '1s/^/[/' -e '$s/,$/]/' *.json > compile_commands.out
+rm *.json
+mv compile_commands.out compile_commands.json
blob - c8603550b60da781c8d7936d293d293916f1ce73 (mode 755)
blob + /dev/null
--- build.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/sh -e
-
-# For building outside of CLion
-
-mkdir -p build
-cd build
-cmake ..
-make
-cp compile_commands.json ..
-cd
blob - 8537d0e64d60f6afc713db24394f31ec71301cab
blob + dae3f84edc29eae7c81087250269466fff5d2326
--- docs/input.md
+++ docs/input.md
@@ -92,7 +92,8 @@ printf("%s\n", sp[0]); // Prints "Split"
 
 ### del_split
 
-Frees all memory used by `split()`. Just like `split`, it does not touch the original string
+Frees all memory used by `split()`. Just like `split`, it does not touch the original string.
+
 ```c
 void del_split(char **sp);
 
@@ -101,3 +102,19 @@ size_t sp_sz = 0;
 char **sp = split("Delete Me!", &sp_sz, " ");
 void del_split(sp);
 ```
+
+### capture_system
+
+Runs a command on the system shell and returns stdout as a string. `buffsize` is the size of 
+the returned buffer that holds `stdout`. Passing `0` to `buffsize` will use the default buffer size of `1024`.
+
+User is responsible for freeing the returned string.
+
+```c
+const char *capture_system(const char *cmd, int buffsize);
+
+/* Usage */
+const char *cap = capture_system("ls $HOME", 0);
+printf("%s\n", cap);
+free(cap);
+```
\ No newline at end of file
blob - /dev/null
blob + b47a96c84956411541fa5365cf203828337e9174 (mode 644)
--- /dev/null
+++ docs/network.md
@@ -0,0 +1,106 @@
+# network
+
+This module provides a generic `Server` type that abstracts away the setup and teardown of a socket
+
+## Enums
+
+### ServerType
+
+Types of servers. Currently supports TCP and UDP, will eventually add UNIX sockets.
+
+```c
+typedef enum ServerType {
+    SERVERTYPE_TCP,
+    SERVERTYPE_UDP
+} ServerType;
+```
+
+## Structs
+
+### Server
+
+Server is a generic abstraction over sockets. The type of the server is defined by `server_type`.
+
+```c
+typedef struct Server {
+    ServerType server_type;
+    int fd;
+    int port;
+    void (*handler)(struct Server *s);
+} Server;
+```
+
+## Functions
+
+### new_server
+
+Create a `Server*`. User is responsible for freeing the memory.
+
+```c
+Server *new_server(ServerType type, const char *port, void(handler)(Server *s));
+```
+
+### delete_server
+
+Frees the memory allocated for `Server*` and sets the pointer to `NULL`.
+
+```c
+void delete_server(Server *s);
+```
+
+### serve
+
+Starts up the server. `backlog_size` is the size of the backlog buffer for the underlying socket. Use the macro 
+`DEFAULT_BACKLOG_SIZE` or pass `0` to use a reasonable default.
+
+```c
+int serve(Server *s, int backlog_size);
+```
+
+### get_in_addr
+
+Convenience method to get an IP address from a `struct sockaddr_storage` of either IPV4 or IPV6. 
+
+```c
+void *get_in_addr(struct sockaddr *sa);
+
+/* Usage */
+struct sockaddr_storage client_addr;
+socklen_t client_addr_sz = sizeof(client_addr); 
+char buf[33];
+
+if (new_fd = accept(s->fd, (struct sockaddr *)&client_addr, &client_addr_sz) == -1) {
+    /* error handling */
+}
+inet_ntop(client_addr.ss_family, get_in_addr((struct sockaddr *)&client_addr), buf, 32);
+printf("Received connection from %s\n", buf);
+
+```
+
+### handler_tcp_echo
+
+An example handler for a multithreaded tcp echo server.
+
+```c
+void handler_tcp_echo(Server *s);
+
+/* Usage */
+#include "lfnetwork.h"
+
+int main(int argc, char **argv) {
+    Server *server = new_server(SERVERTYPE_TCP, "80", handler_tcp_echo);
+    serve(server, DEFAULT_BACKLOG);
+    delete_server(server);
+}
+```
+
+## Macros
+
+### DEFAULT_BACKLOG_SIZE
+
+A default size for the socket's backlog buffer. `5` is a standard default size, providing some backlog but not
+enough to make huge buffers for each socket
+
+```c
+#define DEFAULT_BACKLOG_SIZE 5
+```
\ No newline at end of file
blob - 53ecb0c1769a6ec9fa5362aa7b4717137f9cdd7c
blob + 3e5bc239140cb52650fb5c582e14275216e684a3
--- include/lfinput.h
+++ include/lfinput.h
@@ -17,4 +17,8 @@ void del_split(char **);
 
 void del_lines(char **);
 
+#define DEFAULT_CAPTURE_SYSTEM_BUFSIZE 1024
+
+const char *capture_system(const char *cmd, int buf_sz);
+
 #endif // LIBFLINT_INPUT_H
blob - /dev/null
blob + b4869f0bd0d8c622974b9284ac3623d794dedffb (mode 644)
--- /dev/null
+++ include/lfnetwork.h
@@ -0,0 +1,28 @@
+#ifndef LIBFLINT_NET_H
+#define LIBFLINT_NET_H
+
+#include <netdb.h>
+
+typedef enum ServerType {
+    SERVERTYPE_TCP,
+    SERVERTYPE_UDP
+} ServerType;
+
+typedef struct Server {
+    ServerType server_type;
+    int fd;
+    int port;
+    void (*handler)(struct Server *s);
+} Server;
+
+#define DEFAULT_BACKLOG 5
+
+Server *new_server(ServerType type, const char *port, void(handler)(Server *s));
+void delete_server(Server *s);
+int serve(Server *s, int backlog_size);
+void *get_in_addr(struct sockaddr *sa);
+
+// Example handlers
+void handler_tcp_echo(Server *s);
+
+#endif //LIBFLINT_NET_H
blob - /dev/null
blob + ba1a8603f7586d7eeb17a88cfc6ff4b521905727 (mode 755)
--- /dev/null
+++ run_tests.sh
@@ -0,0 +1,12 @@
+set -e
+
+#./testrunner
+./tcptest &
+tcpout=$(echo "hello" | nc localhost 18632)
+echo "tcpout: $tcpout" 
+if [ "$tcpout" != "TEST SEND" ]; then
+ echo "Error: \"$tcpout\" != \"TEST SEND\""
+ exit 1
+fi
+
+exit 0
blob - f20b8200a38aa43fd268e959f03dee2969515d13
blob + 23d54c9df9d8498a4e6c0e3b3fc2ff863ecb955e
--- src/input.c
+++ src/input.c
@@ -2,6 +2,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <limits.h>
+#include <errno.h>
 
 #ifdef __linux__
 
@@ -121,3 +122,18 @@ void del_split(char **sp) {
 void del_lines(char **lines) {
     del_split(lines);
 }
+
+const char *capture_system(const char *cmd, int buf_sz) {
+    if (buf_sz == 0) {
+        buf_sz = DEFAULT_CAPTURE_SYSTEM_BUFSIZE;
+    }
+    char *buf = malloc(buf_sz);
+    FILE *tmp = popen(cmd, "r");
+    if (tmp == NULL) {
+        fprintf(stderr, "libflint: failed to open FILE *tmp in capture_system. Errno: %d\n", errno);
+        free(buf);
+        return NULL;
+    }
+    fgets(buf, buf_sz, tmp);
+    return buf;
+}
\ No newline at end of file
blob - 3bbcf1c246a5b5475ef1fb51e62831f676c6dcf6 (mode 644)
blob + /dev/null
--- src/macos/macos.c
+++ /dev/null
@@ -1,82 +0,0 @@
-#include <libproc.h>
-#include <time.h>
-#include <mach/mach_time.h>
-#include <sys/errno.h>
-#include <stdlib.h>
-
-#include "lfmacos.h"
-
-#define NS_TO_SECONDS 1000000000.0
-#define NEW_PROCESS_SENTINEL (-1.0)
-
-ProcessData *new_ProcessData() {
-    ProcessData *pd = malloc(sizeof(ProcessData));
-    pd->last_total_consumed = NEW_PROCESS_SENTINEL;
-    return pd;
-}
-
-int update_process(pid_t pid, ProcessData *proc) {
-    struct proc_taskinfo taskinfo;
-    const int r = proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &taskinfo, PROC_PIDTASKINFO_SIZE);
-    if (r != PROC_PIDTASKINFO_SIZE) {
-        return 1;
-    }
-
-    mach_timebase_info_data_t info;
-    mach_timebase_info(&info);
-    const double ns_per_tick = (double)info.numer / (double)info.denom;
-
-    time(&(proc->timestamp));
-    proc->total_kernel_time = (taskinfo.pti_total_system * ns_per_tick) / NS_TO_SECONDS;
-    proc->total_user_time = (taskinfo.pti_total_user * ns_per_tick) / NS_TO_SECONDS;
-    proc->virtual_memory = taskinfo.pti_virtual_size;
-    proc->resident_memory = taskinfo.pti_resident_size;
-
-    if (proc->last_total_consumed != NEW_PROCESS_SENTINEL) {
-        time_t t = proc->timestamp - proc->last_timestamp;
-        proc->percent_cpu = 100.0 * (proc->total_user_time + proc->total_kernel_time - proc->last_total_consumed) / t;
-    } else {
-        proc->percent_cpu = 0.0;
-    }
-
-    proc->last_timestamp = proc->timestamp;
-    proc->last_total_consumed = proc->total_kernel_time + proc->total_user_time;
-
-    return 0;
-}
-
-/* reallocarray is reimplemented here for macOS because Apple doesn't expose
- * their implementation. This is taken straight from the OpenBSD source as
- * shown in the below copyright notice
- */
-
-/*	$OpenBSD: reallocarray.c,v 1.2 2014/12/08 03:45:00 bcook Exp $	*/
-/*
- * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-/* OPENBSD ORIGINAL: lib/libc/stdlib/reallocarray.c */
-
-#define MUL_NO_OVERFLOW	((size_t)1 << (sizeof(size_t) * 4))
-
-void *reallocarray(void *optr, size_t nmemb, size_t size)
-{
-    if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
-        nmemb > 0 && SIZE_MAX / nmemb < size) {
-        errno = ENOMEM;
-        return NULL;
-    }
-    return realloc(optr, size * nmemb);
-}
blob - /dev/null
blob + 3bbcf1c246a5b5475ef1fb51e62831f676c6dcf6 (mode 644)
--- /dev/null
+++ src/macos.c
@@ -0,0 +1,82 @@
+#include <libproc.h>
+#include <time.h>
+#include <mach/mach_time.h>
+#include <sys/errno.h>
+#include <stdlib.h>
+
+#include "lfmacos.h"
+
+#define NS_TO_SECONDS 1000000000.0
+#define NEW_PROCESS_SENTINEL (-1.0)
+
+ProcessData *new_ProcessData() {
+    ProcessData *pd = malloc(sizeof(ProcessData));
+    pd->last_total_consumed = NEW_PROCESS_SENTINEL;
+    return pd;
+}
+
+int update_process(pid_t pid, ProcessData *proc) {
+    struct proc_taskinfo taskinfo;
+    const int r = proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &taskinfo, PROC_PIDTASKINFO_SIZE);
+    if (r != PROC_PIDTASKINFO_SIZE) {
+        return 1;
+    }
+
+    mach_timebase_info_data_t info;
+    mach_timebase_info(&info);
+    const double ns_per_tick = (double)info.numer / (double)info.denom;
+
+    time(&(proc->timestamp));
+    proc->total_kernel_time = (taskinfo.pti_total_system * ns_per_tick) / NS_TO_SECONDS;
+    proc->total_user_time = (taskinfo.pti_total_user * ns_per_tick) / NS_TO_SECONDS;
+    proc->virtual_memory = taskinfo.pti_virtual_size;
+    proc->resident_memory = taskinfo.pti_resident_size;
+
+    if (proc->last_total_consumed != NEW_PROCESS_SENTINEL) {
+        time_t t = proc->timestamp - proc->last_timestamp;
+        proc->percent_cpu = 100.0 * (proc->total_user_time + proc->total_kernel_time - proc->last_total_consumed) / t;
+    } else {
+        proc->percent_cpu = 0.0;
+    }
+
+    proc->last_timestamp = proc->timestamp;
+    proc->last_total_consumed = proc->total_kernel_time + proc->total_user_time;
+
+    return 0;
+}
+
+/* reallocarray is reimplemented here for macOS because Apple doesn't expose
+ * their implementation. This is taken straight from the OpenBSD source as
+ * shown in the below copyright notice
+ */
+
+/*	$OpenBSD: reallocarray.c,v 1.2 2014/12/08 03:45:00 bcook Exp $	*/
+/*
+ * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/stdlib/reallocarray.c */
+
+#define MUL_NO_OVERFLOW	((size_t)1 << (sizeof(size_t) * 4))
+
+void *reallocarray(void *optr, size_t nmemb, size_t size)
+{
+    if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+        nmemb > 0 && SIZE_MAX / nmemb < size) {
+        errno = ENOMEM;
+        return NULL;
+    }
+    return realloc(optr, size * nmemb);
+}
blob - /dev/null
blob + 29841222c56782549f59bf4c7bff135b55aafe67 (mode 644)
--- /dev/null
+++ src/network.c
@@ -0,0 +1,150 @@
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <sys/wait.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <signal.h>
+#include <pthread.h>
+
+#include "lfnetwork.h"
+
+static void sighandler(int s) {
+    int saved_errno = errno;
+    while (waitpid(-1, NULL, WNOHANG) > 0);
+    errno = saved_errno;
+}
+
+void *get_in_addr(struct sockaddr *sa)
+{
+    if (sa->sa_family == AF_INET) {
+        return &(((struct sockaddr_in*)sa)->sin_addr);
+    }
+
+    return &(((struct sockaddr_in6*)sa)->sin6_addr);
+}
+
+Server *new_server(ServerType type, const char *port, void(handler)(Server *s)) {
+    Server *s = (Server *)malloc(sizeof(Server));
+    if (s == NULL) {
+        return NULL;
+    }
+    s->server_type = type;
+    s->handler = handler;
+
+    struct addrinfo *addr = NULL;
+    if (getaddrinfo(NULL, port, NULL, &addr) != 0) {
+        free(s);
+        return NULL;
+    }
+    s->port = (int)strtol(port, NULL, 10);
+
+    int socktype = 0;
+    switch (type) {
+    case SERVERTYPE_TCP:
+        socktype = SOCK_STREAM;
+        break;
+    case SERVERTYPE_UDP:
+        socktype = SOCK_DGRAM;
+        break;
+    }
+
+    struct addrinfo *p;
+    for (p = addr; p != NULL; p = p->ai_next) {
+        s->fd = socket(AF_INET, socktype, 0);
+        if (s->fd == -1) {
+            continue;
+        }
+
+        if (bind(s->fd, p->ai_addr, p->ai_addrlen) != 0) {
+            close(s->fd);
+            continue;
+        }
+
+        break;
+    }
+
+    if (p == NULL) {
+        fprintf(stderr, "Failed to bind\n");
+        free(s);
+        return NULL;
+    }
+
+    freeaddrinfo(addr);
+    return s;
+}
+
+void delete_server(Server *s) {
+    free(s);
+    s = NULL;
+}
+
+int serve(Server *s, int backlog_size) {
+    if (backlog_size == 0) {
+        backlog_size = DEFAULT_BACKLOG;
+    }
+
+    if (listen(s->fd, backlog_size) != 0) {
+        return 1;
+    }
+
+    // Linux doesn't handle SA_RESTART properly, and I don't know about Windows (nor do I care)
+    // This is just for macOS and BSDs
+    #if !defined(__linux__) && !defined(_WIN32)
+    struct sigaction sa;
+    sa.sa_handler = sighandler; 
+    sa.sa_flags = SA_RESTART;
+    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
+        fprintf(stderr, "Failed to set sigaction\n");
+        return 1;
+    }
+    #endif
+
+    s->handler(s);
+
+    return 0;
+}
+
+static void *tcp_echo_thread(void *vargp) {
+    while (1) {
+        char recv_buf[256];
+        int fd = *(int *) vargp;
+
+        int r = (int)recv(fd, recv_buf, 256, 0);
+        if (r < 1) {
+            if (r == -1) {
+                fprintf(stderr, "Failed to recv. Errno: %d\n", errno);
+            }
+            close(fd);
+            break;
+        }
+
+        if (send(fd, recv_buf, strlen(recv_buf), 0) == -1) {
+            fprintf(stderr, "Failed to send echo. Errno: %d\n", errno);
+            close(fd);
+            break;
+        }
+    }
+}
+
+void handler_tcp_echo(Server *s) {
+    while (1) {
+        struct sockaddr_storage client_addr;
+        socklen_t client_addr_sz = sizeof(client_addr);
+        int new_fd = accept(s->fd, (struct sockaddr *)&client_addr, &client_addr_sz);
+        if (new_fd == -1) {
+            fprintf(stderr, "failed to accept. Errno: %d\n", errno);
+            continue;
+        }
+
+        pthread_t srv_tid;
+        pthread_attr_t attr;
+        pthread_attr_init(&attr);
+        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+        pthread_create(&srv_tid, &attr, tcp_echo_thread, &new_fd);
+    }
+}
blob - 541550ed203d975a79e17157c67a45991f80cbba
blob + 663631f37f739d19158a37db96eaed3a4b6c30db
--- tests/tests.c
+++ tests/tests.c
@@ -2,8 +2,11 @@
 #include <stdlib.h>
 #include <assert.h>
 #include <unistd.h>
+#include <arpa/inet.h>
+#include <pthread.h>
 
 #include "lflinkedlist.h"
+#include "lfnetwork.h"
 #include "lfset.h"
 #include "lfstack.h"
 #include "lfbinarytree.h"
@@ -12,6 +15,7 @@
 #include "lfstring.h"
 #include "lfcrypto.h"
 #include "lfparsing.h"
+#include "lfinput.h"
 
 #if defined(__APPLE__) || defined(__MACH__)
 #include "lfmacos.h"
@@ -395,6 +399,60 @@ void test_parsing() {
     printf("Passes all parsing tests\n");
 }
 
+#define NET_MSG "TEST SEND"
+
+void tcp_test_handler(Server *s) {
+    struct sockaddr_storage client_addr;
+    socklen_t client_addr_sz = sizeof(client_addr);
+    int new_fd = accept(s->fd, (struct sockaddr *)&client_addr, &client_addr_sz);
+    assert(new_fd != -1);
+    assert(send(new_fd, NET_MSG, 10, 0) != -1);
+    close(new_fd);
+}
+
+void *tcp_server_thread(void *vargp) {
+    Server *server = new_server(SERVERTYPE_TCP, "18632", tcp_test_handler);
+    serve(server, DEFAULT_BACKLOG);
+    delete_server(server);
+}
+
+void udp_test_handler(Server *s) {
+    struct sockaddr_storage client_addr;
+    socklen_t client_addr_sz = sizeof(client_addr);
+    char recv_buf[128];
+
+    int r = (int)recvfrom(s->fd, recv_buf, 128, 0, (struct sockaddr*)&client_addr, &client_addr_sz);
+    assert(r > 0);
+    assert(strcmp(recv_buf, NET_MSG) == 0);
+}
+
+void *udp_server_thread(void *vargp) {
+    Server *server = new_server(SERVERTYPE_UDP, "18633", udp_test_handler);
+    serve(server, DEFAULT_BACKLOG);
+    delete_server(server);
+}
+
+void test_network() {
+    printf("\n--- NETWORK TEST ---\n");
+    pthread_t srv_tid;
+    pthread_create(&srv_tid, NULL, tcp_server_thread, NULL);
+
+    sleep(1);
+    const char *s = capture_system("echo hello | nc localhost 18632", 0);
+    assert(strcmp(s, NET_MSG) == 0);
+    free((char *)s);
+
+    pthread_join(srv_tid, NULL);
+    printf("Passed TCP test\n");
+
+    pthread_create(&srv_tid, NULL, udp_server_thread, NULL);
+    sleep(1);
+    system("echo hello | nc localhost 18633");
+
+    pthread_join(srv_tid, NULL);
+    printf("Passed UDP test\n");
+}
+
 #if defined(__APPLE__) || defined(__MACH__)
 void test_macos() {
     printf("\n--- macOS TEST ---\n");
@@ -420,10 +478,11 @@ int main() {
     test_string();
     test_crypto();
     test_parsing();
+    test_network();
 
 #if defined(__APPLE__) || defined(__MACH__)
     test_macos();
 #endif
 
     return 0;
-}
\ No newline at end of file
+}
blob - /dev/null
blob + c1eb3dfb79f83d7264007e335b15a1530a8956dd (mode 644)
--- /dev/null
+++ tests/netmanual.c
@@ -0,0 +1,7 @@
+#include "lfnetwork.h"
+
+int main(int argc, char **argv) {
+    Server *server = new_server(SERVERTYPE_TCP, "18632", handler_tcp_echo);
+    serve(server, DEFAULT_BACKLOG);
+    delete_server(server);
+}