diff --git a/src/libcamera/include/ipc_unixsocket.h b/src/libcamera/include/ipc_unixsocket.h
new file mode 100644
index 0000000000000000..864fa93b1f190fb7
--- /dev/null
+++ b/src/libcamera/include/ipc_unixsocket.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * ipc_unixsocket.h - IPC mechanism based on Unix sockets
+ */
+
+#ifndef __LIBCAMERA_IPC_UNIXSOCKET_H__
+#define __LIBCAMERA_IPC_UNIXSOCKET_H__
+
+#include <cstdint>
+#include <sys/types.h>
+#include <vector>
+
+namespace libcamera {
+
+class IPCUnixSocket
+{
+public:
+	struct Payload {
+		uint32_t priv;
+		std::vector<uint8_t> data;
+		std::vector<int32_t> fds;
+	};
+
+	IPCUnixSocket();
+	IPCUnixSocket(int fd);
+
+	int create();
+	int connect();
+	void close();
+
+	int send(const Payload &payload);
+	int recv(Payload *payload, int timeout);
+	int call(const Payload &payload, Payload *response, int timeout);
+
+private:
+	struct Header {
+		uint32_t priv;
+		uint32_t data;
+		uint8_t fds;
+	};
+
+	int poll(int timeout);
+
+	int sendData(const void *buffer, ssize_t length);
+	int recvData(void *buffer, ssize_t length, int timeout);
+
+	int sendFds(const int32_t *fds, unsigned int num);
+	int recvFds(int32_t *fds, unsigned int num, int timeout);
+
+	int fd_;
+	bool master_;
+};
+
+} /* namespace libcamera */
+
+#endif /* __LIBCAMERA_IPC_UNIXSOCKET_H__ */
diff --git a/src/libcamera/ipc_unixsocket.cpp b/src/libcamera/ipc_unixsocket.cpp
new file mode 100644
index 0000000000000000..b34fa0317a18b37c
--- /dev/null
+++ b/src/libcamera/ipc_unixsocket.cpp
@@ -0,0 +1,330 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * ipc_unixsocket.cpp - IPC mechanism based on Unix sockets
+ */
+
+#include "ipc_unixsocket.h"
+
+#include <errno.h>
+#include <poll.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "log.h"
+
+/**
+ * \file ipc_unixsocket.h
+ * \brief IPC mechanism based on Unix sockets
+ */
+
+/*
+ * Markers to use in IPC protocol, there is no specific meaning to the values,
+ * but they should be unique.
+ */
+#define CMD_PING 0x1f
+#define CMD_PONG 0xf1
+#define CMD_FD 0x77
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(IPCUnixSocket)
+
+IPCUnixSocket::IPCUnixSocket()
+	: fd_(-1), master_(false)
+{
+}
+
+IPCUnixSocket::IPCUnixSocket(int fd)
+	: fd_(fd), master_(false)
+{
+}
+
+int IPCUnixSocket::create()
+{
+	int sockets[2];
+	int ret;
+
+	ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets);
+	if (ret) {
+		ret = -errno;
+		LOG(IPCUnixSocket, Error)
+			<< "Failed to create socket pair: " << strerror(-ret);
+		return ret;
+	}
+
+	fd_ = sockets[0];
+	master_ = true;
+
+	return sockets[1];
+}
+
+int IPCUnixSocket::connect()
+{
+	Payload payload = {};
+	Payload response = {};
+
+	if (master_) {
+		payload.data.push_back(CMD_PING);
+
+		if (call(payload, &response, 500))
+			return -1;
+
+		if (response.data[0] != CMD_PONG)
+			return -1;
+	} else {
+		if (recv(&payload, 500))
+			return -1;
+
+		if (payload.data[0] != CMD_PING)
+			return -1;
+
+		response.data.push_back(CMD_PONG);
+
+		if (send(response))
+			return -1;
+	}
+
+	return 0;
+}
+
+void IPCUnixSocket::close()
+{
+	if (fd_ == -1)
+		return;
+
+	::close(fd_);
+
+	fd_ = -1;
+}
+
+int IPCUnixSocket::send(const Payload &payload)
+{
+	Header hdr;
+	int ret;
+
+	if (fd_ < 0)
+		return -ENOTCONN;
+
+	hdr.priv = payload.priv;
+	hdr.data = payload.data.size();
+	hdr.fds = payload.fds.size();
+
+	ret = sendData(&hdr, sizeof(hdr));
+	if (ret)
+		return ret;
+
+	if (hdr.data) {
+		ret = sendData(payload.data.data(), hdr.data);
+		if (ret)
+			return ret;
+	}
+
+	if (hdr.fds) {
+		ret = sendFds(payload.fds.data(), hdr.fds);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+int IPCUnixSocket::recv(Payload *payload, int timeout)
+{
+	Header hdr;
+	int ret;
+
+	if (fd_ < 0)
+		return -ENOTCONN;
+
+	ret = recvData(&hdr, sizeof(hdr), timeout);
+	if (ret)
+		return ret;
+
+	payload->priv = hdr.priv;
+	payload->data.resize(hdr.data);
+	payload->fds.resize(hdr.fds);
+
+	if (hdr.data) {
+		ret = recvData(payload->data.data(), hdr.data, timeout);
+		if (ret)
+			return ret;
+	}
+
+	if (hdr.fds) {
+		ret = recvFds(payload->fds.data(), hdr.fds, timeout);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+int IPCUnixSocket::call(const Payload &payload, Payload *response, int timeout)
+{
+	int ret = send(payload);
+	if (ret)
+		return ret;
+
+	return recv(response, timeout);
+}
+
+int IPCUnixSocket::poll(int timeout)
+{
+	struct pollfd pollfd = { fd_, POLLIN, 0 };
+
+	int ret = ::poll(&pollfd, 1, timeout);
+	if (ret < 0) {
+		ret = -errno;
+		LOG(IPCUnixSocket, Error)
+			<< "Failed to poll: " << strerror(-ret);
+		return ret;
+	} else if (ret == 0) {
+		return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+int IPCUnixSocket::sendData(const void *buffer, ssize_t length)
+{
+	ssize_t len, sent;
+	const uint8_t *pos;
+
+	if (fd_ < 0)
+		return -ENOTCONN;
+
+	pos = static_cast<const uint8_t *>(buffer);
+	len = length;
+
+	while (len) {
+		sent = ::send(fd_, pos, len, 0);
+		if (sent < 0) {
+			sent = -errno;
+			LOG(IPCUnixSocket, Error)
+				<< "Failed to send: " << strerror(-sent);
+			return sent;
+		}
+
+		pos += sent;
+		len -= sent;
+	}
+
+	return 0;
+}
+
+int IPCUnixSocket::recvData(void *buffer, ssize_t length, int timeout)
+{
+	ssize_t len, recived;
+	uint8_t *pos;
+
+	if (fd_ < 0)
+		return -ENOTCONN;
+
+	pos = static_cast<uint8_t *>(buffer);
+	len = length;
+
+	while (len) {
+		int ret = poll(timeout);
+		if (ret < 0)
+			return ret;
+
+		recived = ::recv(fd_, pos, len, 0);
+		if (recived < 0) {
+			recived = -errno;
+			LOG(IPCUnixSocket, Error)
+				<< "Failed to recv: " << strerror(-recived);
+			return recived;
+		}
+
+		pos += recived;
+		len -= recived;
+	}
+
+	return 0;
+}
+
+int IPCUnixSocket::sendFds(const int32_t *fds, unsigned int num)
+{
+	char cmd = CMD_FD;
+	struct iovec iov[1];
+	iov[0].iov_base = &cmd;
+	iov[0].iov_len = sizeof(cmd);
+
+	char buf[CMSG_SPACE(num * sizeof(uint32_t))];
+	memset(buf, 0, sizeof(buf));
+
+	struct cmsghdr *cmsg = (struct cmsghdr *)buf;
+	cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t));
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+
+	struct msghdr msg;
+	msg.msg_name = nullptr;
+	msg.msg_namelen = 0;
+	msg.msg_iov = iov;
+	msg.msg_iovlen = 1;
+	msg.msg_control = cmsg;
+	msg.msg_controllen = cmsg->cmsg_len;
+	msg.msg_flags = 0;
+	memcpy(CMSG_DATA(cmsg), fds, num * sizeof(uint32_t));
+
+	if (sendmsg(fd_, &msg, 0) < 0) {
+		int ret = -errno;
+		LOG(IPCUnixSocket, Error)
+			<< "Failed to sendmsg: " << strerror(-ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+int IPCUnixSocket::recvFds(int32_t *fds, unsigned int num, int timeout)
+{
+	char cmd;
+	struct iovec iov[1];
+	iov[0].iov_base = &cmd;
+	iov[0].iov_len = sizeof(cmd);
+
+	char buf[CMSG_SPACE(num * sizeof(uint32_t))];
+	memset(buf, 0, sizeof(buf));
+
+	struct cmsghdr *cmsg = (struct cmsghdr *)buf;
+	cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t));
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+
+	struct msghdr msg;
+	msg.msg_name = nullptr;
+	msg.msg_namelen = 0;
+	msg.msg_iov = iov;
+	msg.msg_iovlen = 1;
+	msg.msg_control = cmsg;
+	msg.msg_controllen = cmsg->cmsg_len;
+	msg.msg_flags = 0;
+
+	int ret = poll(timeout);
+	if (ret < 0)
+		return ret;
+
+	if (recvmsg(fd_, &msg, 0) < 0) {
+		int ret = -errno;
+		LOG(IPCUnixSocket, Error)
+			<< "Failed to recvmsg: " << strerror(-ret);
+		return ret;
+	}
+
+	if (cmd != CMD_FD) {
+		LOG(IPCUnixSocket, Error) << "FD marker wrong";
+		return -EINVAL;
+	}
+
+	memcpy(fds, CMSG_DATA(cmsg), num * sizeof(uint32_t));
+
+	return 0;
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index f26ad5b2dc57c014..1158825fa5b0702d 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -13,6 +13,7 @@ libcamera_sources = files([
     'ipa_interface.cpp',
     'ipa_manager.cpp',
     'ipa_module.cpp',
+    'ipc_unixsocket.cpp',
     'log.cpp',
     'media_device.cpp',
     'media_object.cpp',
@@ -37,6 +38,7 @@ libcamera_headers = files([
     'include/formats.h',
     'include/ipa_manager.h',
     'include/ipa_module.h',
+    'include/ipc_unixsocket.h',
     'include/log.h',
     'include/media_device.h',
     'include/media_object.h',
