diff --git a/src/libcamera/include/ipc_unixsocket.h b/src/libcamera/include/ipc_unixsocket.h
new file mode 100644
index 0000000000000000..68edbe72e2af1298
--- /dev/null
+++ b/src/libcamera/include/ipc_unixsocket.h
@@ -0,0 +1,59 @@
+/* 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>
+
+#include <libcamera/event_notifier.h>
+
+namespace libcamera {
+
+class IPCUnixSocket
+{
+public:
+	struct Payload {
+		std::vector<uint8_t> data;
+		std::vector<int32_t> fds;
+	};
+
+	IPCUnixSocket();
+
+	int create();
+	int attach(int fd);
+	void close();
+
+	Signal<const Payload &> payloadReceived;
+
+	int send(Payload &payload);
+
+private:
+	struct Header {
+		uint32_t data;
+		uint8_t fds;
+	};
+
+	int configure();
+
+	int sendData(void *buffer, size_t length, const int32_t *fds, unsigned int num);
+
+	int recv();
+	int recvData(void *buffer, size_t length, int32_t *fds, unsigned int num);
+
+	void dataNotifier(EventNotifier *notifier);
+
+	int fd_;
+	bool master_;
+	EventNotifier *notifier_;
+};
+
+} /* 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..7b3d8995374dac1e
--- /dev/null
+++ b/src/libcamera/ipc_unixsocket.cpp
@@ -0,0 +1,288 @@
+/* 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 <string.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "log.h"
+
+/**
+ * \file ipc_unixsocket.h
+ * \brief IPC mechanism based on Unix sockets
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(IPCUnixSocket)
+
+/**
+ * \struct IPCUnixSocket::Payload
+ * \brief Container for an IPC payload
+ *
+ * Holds an array of bytes and an array of file descriptors that can be
+ * transported across a IPC boundary.
+ */
+
+/**
+ * \var IPCUnixSocket::Payload::data
+ * \brief Array of bytes to cross IPC boundary
+ */
+
+/**
+ * \var IPCUnixSocket::Payload::fds
+ * \brief Array of file descriptors to cross IPC boundary
+ */
+
+/**
+ * \class IPCUnixSocket
+ * \brief IPC mechanism based on Unix sockets
+ *
+ * The IPC mechanism provided by libcamera are centred around passing arrays of
+ * raw bytes and file descriptors between processes. The primary users of the
+ * IPC objects are pipeline handlers and Image Processing Algorithms components.
+ * A pipeline handler would act as an IPC master and send messages for an IPA to
+ * process and react to.
+ *
+ * The IPC design is asynchronous, a message is queued to a receiver which gets
+ * notified that a message is ready to be consumed by a signal. The queuer of
+ * the message gets no notification when a message is delivers nor processed.
+ * If such interactions are needed a protocol specific to the users use-case
+ * should be implemented on top of the IPC objects.
+ *
+ * The IPC design can transmit messages in any direction and the only difference
+ * from a master and slave operation is how they are created and attached to one
+ * another. After the two parts are setup the operation to send and receive
+ * messages are the same for both.
+ */
+
+IPCUnixSocket::IPCUnixSocket()
+	: fd_(-1), master_(false), notifier_(nullptr)
+{
+}
+
+/**
+ * \brief Create an new IPC channel
+ *
+ * Create a new IPC channel. Returned on success is a file descriptor which
+ * needs to be passed to the slave process and used when attaching to the IPC
+ * channel using attach().
+ *
+ * \return A file descriptor on success, negative error code on failure
+ */
+int IPCUnixSocket::create()
+{
+	int sockets[2];
+	int ret;
+
+	ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets);
+	if (ret) {
+		ret = -errno;
+		LOG(IPCUnixSocket, Error)
+			<< "Failed to create socket pair: " << strerror(-ret);
+		return ret;
+	}
+
+	fd_ = sockets[0];
+	master_ = true;
+
+	ret = configure();
+	if (ret)
+		return ret;
+
+	return sockets[1];
+}
+
+/**
+ * \brief Attach to an existing IPC channel
+ * \param[in] fd File descriptor
+ *
+ * Attach to an existing IPC channel. The \a fd argument is the file descriptor
+ * returned from create() in the master process and passed to the slave process
+ * to be able to establish the IPC channel.
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+int IPCUnixSocket::attach(int fd)
+{
+	fd_ = fd;
+
+	return configure();
+}
+
+/**
+ * \brief Close the IPC channel
+ *
+ * Close the IPC channel, no communication is possible after close() have been
+ * called.
+ */
+void IPCUnixSocket::close()
+{
+	delete notifier_;
+
+	if (fd_ == -1)
+		return;
+
+	::close(fd_);
+
+	fd_ = -1;
+}
+
+/**
+ * \brief Send a message payload
+ * \param[in] payload Message payload to send
+ *
+ * Queues the message payload for transmission to the other end of the IPC
+ * channel.
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+int IPCUnixSocket::send(Payload &payload)
+{
+	Header hdr;
+	int ret;
+
+	if (fd_ < 0)
+		return -ENOTCONN;
+
+	hdr.data = payload.data.size();
+	hdr.fds = payload.fds.size();
+
+	ret = ::send(fd_, &hdr, sizeof(hdr), 0);
+	if (ret < 0) {
+		ret = -errno;
+		LOG(IPCUnixSocket, Error)
+			<< "Failed to send: " << strerror(-ret);
+		return ret;
+	}
+
+	ret = sendData(payload.data.data(), hdr.data, payload.fds.data(), hdr.fds);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/**
+ * \var IPCUnixSocket::payloadReceived
+ * \brief A Signal emitted when a message payload is received
+ */
+
+int IPCUnixSocket::configure()
+{
+	notifier_ = new EventNotifier(fd_, EventNotifier::Read);
+	notifier_->activated.connect(this, &IPCUnixSocket::dataNotifier);
+
+	return 0;
+}
+
+int IPCUnixSocket::sendData(void *buffer, size_t length, const int32_t *fds, unsigned int num)
+{
+	struct iovec iov[1];
+	iov[0].iov_base = buffer;
+	iov[0].iov_len = length;
+
+	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::recv()
+{
+	Payload payload;
+	Header hdr;
+	int ret;
+
+	if (fd_ < 0)
+		return -ENOTCONN;
+
+	ret = ::recv(fd_, &hdr, sizeof(hdr), 0);
+	if (ret < 0) {
+		ret = -errno;
+		LOG(IPCUnixSocket, Error)
+			<< "Failed to recv header: " << strerror(-ret);
+		return ret;
+	}
+
+	payload.data.resize(hdr.data);
+	payload.fds.resize(hdr.fds);
+
+	ret = recvData(payload.data.data(), hdr.data, payload.fds.data(), hdr.fds);
+	if (ret)
+		return ret;
+
+	payloadReceived.emit(payload);
+
+	return 0;
+}
+
+int IPCUnixSocket::recvData(void *buffer, size_t length, int32_t *fds, unsigned int num)
+{
+	struct iovec iov[1];
+	iov[0].iov_base = buffer;
+	iov[0].iov_len = length;
+
+	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;
+
+	if (recvmsg(fd_, &msg, 0) < 0) {
+		int ret = -errno;
+		LOG(IPCUnixSocket, Error)
+			<< "Failed to recvmsg: " << strerror(-ret);
+		return ret;
+	}
+
+	memcpy(fds, CMSG_DATA(cmsg), num * sizeof(uint32_t));
+
+	return 0;
+}
+
+void IPCUnixSocket::dataNotifier(EventNotifier *notifier)
+{
+	recv();
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index 985aa7e8ab0eb6ce..45bd9d1793aa0b19 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',
@@ -38,6 +39,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',
