diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt
index b9c27ede..14863215 100644
--- a/kernel/CMakeLists.txt
+++ b/kernel/CMakeLists.txt
@@ -164,6 +164,7 @@ SET(KERNEL_SRCS
         syscall/read_write.cpp
         syscall/sigaction.cpp
         syscall/sleep.cpp
+        syscall/socket.cpp
         syscall/stat.cpp
         syscall/thread.cpp
         syscall/truncate.cpp
@@ -174,7 +175,10 @@ SET(KERNEL_SRCS
         StackWalker.cpp
         net/NetworkAdapter.cpp
         net/E1000Adapter.cpp
-        net/NetworkManager.cpp)
+        net/NetworkManager.cpp
+        net/Socket.cpp
+        net/IPSocket.cpp
+        net/UDPSocket.cpp)
 
 add_custom_command(
         OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/generated/duckos_version.h"
diff --git a/kernel/api/in.h b/kernel/api/in.h
new file mode 100644
index 00000000..e86aae29
--- /dev/null
+++ b/kernel/api/in.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/* Copyright © 2016-2024 Byteduck */
+
+#pragma once
+
+#include "socket.h"
+
+__DECL_BEGIN
+
+typedef uint16_t in_port_t;
+typedef uint32_t in_addr_t;
+
+struct in_addr {
+	in_addr_t s_addr;
+};
+
+struct sockaddr_in {
+	sa_family_t     sin_family;
+	in_port_t       sin_port;
+	struct in_addr  sin_addr;
+};
+
+# define INADDR_ANY ((uint32_t) 0x00000000)
+# define INADDR_NONE    0xffffffff
+# define INPORT_ANY 0
+#define IPPROTO_TCP 0
+#define IPPROTO_UDP 0
+
+__DECL_END
\ No newline at end of file
diff --git a/kernel/api/ipv4.h b/kernel/api/ipv4.h
index 3651853c..b7e3e7ab 100644
--- a/kernel/api/ipv4.h
+++ b/kernel/api/ipv4.h
@@ -11,6 +11,13 @@ class __attribute__((packed)) IPv4Address {
 public:
 	constexpr IPv4Address() = default;
 
+	constexpr IPv4Address(uint32_t addr) {
+		m_data[0] = addr >> 24;
+		m_data[1] = addr >> 16;
+		m_data[2] = addr >> 8;
+		m_data[3] = addr;
+	}
+
 	constexpr IPv4Address(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
 		m_data[0] = a;
 		m_data[1] = b;
diff --git a/kernel/api/socket.h b/kernel/api/socket.h
new file mode 100644
index 00000000..18c26017
--- /dev/null
+++ b/kernel/api/socket.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/* Copyright © 2016-2024 Byteduck */
+
+#pragma once
+
+#include "cdefs.h"
+#include "types.h"
+#include "un.h"
+
+__DECL_BEGIN
+
+// Domains
+#define AF_UNSPEC   0
+#define AF_UNIX     1
+#define AF_LOCAL    2
+#define AF_INET     3
+#define AF_PACKET   4
+
+// Types
+#define SOCK_STREAM  1
+#define SOCK_DGRAM   2
+#define SOCK_RAW     3
+
+typedef uint16_t sa_family_t;
+typedef uint32_t socklen_t;
+
+struct sockaddr {
+	sa_family_t sa_family;
+	char        sa_data[14];
+};
+
+struct iovec {
+	void*   iov_base;
+	size_t  iov_len;
+};
+
+struct msghdr {
+	void*          msg_name;
+	socklen_t      msg_namelen;
+	struct iovec*  msg_iov;
+	size_t         msg_iovlen;
+	void*          msg_control;
+	size_t         msg_controllen;
+	int            msg_flags;
+};
+
+struct sockaddr_storage {
+	sa_family_t ss_family;
+	struct sockaddr_un __un;
+};
+
+__DECL_END
\ No newline at end of file
diff --git a/kernel/api/udp.h b/kernel/api/udp.h
new file mode 100644
index 00000000..d0f819ea
--- /dev/null
+++ b/kernel/api/udp.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/* Copyright © 2016-2024 Byteduck */
+
+#pragma once
+
+#include "endian.h"
+
+struct UDPPacket {
+	BigEndian<uint16_t> source_port;
+	BigEndian<uint16_t> dest_port;
+	BigEndian<uint16_t> len;
+	BigEndian<uint16_t> checksum = 0;
+	uint8_t payload[];
+} __attribute__((packed));
+
+static_assert(sizeof(UDPPacket) == 8);
\ No newline at end of file
diff --git a/kernel/api/un.h b/kernel/api/un.h
new file mode 100644
index 00000000..ef1fa61f
--- /dev/null
+++ b/kernel/api/un.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/* Copyright © 2016-2024 Byteduck */
+
+#pragma once
+
+#include "types.h"
+
+__DECL_BEGIN
+
+#define UNIX_PATH_MAX 100
+
+struct sockaddr_un {
+	uint16_t sun_family;
+	char sun_path[UNIX_PATH_MAX];
+};
+
+__DECL_END
\ No newline at end of file
diff --git a/kernel/filesystem/File.cpp b/kernel/filesystem/File.cpp
index b65d2109..c2481432 100644
--- a/kernel/filesystem/File.cpp
+++ b/kernel/filesystem/File.cpp
@@ -52,6 +52,10 @@ bool File::is_fifo() {
 	return false;
 }
 
+bool File::is_socket() {
+	return false;
+}
+
 ssize_t File::read(FileDescriptor &fd, size_t offset, SafePointer<uint8_t> buffer, size_t count) {
 	return 0;
 }
diff --git a/kernel/filesystem/File.h b/kernel/filesystem/File.h
index a8c92ff9..dfe43124 100644
--- a/kernel/filesystem/File.h
+++ b/kernel/filesystem/File.h
@@ -37,6 +37,7 @@ class File {
 	virtual bool is_pty_mux();
 	virtual bool is_pty();
 	virtual bool is_fifo();
+	virtual bool is_socket();
 	virtual int ioctl(unsigned request, SafePointer<void*> argp);
 	virtual void open(FileDescriptor& fd, int options);
 	virtual void close(FileDescriptor& fd);
diff --git a/kernel/kstd/ListQueue.h b/kernel/kstd/ListQueue.h
new file mode 100644
index 00000000..20e5aa4b
--- /dev/null
+++ b/kernel/kstd/ListQueue.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/* Copyright © 2016-2024 Byteduck */
+
+#pragma once
+
+#include "types.h"
+#include "Optional.h"
+
+namespace kstd {
+
+template<typename T, size_t max_count>
+class ListQueue {
+public:
+	ListQueue() = default;
+
+	bool enqueue(const T& val) {
+		if (m_count >= max_count)
+			return false;
+
+		if (m_tail == nullptr) {
+			m_head = new Entry {nullptr, val};
+			m_head = m_tail;
+		} else {
+			m_tail->next = new Entry {nullptr, val};
+			m_tail = m_tail->next;
+		}
+
+		m_count++;
+
+		return true;
+	}
+
+	Optional<T> dequeue() {
+		if (!m_head)
+			return nullopt;
+
+		m_count--;
+		auto* ent = m_head;
+		m_head = ent->next;
+		if (m_tail == ent)
+			m_tail = nullptr;
+		return ent->val;
+	}
+
+	Optional<T> peek() {
+		if (!m_head)
+			return nullopt;
+		return m_head->val;
+	}
+
+	[[nodiscard]] bool empty() const {
+		return m_count == 0;
+	}
+
+	[[nodiscard]] size_t count() const {
+		return count;
+	}
+
+private:
+	struct Entry {
+		Entry* next;
+		T val;
+	};
+
+	Entry* m_head = nullptr;
+	Entry* m_tail = nullptr;
+	size_t m_count = 0;
+};
+
+}
diff --git a/kernel/memory/SafePointer.h b/kernel/memory/SafePointer.h
index b5e68e5a..c610186c 100644
--- a/kernel/memory/SafePointer.h
+++ b/kernel/memory/SafePointer.h
@@ -9,6 +9,7 @@
 template<typename T>
 class SafePointer {
 public:
+	SafePointer() = default;
 	explicit SafePointer(T* raw_ptr, bool is_user):
 		m_ptr(raw_ptr), m_is_user(is_user) {}
 	template<typename C> SafePointer(const SafePointer<C>& safe_ptr):
@@ -153,9 +154,14 @@ class SafePointer {
 		});
 	}
 
+	template<typename R>
+	SafePointer<R> as() {
+		return SafePointer<R>((R*) m_ptr, m_is_user);
+	}
+
 private:
-	T* const m_ptr;
-	const bool m_is_user;
+	T* const m_ptr = nullptr;
+	const bool m_is_user = false;
 };
 
 template<typename T>
diff --git a/kernel/net/IPSocket.cpp b/kernel/net/IPSocket.cpp
new file mode 100644
index 00000000..b62edea2
--- /dev/null
+++ b/kernel/net/IPSocket.cpp
@@ -0,0 +1,87 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/* Copyright © 2016-2024 Byteduck */
+
+#include "IPSocket.h"
+#include "UDPSocket.h"
+#include "../api/in.h"
+#include "../kstd/KLog.h"
+#include "../tasking/PollBlocker.h"
+#include "../filesystem/FileDescriptor.h"
+
+IPSocket::IPSocket(Socket::Type type, int protocol): Socket(Domain::Inet, type, protocol) {
+
+}
+
+ResultRet<kstd::Arc<IPSocket>> IPSocket::make(Socket::Type type, int protocol) {
+	switch (type) {
+	case Type::Dgram:
+		return kstd::static_pointer_cast<IPSocket>(TRY(UDPSocket::make()));
+	default:
+		return Result(EINVAL);
+	}
+}
+
+Result IPSocket::bind(SafePointer<sockaddr> addr_ptr, socklen_t addrlen) {
+	if (m_bound || addrlen != sizeof(sockaddr_in))
+		return Result(set_error(EINVAL));
+
+	auto addr = addr_ptr.as<sockaddr_in>().get();
+	if (addr.sin_family != AF_INET)
+		return Result(set_error(EINVAL));
+
+	m_port = from_big_endian(addr.sin_port);
+	m_addr = IPv4Address(from_big_endian(addr.sin_addr.s_addr));
+
+	return do_bind();
+}
+
+ssize_t IPSocket::recvfrom(FileDescriptor& fd, SafePointer<uint8_t> buf, size_t len, int flags, SafePointer<sockaddr> src_addr, SafePointer<socklen_t> addrlen) {
+	m_receive_queue_lock.acquire();
+
+	// Block until we have a packet to read
+	while (m_receive_queue.empty()) {
+		if (fd.nonblock()) {
+			m_receive_queue_lock.release();
+			return -EAGAIN;
+		}
+
+		update_blocker();
+		m_receive_queue_lock.release();
+		TaskManager::current_thread()->block(m_receive_blocker);
+		m_receive_queue_lock.acquire();
+	}
+
+	// Read our packet
+	auto* packet = m_receive_queue.pop_front();
+	update_blocker();
+	m_receive_queue_lock.release();
+	auto res = do_recv(packet, buf, len);
+	kfree(packet);
+	return res;
+}
+
+Result IPSocket::recv_packet(const void* buf, size_t len) {
+	LOCK(m_receive_queue_lock);
+
+	if (m_receive_queue.size() == m_receive_queue.capacity()) {
+		KLog::warn("IPSocket", "Dropping packet because receive queue is full");
+		return Result(ENOSPC);
+	}
+
+	auto* src_pkt = (const IPv4Packet*) buf;
+	auto* new_pkt = (IPv4Packet*) kmalloc(len);
+	memcpy(new_pkt, src_pkt, len);
+
+	m_receive_queue.push_back(new_pkt);
+	update_blocker();
+
+	return Result(SUCCESS);
+}
+
+bool IPSocket::can_read(const FileDescriptor& fd) {
+	return !m_receive_queue.empty();
+}
+
+void IPSocket::update_blocker() {
+	m_receive_blocker.set_ready(!m_receive_queue.empty());
+}
diff --git a/kernel/net/IPSocket.h b/kernel/net/IPSocket.h
new file mode 100644
index 00000000..a2fa2bcc
--- /dev/null
+++ b/kernel/net/IPSocket.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/* Copyright © 2016-2024 Byteduck */
+
+#pragma once
+
+#include "Socket.h"
+#include "../api/ipv4.h"
+#include "../kstd/ListQueue.h"
+
+class IPSocket: public Socket {
+public:
+	static ResultRet<kstd::Arc<IPSocket>> make(Socket::Type type, int protocol);
+
+	// Socket
+	Result bind(SafePointer<sockaddr> addr, socklen_t addrlen) override;
+	ssize_t recvfrom(FileDescriptor &fd, SafePointer<uint8_t> buf, size_t len, int flags, SafePointer<sockaddr> src_addr, SafePointer<socklen_t> addrlen) override;
+	Result recv_packet(const void* buf, size_t len) override;
+
+	// File
+	bool can_read(const FileDescriptor &fd) override;
+
+protected:
+	IPSocket(Socket::Type type, int protocol);
+
+	virtual ssize_t do_recv(const IPv4Packet* pkt, SafePointer<uint8_t> buf, size_t len) = 0;
+	virtual Result do_bind() = 0;
+
+	void update_blocker();
+
+	bool m_bound = false;
+	uint16_t m_port;
+	IPv4Address m_addr;
+	kstd::circular_queue<IPv4Packet*> m_receive_queue { 16 };
+	Mutex m_receive_queue_lock { "IPSocket::receive_queue" };
+	BooleanBlocker m_receive_blocker;
+};
diff --git a/kernel/net/NetworkManager.cpp b/kernel/net/NetworkManager.cpp
index 807c48a6..dd58920c 100644
--- a/kernel/net/NetworkManager.cpp
+++ b/kernel/net/NetworkManager.cpp
@@ -4,6 +4,8 @@
 #include "NetworkManager.h"
 #include "../kstd/KLog.h"
 #include "ICMP.h"
+#include "UDPSocket.h"
+#include "../api/udp.h"
 
 #define ARP_DEBUG 1
 
@@ -103,7 +105,11 @@ void NetworkManager::handle_ipv4(NetworkAdapter* adapter, const NetworkAdapter::
 			handle_icmp(adapter, packet);
 			break;
 		case IPv4Proto::TCP:
+			KLog::warn("NetworkManager", "Received TCP packet! Can't handle this yet!");
+			break;
 		case IPv4Proto::UDP:
+			handle_udp(adapter, packet);
+			break;
 		default:
 			KLog::warn("NetworkManager", "Received IPv4 packet with unknown protocol %d!", packet.proto);
 	}
@@ -116,3 +122,25 @@ void NetworkManager::handle_icmp(NetworkAdapter* adapter, const IPv4Packet& pack
 	}
 	const auto& header= *((ICMPHeader*) packet.payload);
 }
+
+void NetworkManager::handle_udp(NetworkAdapter* adapter, const IPv4Packet& packet) {
+	if (packet.length < (sizeof(IPv4Packet) + sizeof(UDPPacket))) {
+		KLog::warn("NetworkManager", "Received UDP packet of invalid size!");
+		return;
+	}
+
+	auto* udp_pkt = (UDPPacket*) packet.payload;
+	if (udp_pkt->len < sizeof(UDPPacket)) {
+		KLog::warn("NetworkManager", "Received UDP packet of invalid size!");
+		return;
+	}
+
+	// Get the socket associated with the port
+	auto sock = UDPSocket::get_socket(udp_pkt->dest_port);
+	if (!sock) {
+		KLog::warn("NetworkManager", "Received UDP packet for port %d but no such port is bound.", udp_pkt->dest_port.val());
+		return;
+	}
+
+	sock->recv_packet((uint8_t*) &packet, packet.length.val());
+}
diff --git a/kernel/net/NetworkManager.h b/kernel/net/NetworkManager.h
index a6f56031..e2d66f84 100644
--- a/kernel/net/NetworkManager.h
+++ b/kernel/net/NetworkManager.h
@@ -22,6 +22,7 @@ class NetworkManager {
 	void handle_arp(NetworkAdapter* adapter, const NetworkAdapter::Packet* packet);
 	void handle_ipv4(NetworkAdapter* adapter, const NetworkAdapter::Packet* packet);
 	void handle_icmp(NetworkAdapter* adapter, const IPv4Packet& packet);
+	void handle_udp(NetworkAdapter* adapter, const IPv4Packet& packet);
 
 	static NetworkManager* s_inst;
 
diff --git a/kernel/net/Socket.cpp b/kernel/net/Socket.cpp
new file mode 100644
index 00000000..d62b79f4
--- /dev/null
+++ b/kernel/net/Socket.cpp
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/* Copyright © 2016-2024 Byteduck */
+
+#include "Socket.h"
+#include "IPSocket.h"
+
+Socket::Socket(Socket::Domain domain, Socket::Type type, int protocol):
+	m_domain(domain),
+	m_type(type),
+	m_protocol(protocol)
+{}
+
+ResultRet<kstd::Arc<Socket>> Socket::make(Socket::Domain domain, Socket::Type type, int protocol) {
+	switch (domain) {
+		case Domain::Inet:
+			return kstd::static_pointer_cast<Socket>(TRY(IPSocket::make(type, protocol)));
+		default:
+			return Result(EINVAL);
+	}
+}
+
+ssize_t Socket::read(FileDescriptor& fd, size_t offset, SafePointer<uint8_t> buffer, size_t count) {
+	return recvfrom(fd, buffer, count, 0, {}, {});
+}
diff --git a/kernel/net/Socket.h b/kernel/net/Socket.h
new file mode 100644
index 00000000..eea38b9f
--- /dev/null
+++ b/kernel/net/Socket.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/* Copyright © 2016-2024 Byteduck */
+
+#pragma once
+
+#include "../filesystem/File.h"
+#include "../api/socket.h"
+
+class Socket: public File {
+public:
+	enum Domain {
+		Inet = AF_INET,
+		Local = AF_LOCAL,
+		Packet = AF_PACKET,
+		Unix = AF_UNIX
+	};
+
+	enum Type {
+		Stream = SOCK_STREAM,
+		Dgram = SOCK_DGRAM,
+		Raw = SOCK_RAW
+	};
+
+	static ResultRet<kstd::Arc<Socket>> make(Domain domain, Type type, int protocol);
+
+	// Socket
+	virtual Result bind(SafePointer<sockaddr> addr, socklen_t addrlen) = 0;
+	virtual ssize_t recvfrom(FileDescriptor& fd, SafePointer<uint8_t> buf, size_t len, int flags, SafePointer<sockaddr> src_addr, SafePointer<socklen_t> addrlen) = 0;
+	virtual Result recv_packet(const void* buf, size_t len) = 0;
+
+	[[nodiscard]] int error() const { return m_error; }
+
+	// File
+	bool is_socket() override { return true; }
+	ssize_t read(FileDescriptor &fd, size_t offset, SafePointer<uint8_t> buffer, size_t count) override;
+
+protected:
+	Socket(Domain domain, Type type, int protocol);
+
+	int set_error(int error) { m_error = error; return error; }
+	void clear_error() { m_error = 0; }
+
+	int m_error = 0;
+	const Domain m_domain;
+	const Type m_type;
+	const int m_protocol;
+};
diff --git a/kernel/net/UDPSocket.cpp b/kernel/net/UDPSocket.cpp
new file mode 100644
index 00000000..42fb8d40
--- /dev/null
+++ b/kernel/net/UDPSocket.cpp
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/* Copyright © 2016-2024 Byteduck */
+
+#include "UDPSocket.h"
+#include "../kstd/KLog.h"
+#include "../api/udp.h"
+
+#define UDP_DBG 1
+
+kstd::map<uint16_t, kstd::Weak<UDPSocket>> UDPSocket::s_sockets;
+Mutex UDPSocket::s_sockets_lock { "UDPSocket::sockets" };
+
+UDPSocket::UDPSocket(): IPSocket(Type::Dgram, 0) {
+
+}
+
+UDPSocket::~UDPSocket() {
+	LOCK(s_sockets_lock);
+	if (m_bound && s_sockets.contains(m_port)) {
+		s_sockets.erase(m_port);
+		KLog::dbg_if<UDP_DBG>("UDPSocket", "Unbinding from port %d", m_port);
+	}
+}
+
+kstd::Arc<UDPSocket> UDPSocket::get_socket(uint16_t port) {
+	LOCK(s_sockets_lock);
+	auto sock = s_sockets.get(port);
+	if (!sock)
+		return kstd::Arc<UDPSocket>(nullptr);
+	auto locked = (*sock).lock();
+	ASSERT(locked);
+	return locked;
+}
+
+ResultRet<kstd::Arc<UDPSocket>> UDPSocket::make() {
+	return kstd::Arc(new UDPSocket());
+}
+
+Result UDPSocket::do_bind() {
+	LOCK(s_sockets_lock);
+	if (m_bound)
+		return Result(EINVAL);
+	if (s_sockets.contains(m_port))
+		return Result(EADDRINUSE);
+
+	KLog::dbg_if<UDP_DBG>("UDPSocket", "Binding to port %d", m_port);
+
+	// TODO: Device? IP?
+
+	s_sockets[m_port] = self();
+	m_bound = true;
+
+	return Result(SUCCESS);
+}
+
+ssize_t UDPSocket::do_recv(const IPv4Packet* pkt, SafePointer<uint8_t> buf, size_t len) {
+	auto* udp_pkt = (const UDPPacket*) pkt->payload;
+	ASSERT(pkt->length >= sizeof(IPv4Packet) + sizeof(UDPPacket)); // Should've been rejected at IP layer
+	ASSERT(udp_pkt->len >= sizeof(UDPPacket)); // Should've been rejected in NetworkManager
+
+	const size_t nread = min(len, udp_pkt->len.val() - sizeof(UDPPacket));
+	buf.write(udp_pkt->payload, nread);
+
+	return (ssize_t) nread;
+}
diff --git a/kernel/net/UDPSocket.h b/kernel/net/UDPSocket.h
new file mode 100644
index 00000000..7e602967
--- /dev/null
+++ b/kernel/net/UDPSocket.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/* Copyright © 2016-2024 Byteduck */
+
+#pragma once
+
+#include "IPSocket.h"
+
+class UDPSocket: public IPSocket, public kstd::ArcSelf<UDPSocket> {
+public:
+	static ResultRet<kstd::Arc<UDPSocket>> make();
+
+	~UDPSocket() override;
+
+	static kstd::Arc<UDPSocket> get_socket(uint16_t port);
+
+protected:
+	UDPSocket();
+
+	Result do_bind() override;
+	ssize_t do_recv(const IPv4Packet *pkt, SafePointer<uint8_t> buf, size_t len) override;
+
+	static kstd::map<uint16_t, kstd::Weak<UDPSocket>> s_sockets;
+	static Mutex s_sockets_lock;
+};
diff --git a/kernel/syscall/read_write.cpp b/kernel/syscall/read_write.cpp
index 62f2f0ef..1df203d6 100644
--- a/kernel/syscall/read_write.cpp
+++ b/kernel/syscall/read_write.cpp
@@ -6,28 +6,52 @@
 #include <kernel/filesystem/VFS.h>
 
 ssize_t Process::sys_read(int fd, UserspacePointer<uint8_t> buf, size_t count) {
-	if(fd < 0 || fd >= (int) _file_descriptors.size() || !_file_descriptors[fd])
+	m_fd_lock.acquire();
+	if(fd < 0 || fd >= (int) _file_descriptors.size() || !_file_descriptors[fd]) {
+		m_fd_lock.release();
 		return -EBADF;
-	return  _file_descriptors[fd]->read(buf, count);
+	}
+	auto desc = _file_descriptors[fd];
+	m_fd_lock.release();
+
+	return  desc->read(buf, count);
 }
 
-int Process::sys_readdir(int file, UserspacePointer<char> buf, size_t len) {
-	if(file < 0 || file >= (int) _file_descriptors.size() || !_file_descriptors[file])
+int Process::sys_readdir(int fd, UserspacePointer<char> buf, size_t len) {
+	m_fd_lock.acquire();
+	if(fd < 0 || fd >= (int) _file_descriptors.size() || !_file_descriptors[fd]) {
+		m_fd_lock.release();
 		return -EBADF;
-	return _file_descriptors[file]->read_dir_entries(buf, len);
+	}
+	auto desc = _file_descriptors[fd];
+	m_fd_lock.release();
+
+	return desc->read_dir_entries(buf, len);
 }
 
 ssize_t Process::sys_write(int fd, UserspacePointer<uint8_t> buffer, size_t count) {
-	if(fd < 0 || fd >= (int) _file_descriptors.size() || !_file_descriptors[fd])
+	m_fd_lock.acquire();
+	if(fd < 0 || fd >= (int) _file_descriptors.size() || !_file_descriptors[fd]) {
+		m_fd_lock.release();
 		return -EBADF;
-	ssize_t ret = _file_descriptors[fd]->write(buffer, count);
+	}
+	auto desc = _file_descriptors[fd];
+	m_fd_lock.release();
+
+	ssize_t ret = desc->write(buffer, count);
 	return ret;
 }
 
-int Process::sys_lseek(int file, off_t off, int whence) {
-	if(file < 0 || file >= (int) _file_descriptors.size() || !_file_descriptors[file])
+int Process::sys_lseek(int fd, off_t off, int whence) {
+	m_fd_lock.acquire();
+	if(fd < 0 || fd >= (int) _file_descriptors.size() || !_file_descriptors[fd]) {
+		m_fd_lock.release();
 		return -EBADF;
-	return _file_descriptors[file]->seek(off, whence);
+	}
+	auto desc = _file_descriptors[fd];
+	m_fd_lock.release();
+
+	return desc->seek(off, whence);
 }
 
 int Process::sys_open(UserspacePointer<char> filename, int options, int mode) {
@@ -36,6 +60,7 @@ int Process::sys_open(UserspacePointer<char> filename, int options, int mode) {
 	auto fd_or_err = VFS::inst().open(path, options, mode & (~_umask), _user, _cwd);
 	if(fd_or_err.is_error())
 		return fd_or_err.code();
+	LOCK(m_fd_lock);
 	_file_descriptors.push_back(fd_or_err.value());
 	fd_or_err.value()->set_owner(_self_ptr);
 	fd_or_err.value()->set_path(path);
@@ -43,9 +68,10 @@ int Process::sys_open(UserspacePointer<char> filename, int options, int mode) {
 	return (int)_file_descriptors.size() - 1;
 }
 
-int Process::sys_close(int file) {
-	if(file < 0 || file >= (int) _file_descriptors.size() || !_file_descriptors[file])
+int Process::sys_close(int fd) {
+	LOCK(m_fd_lock);
+	if(fd < 0 || fd >= (int) _file_descriptors.size() || !_file_descriptors[fd])
 		return -EBADF;
-	_file_descriptors[file] = kstd::Arc<FileDescriptor>(nullptr);
+	_file_descriptors[fd] = kstd::Arc<FileDescriptor>(nullptr);
 	return 0;
 }
\ No newline at end of file
diff --git a/kernel/syscall/socket.cpp b/kernel/syscall/socket.cpp
new file mode 100644
index 00000000..06568b7c
--- /dev/null
+++ b/kernel/syscall/socket.cpp
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/* Copyright © 2016-2024 Byteduck */
+
+#include "../tasking/Process.h"
+#include "../memory/SafePointer.h"
+#include "../net/Socket.h"
+#include "../filesystem/FileDescriptor.h"
+
+int Process::sys_socket(int domain, int type, int protocol) {
+	auto socket_res = Socket::make((Socket::Domain) domain, (Socket::Type) type, protocol);
+	if (socket_res.is_error())
+		return -socket_res.code();
+	auto fd = kstd::make_shared<FileDescriptor>(socket_res.value(), this);
+
+	LOCK(m_fd_lock);
+	_file_descriptors.push_back(fd);
+	fd->set_owner(_self_ptr);
+	fd->set_id((int) _file_descriptors.size() - 1);
+	return (int)_file_descriptors.size() - 1;
+}
+
+#define get_socket(fd) \
+	m_fd_lock.acquire(); \
+	if(fd < 0 || fd >= (int) _file_descriptors.size() || !_file_descriptors[fd]) { \
+	m_fd_lock.release(); \
+		return -EBADF; \
+	} \
+	auto desc = _file_descriptors[fd]; \
+	if (!desc->file()->is_socket()) \
+		return -ENOTSOCK; \
+	auto socket = kstd::static_pointer_cast<Socket>(desc->file());
+
+int Process::sys_bind(int sockfd, UserspacePointer<sockaddr> addr, uint32_t addrlen) {
+	get_socket(sockfd);
+	return -socket->bind(addr, addrlen).code();
+}
+
+int Process::sys_setsockopt(UserspacePointer<struct setsockopt_args> ptr) {
+	return -1;
+}
+
+int Process::sys_getsockopt(UserspacePointer<struct getsockopt_args> ptr) {
+	return -1;
+}
+
+int Process::sys_recvmsg(int sockfd, UserspacePointer<struct msghdr> msg_ptr, int flags) {
+	get_socket(sockfd);
+	auto msg = msg_ptr.get();
+
+	// TODO: More than one entry in iovec
+	if (msg.msg_iovlen != 1)
+		return -EINVAL;
+
+	auto iov = UserspacePointer(msg.msg_iov).get();
+	auto addr_ptr = UserspacePointer((sockaddr*) msg.msg_name);
+	auto addrlen_ptr = UserspacePointer(&msg_ptr.raw()->msg_namelen);
+	auto buf = UserspacePointer((uint8_t*) iov.iov_base);
+
+	// TODO: Control messages
+	UserspacePointer(&msg_ptr.raw()->msg_controllen).set(0);
+
+	return socket->recvfrom(*desc, buf, iov.iov_len, flags, addr_ptr, addrlen_ptr);
+}
+
+int Process::sys_sendmsg(int sockfd, UserspacePointer<struct msghdr> msg, int flags) {
+	return -1;
+}
\ No newline at end of file
diff --git a/kernel/syscall/syscall.cpp b/kernel/syscall/syscall.cpp
index 9a3c0ca6..2a1ee7aa 100644
--- a/kernel/syscall/syscall.cpp
+++ b/kernel/syscall/syscall.cpp
@@ -182,6 +182,18 @@ int handle_syscall(ThreadRegisters& regs, uint32_t call, uint32_t arg1, uint32_t
 			return cur_proc->sys_mprotect((void*) arg1, (size_t) arg2, arg3);
 		case SYS_UNAME:
 			return cur_proc->sys_uname((struct utsname*) arg1);
+		case SYS_SOCKET:
+			return cur_proc->sys_socket(arg1, arg2, arg3);
+		case SYS_BIND:
+			return cur_proc->sys_bind(arg1, (struct sockaddr*) arg2, arg3);
+		case SYS_SETSOCKOPT:
+			return cur_proc->sys_setsockopt((struct setsockopt_args*) arg1);
+		case SYS_GETSOCKOPT:
+			return cur_proc->sys_getsockopt((struct getsockopt_args*) arg1);
+		case SYS_SENDMSG:
+			return cur_proc->sys_sendmsg(arg1, (struct msghdr*) arg2, arg3);
+		case SYS_RECVMSG:
+			return cur_proc->sys_recvmsg(arg1, (struct msghdr*) arg2, arg3);
 
 		//TODO: Implement these syscalls
 		case SYS_TIMES:
diff --git a/kernel/syscall/syscall_numbers.h b/kernel/syscall/syscall_numbers.h
index 29754490..06f7bc16 100644
--- a/kernel/syscall/syscall_numbers.h
+++ b/kernel/syscall/syscall_numbers.h
@@ -79,6 +79,12 @@
 #define SYS_MPROTECT 76
 #define SYS_UNAME 77
 #define SYS_PTRACE 78
+#define SYS_SOCKET 79
+#define SYS_BIND 80
+#define SYS_SETSOCKOPT 81
+#define SYS_GETSOCKOPT 82
+#define SYS_RECVMSG 83
+#define SYS_SENDMSG 84
 
 #ifndef DUCKOS_KERNEL
 #include <sys/types.h>
@@ -89,4 +95,20 @@ struct readlinkat_args {
 	const char* path;
 	char* buf;
 	size_t bufsize;
+};
+
+struct getsockopt_args {
+	int sockfd;
+	int level;
+	int option_name;
+	void* option_value;
+	uint32_t* option_len;
+};
+
+struct setsockopt_args {
+	int sockfd;
+	int level;
+	int option_name;
+	const void* option_value;
+	uint32_t option_len;
 };
\ No newline at end of file
diff --git a/kernel/tasking/Process.h b/kernel/tasking/Process.h
index f8fda20e..e9f908da 100644
--- a/kernel/tasking/Process.h
+++ b/kernel/tasking/Process.h
@@ -167,6 +167,12 @@ class Process {
 	int sys_munmap(void* addr, size_t length);
 	int sys_mprotect(void* addr, size_t length, int prot);
 	int sys_uname(UserspacePointer<struct utsname> buf);
+	int sys_socket(int domain, int type, int protocol);
+	int sys_bind(int sockfd, UserspacePointer<struct sockaddr> addr, uint32_t addrlen);
+	int sys_setsockopt(UserspacePointer<struct setsockopt_args> ptr);
+	int sys_getsockopt(UserspacePointer<struct getsockopt_args> ptr);
+	int sys_recvmsg(int sockfd, UserspacePointer<struct msghdr> msg, int flags);
+	int sys_sendmsg(int sockfd, UserspacePointer<struct msghdr> msg, int flags);
 
 private:
 	friend class Thread;
@@ -219,6 +225,7 @@ class Process {
 	size_t m_used_shmem = 0;
 
 	//Files & Pipes
+	Mutex m_fd_lock { "Process::FileDescriptor" };
 	kstd::vector<kstd::Arc<FileDescriptor>> _file_descriptors;
 	kstd::Arc<LinkedInode> _cwd;
 
diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt
index 85d2d4fd..00db9eb6 100644
--- a/libraries/CMakeLists.txt
+++ b/libraries/CMakeLists.txt
@@ -1,4 +1,4 @@
-ADD_COMPILE_OPTIONS(-O2)
+ADD_COMPILE_OPTIONS(-O3 -msse2)
 ADD_SUBDIRECTORY(libc/)
 ADD_SUBDIRECTORY(libm/)
 ADD_SUBDIRECTORY(libpond/)
diff --git a/libraries/libc/CMakeLists.txt b/libraries/libc/CMakeLists.txt
index feb77adf..3c973e99 100644
--- a/libraries/libc/CMakeLists.txt
+++ b/libraries/libc/CMakeLists.txt
@@ -1,4 +1,5 @@
 SET(SOURCES
+        arpa/inet.cpp
         assert.c
         crt0.c
         ctype.c
@@ -20,6 +21,7 @@ SET(SOURCES
         sys/ptrace.c
         sys/liballoc.cpp
         sys/scanf.c
+        sys/socket.c
         sys/socketfs.c
         sys/stat.c
         sys/status.c
diff --git a/libraries/libc/arpa/inet.cpp b/libraries/libc/arpa/inet.cpp
new file mode 100644
index 00000000..ede6c4f9
--- /dev/null
+++ b/libraries/libc/arpa/inet.cpp
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/* Copyright © 2016-2024 Byteduck */
+
+#include "inet.h"
+#include <kernel/api/endian.h>
+
+uint32_t htonl(uint32_t hostlong) {
+	return as_big_endian(hostlong);
+}
+
+uint16_t htons(uint16_t hostshort) {
+	return as_big_endian(hostshort);
+}
+
+uint32_t ntohl(uint32_t netlong) {
+	return from_big_endian(netlong);
+}
+
+uint16_t ntohs(uint16_t netshort) {
+	return from_big_endian(netshort);
+}
\ No newline at end of file
diff --git a/libraries/libc/arpa/inet.h b/libraries/libc/arpa/inet.h
new file mode 100644
index 00000000..6733eaa0
--- /dev/null
+++ b/libraries/libc/arpa/inet.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/* Copyright © 2016-2024 Byteduck */
+
+#pragma once
+
+#include <kernel/api/cdefs.h>
+#include "../stdint.h"
+
+__DECL_BEGIN
+
+uint32_t htonl(uint32_t hostlong);
+uint16_t htons(uint16_t hostshort);
+uint32_t ntohl(uint32_t netlong);
+uint16_t ntohs(uint16_t netshort);
+
+__DECL_END
\ No newline at end of file
diff --git a/libraries/libc/netinet/in.h b/libraries/libc/netinet/in.h
new file mode 100644
index 00000000..7bed5a89
--- /dev/null
+++ b/libraries/libc/netinet/in.h
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/* Copyright © 2016-2024 Byteduck */
+
+#pragma once
+#include <kernel/api/in.h>
\ No newline at end of file
diff --git a/libraries/libc/string.c b/libraries/libc/string.c
index cfde29fa..8538e28d 100644
--- a/libraries/libc/string.c
+++ b/libraries/libc/string.c
@@ -382,6 +382,10 @@ char* strerror(int errnum) {
 			return "No message of desired type";
 		case EIDRM:
 			return "Identifier removed";
+		case EADDRINUSE:
+			return "Address in use";
+		case ENOTSOCK:
+			return "File descriptor is not a socket";
 		default:
 			return "Unknown error";
 	}
diff --git a/libraries/libc/sys/socket.c b/libraries/libc/sys/socket.c
new file mode 100644
index 00000000..be450f85
--- /dev/null
+++ b/libraries/libc/sys/socket.c
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/* Copyright © 2016-2024 Byteduck */
+
+#include "socket.h"
+#include "syscall.h"
+#include "../stdlib.h"
+#include "errno.h"
+#include "../string.h"
+
+int socket(int domain, int type, int protocol) {
+	return syscall4(SYS_SOCKET, domain, type, protocol);
+}
+
+int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen) {
+	return syscall4(SYS_BIND, sockfd, (int) addr, addrlen);
+}
+
+int setsockopt(int sockfd, int level, int option, const void* option_value, socklen_t option_len) {
+	struct setsockopt_args args = { sockfd, level, option, option_value, option_len };
+	return syscall2(SYS_SETSOCKOPT, (int) &args);
+}
+
+int getsockopt(int sockfd, int level, int option, void* option_value, socklen_t* option_len) {
+	struct getsockopt_args args = { sockfd, level, option, option_value, option_len };
+	return syscall2(SYS_GETSOCKOPT, (int) &args);
+}
+
+ssize_t send(int sockfd, const void *buf, size_t len, int flags) {
+	return sendto(sockfd, buf, len, flags, NULL, NULL);
+}
+
+ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) {
+	struct iovec iov = {(void*) buf, len};
+	struct msghdr msg = {(struct sockaddr*) dest_addr, addrlen, &iov, 1, NULL, 0, 0};
+	return sendmsg(sockfd, &msg, flags);
+}
+
+ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) {
+	return syscall4(SYS_SENDMSG, sockfd, (int) msg, flags);
+}
+
+ssize_t recv(int sockfd, void *buf, size_t len, int flags) {
+	return recvfrom(sockfd, buf, len, flags, NULL, NULL);
+}
+
+ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) {
+	if (!addrlen && src_addr) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	struct sockaddr_storage addr;
+	struct iovec iov = {buf, len};
+	struct msghdr msg = {src_addr ? &addr : NULL, src_addr ? sizeof(addr) : 0, &iov, 1, NULL, 0, 0};
+	ssize_t nread = recvmsg(sockfd, &msg, flags);
+	if (src_addr && nread >= 0) {
+		memcpy(src_addr, &addr, *addrlen < msg.msg_namelen ? *addrlen : msg.msg_namelen);
+		*addrlen = msg.msg_namelen;
+	}
+	return nread;
+}
+
+ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags) {
+	return syscall4(SYS_RECVMSG, sockfd, (int) msg, flags);
+}
\ No newline at end of file
diff --git a/libraries/libc/sys/socket.h b/libraries/libc/sys/socket.h
index 03a1b15e..2a86b841 100644
--- a/libraries/libc/sys/socket.h
+++ b/libraries/libc/sys/socket.h
@@ -1,4 +1,22 @@
 /* Workaround for GCC compilation */
 #ifdef CODY_NETWORKING
 #define CODY_NETWORKING 0
-#endif
\ No newline at end of file
+#endif
+
+#include <kernel/api/socket.h>
+#include "../netinet/in.h"
+#include "types.h"
+#include "un.h"
+
+__DECL_BEGIN
+int socket(int domain, int type, int protocol);
+int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
+int setsockopt(int sockfd, int level, int option, const void* option_value, socklen_t option_len);
+int getsockopt(int sockfd, int level, int option, void* option_value, socklen_t* option_len);
+ssize_t send(int sockfd, const void *buf, size_t len, int flags);
+ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
+ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
+ssize_t recv(int sockfd, void *buf, size_t len, int flags);
+ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
+ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
+__DECL_END
\ No newline at end of file
diff --git a/libraries/libc/sys/un.h b/libraries/libc/sys/un.h
new file mode 100644
index 00000000..ea647e32
--- /dev/null
+++ b/libraries/libc/sys/un.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/* Copyright © 2016-2024 Byteduck */
+
+#pragma once
+
+#include <kernel/api/un.h>
\ No newline at end of file