## Before starting This file is a presentation. Better get the point tools here: https://git.baguette.netlib.re/Baguette/pointtools TODO: Explain the problem TODO: Explain the solution TODO: Explain why LibIPC TODO: Explain how LibIPC TODO: Explain other possible implementations TODO: Explain future of LibIPC TODO: Explain what can be done right now TODO: Explain what I actually do with it TODO: Explain LibIPC isn't a silver bullet but fine for what I want Have fun! ## Programming problems Libraries * change often = hard to follow * don't often provide a high-level interface * each library is coded in its own way * availability vary depending on the language Example: libraries to access databases * languages often have their own implementation * functions may vary from a language to another ## Infrastructure problems Infrastructure * Always need to install all required libraries * No clear way to sandbox part of an application ## What solution? MAKE APPLICATIONS, NOT LIBRARIES * apps talking to apps Create an abstraction for libraries * languages, implementations, code modifications in general network code * networking: performed by dedicated services - example: TCPd, UDPd, TLSd, HTTPd... - LibIPC is independant from protocols and formats * applications think communications are local ## In practice usage must be simple 1. init connection or service 2. loop over events #pause events are simple and high level 1. connection and disconnection 2. message received and sent #pause message have a simple format: length + value #pause And that's it. ## When LibIPC is useful when the app: - cannot be a simple shell command - needs a bidirectional communication - is an abstraction over a library ## Available libraries * DBUS * libevent * even more complicated stuff * RPC-style, like Corba #pause * ... or bare libc api * shared memory * pipes * sockets (unix, inet, inet6) ## DBUS * not well suited for our needs (a polite way to say: what a bloody mess) Is it designed *NOT* to be used? * over-engineered * complex * documentation isn't great * no code example ## DBUS (bonus page!) DBUS feels obsolete: a big chunk of the documentation is about message format. Just use CBOR already! #pause They even admit they did a poor job on the C part: > There is a low-level C binding, but that is probably too detailed > and cumbersome for anything but writing other bindings. #pause Oh. And C++. YOU SHALL NOT PASS! This is a Linux requirement nowadays, wth? ## libevent * works with epoll and kqueue * great performances * works on Linux and *BSD * a bit complicated ## Bare libc api shared memory and semaphores * (kinda) complicated api * not about exchanging messages pipes, sockets * lack a conventional message format ... but that's about it * Great to start with! All have great performances to exchange data. What is slow is the function to _wait_ for new events. ## LibIPC's choice Unix sockets - fast, simple, reliable, bidirectional - remote connections will have their own service (ex: TCPd) Dumbest possible message format - length + value - build your own format on top of it! Wait on file descriptors with poll(2) - slow, but available everywhere - may upgrade to libevent ## LibIPC history (1/2) 1. based on pipes * because we gotta go fast! * ... but implementation was a bit of a mess #pause 2. rewrite to work with unix sockets * performances are excellent, no need for _absolute best_ * way nicer implementation * select(2) for listening on file descriptors #pause * ... wait, does select(2) support more than 1024 connections? ## LibIPC history (2/2) 3. rewrite using poll(2) * many bugfixes later, way more tested than before * implementation was (kinda) production-ready * implementation was simple: < 2000 lines of C code Still wasn't as simple as I wanted ## Current implementation of libIPC Written in Zig bindings available in Crystal * as well as fancy mappings: JSON and CBOR class serialization #pause epoll (Linux) and kqueue (*BSD) were avoided * 'cause callbacks hell => harder to read and to write code #pause * but we need them for better performances with many connections though, API should stay the same for non threaded applications (simple implementation) #pause LibIPC doesn't handle parallelism, yet ## How libIPC works LibIPC has a high level API for the user 1. init a connection (client) or create an unix socket (service) example: ipc_service_init (context, "service") #pause 2. loop, wait for events listening to file descriptors (libIPC ones or not) example: while(1) { wait_event (context, &event, &timer) switch (event.type) { case IPC_CONNECTION : ... case IPC_DISCONNECTION : ... case IPC_MESSAGE: { struct ipc_message *m = event.m; ... } } } ## How libIPC works 3. send messages ```c struct ipc_message m m.payload = ... m.length = strlen(m.payload) m.fd = event.fd m.type = ... m.user_type = ... ipc_write (context, &m) ``` #pause 4. add and remove fd from the context ipc_add_fd (context, fd) ipc_del_fd (context, fd) ## How libIPC works LibIPC also helps to create "protocol daemons" like TCPd with automatic switching between file descriptors LibIPC takes callbacks to obtain libipc payloads inside arbitrary message structure Example: websocketd. Clients exchange data with a libipc service through websockets messages. websocketd binds both the client and its service file descriptors, then provides the libipc a callback to extract libipc messages from the websocket messages sent by the client. Same thing the other way. ipc_switching_callbacks (context, client_fd, cb_in, cb_out) ## libIPC internal structures Main goal: simplest possible structures Examples: Message struct ipc_message { char type; => Internal message type, used by protocol daemons. char user_type; => User-defined message type (arbitrary). int fd; => File descriptor concerned about this message. uint32_t length; => Payload length. char *payload; => Actual payload. }; Context of the whole networking state struct ipc_ctx { struct ipc_connection_info *cinfos; => Keeps track of connections. struct pollfd *pollfd; => List of "pollfd" structures within cinfos, so we can pass it to poll(2). size_t size; => Size of the connection list. struct ipc_messages tx; => Messages to send. struct ipc_switchings switchdb; => Relations between fd. }; ## Future of libIPC LibIPC will be rewritten in Zig * simpler to read and write * data structures are simpler to create with comptime * less code redundancy (more generic functions) * already existing error management as written in current libIPC * already existing loggin system * no more C's pitfalls * way better at exposing bugs * thanks to a better type system * way safer: cannot ignore errors * so I won't * simpler to cross-compile: same standard library for every OSs * simpler (and more secure) memory management Also: this won't change existing bindings! No excuses! ## Why not use it? Current limitations * performances (libIPC is based on poll(2), not epoll nor kqueue) * it really isn't an issue until you have hundreds or thousands of clients * parallelism is not permitted nothing in libIPC is thread-safe The future Zig implementation will overcome these issues. ## Questions? Ask! `karchnu at karchnu.fr`