[{"content":"In this final post of the series we take a look at Network namespaces. As we hinted at during the intro post, a network namespace isolates network related resources - a process running in a distinct network namespace has its own networking devices, routing tables, firewall rules etc. We can see this in action immediately by inspecting our current network environment.\nThe ip Command Since we will be interacting with network devices in this post, we will re-enforce the superuser requirements that we relaxed in the previous posts. From now on, we will assume that both ip and isolate are being run with sudo.\n$ ip link list 1: lo: \u0026lt;LOOPBACK,UP,LOWER_UP\u0026gt; mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: ens33: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 00:0c:29:96:2e:3b brd ff:ff:ff:ff:ff:ff Star of the show here is the ip command - the Swiss Army Knife for networking in Linux - and we will use it extensively in this post. Right now we have just run the link list subcommand to show us what networking devices are currently available in the system (here we have lo, the loopback interface and ens33 an ethernet LAN interface). As with all other namespaces, the system starts with an initial network namespace within to which all processes belong unless specified otherwise. Running this ip link list command as-is gives us the networking devices owned by the initial namespace (since our shell and the ip command belong to this namespace).\nNamed Network Namespaces Let\u0026rsquo;s create a new network namespace:\n$ ip netns add coke $ ip netns list coke Again, we\u0026rsquo;ve used the ip command. Its netns subcommand allows us to play with network namespaces - for example we can create new network namespaces using the add subcommand of netns and use list to, well, list them.\nYou might notice that list only returned our newly created namespace - shouldn\u0026rsquo;t it return at least two, the other one being the initial namespace that we mentioned earlier? The reason for this is that ip creates what is called a named network namespace, which simply is a network namespace that is identifiable by a unique name (in our case coke). Only named network namespaces are shown via list and the initial network namespace isn\u0026rsquo;t named. Named network namespaces are easier to get a hold of. For example, a file is created for each named network namespace under the /var/run/netns folder and can be used by a process that wants to switch to its namespace. Another property of named network namespaces is that they can exist without having any process as a member - unlike non-named ones that will be deleted once all member processes exit. Now that we have a child network namespace, we can see networking from its perspective.\nWe will be using the command prompt C$ to emphasize a shell running inside a child network namespace.\n$ ip netns exec coke bash C$ ip link list 1: lo: \u0026lt;LOOPBACK\u0026gt; mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 The exec $namespace $command subcommand executes $command in the named network namespace $namespace. Here we ran a shell inside the coke namespace and listed the network devices available. We can see that at least our ens33 device has disappeared. The only device that shows up is loopback and even that interface is down.\nC$ ping 127.0.0.1 connect: Network is unreachable We should be used to this by now, the default setup for namespaces are usually very strict. For network namespaces as we can see, no devices except loopback will be present. We can bring the loopback interface up without any paperwork though:\nC$ ip link set dev lo up C$ ping 127.0.0.1 PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.034 ms ... Network Isolation We\u0026rsquo;re already starting to see that by running a process in a nested network namespace like coke, we can be sure that it is isolated from the rest of the system as far as networking is concerned. Our shell process running in coke can only communicate via loopback - this means that it can only communicate with processes that are also members of the coke namespace but currently there are no other member processes (and in the name of isolation, we would like that it remains that way) so it\u0026rsquo;s a bit lonely. Let\u0026rsquo;s try to relax this isolation a bit, we will create a tunnel through which processes in coke can communicate with processes in our initial namespace. Now, any network communication has to go via some network device and a device can exist in exactly one network namespace at any given time so communication between any two processes in different namespaces must go via at least two network devices - one in each network namespace.\nVeth Devices We will use a virtual ethernet network device (or veth for short) to fulfill our need. Veth devices are always created as a pair of devices in a tunnel-like fashion so that messages written to the device on one end comes out of the device on the other end. You might guess that we could easily have one end in the initial network namespace and the other in our child network namespace and have all inter-network-namespace communication go via the respective veth end device (and you would be correct).\n# Create a veth pair (veth0 \u0026lt;=\u0026gt; veth1) $ ip link add veth0 type veth peer name veth1 # Move the veth1 end to the new namespace $ ip link set veth1 netns coke # List the network devices from inside the new namespace C$ ip link list 1: lo: \u0026lt;LOOPBACK,UP,LOWER_UP\u0026gt; mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 7: veth1@if8: \u0026lt;BROADCAST,MULTICAST\u0026gt; mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether ee:16:0c:23:f3:af brd ff:ff:ff:ff:ff:ff link-netnsid 0 Our veth1 device now shows up in the coke namespace. But to make the veth pair functional, we need to give them both IP addresses and bring the interfaces up. We will do this in their respective network namespace.\n# In the initial namespace $ ip addr add 10.1.1.1/24 dev veth0 $ ip link set dev veth0 up # In the coke namespace C$ ip addr add 10.1.1.2/24 dev veth1 C$ ip link set dev veth1 up C$ ip addr show veth1 7: veth1@if8: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether ee:16:0c:23:f3:af brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.1.1.2/24 scope global veth1 valid_lft forever preferred_lft forever inet6 fe80::ec16:cff:fe23:f3af/64 scope link valid_lft forever preferred_lft forever We should see that veth1 is up and has our assigned address 10.1.1.2 - the same should happen for veth0 in the initial namespace. Now we should be able to do an inter-namespace ping between two processes running in both namespaces.\n$ ping -I veth0 10.1.1.2 PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data. 64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.041 ms ... C$ ping 10.1.1.1 PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data. 64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.067 ms ... Implementation The source code for this post can be found here.\nAs usual, we will now try to replicate what we\u0026rsquo;ve seen so far in code. Specifically, we will need to do the following:\nExecute the command within a new network namespace. Create a veth pair (veth0 \u0026lt;=\u0026gt; veth1). Move the veth1 device to the new namespace. Assign IP addresses to both devices and bring them up. Step 1 is straight-forward, we create our command process in a new network namespace by adding the CLONE_NEWNET flag to clone:\nint clone_flags = SIGCHLD | CLONE_NEWUTS | CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWNET; Netlink For the remaining steps, we will primarily be using the Netlink interface to communicate with Linux. Netlink is primarily used for communication between regular applications (like isolate) and the Linux kernel. It exposes an API on top of sockets, based on a protocol that determines message structure and content. Using this protocol we can send messages that Linux receives and translates to requests - like create a veth pair with names veth0 and veth1. Let\u0026rsquo;s start by creating our netlink socket. In it, we specify that we want to use the NETLINK_ROUTE protocol - this protocol covers implementations for network routing and device management.\nint create_socket(int domain, int type, int protocol) { int sock_fd = socket(domain, type, protocol); if (sock_fd \u0026lt; 0) die(\u0026#34;cannot open socket: %m\\n\u0026#34;); return sock_fd; } int sock_fd = create_socket( PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); Netlink Message Format A Netlink message is a 4-byte aligned block of data containing a header (struct nlmsghdr) and a payload. The header format is described here. The Network Interface Service (NIS) Module specifies the format (struct ifinfomsg) that payload related to network interface administration must begin with. Our request will be represented by the following C struct:\n#define MAX_PAYLOAD 1024 struct nl_req { struct nlmsghdr n; // Netlink message header struct ifinfomsg i; // Payload starting with NIS module info char buf[MAX_PAYLOAD]; // Remaining payload }; Netlink Attributes The NIS module requires the payload to be encoded as Netlink attributes. Attributes provide a way to segment the payload into subsections. An attribute has a type and a length in addition to a payload containing its actual data. The Netlink message payload will be encoded as a list of attributes (where any such attribute can in turn have nested attributes) and we will have some helper functions to populate it with attributes. In code, an attribute is represented by the rtattr struct in the linux/rtnetlink.h header file as:\nstruct rtattr { unsigned short rta_len; unsigned short rta_type; }; rta_len is the length of the attribute\u0026rsquo;s payload which immediately follows the rt_attr struct in memory (i.e the next rta_len bytes). How the content of this payload is interpreted is dictated by rta_type and possible values are entirely dependent on the receiver implementation and the request being sent. In an attempt to put this all together, let\u0026rsquo;s see how isolate makes a netlink request to create veth pair with the following function create_veth that fulfills step 2:\n// ip link add ifname type veth ifname name peername void create_veth(int sock_fd, char *ifname, char *peername) { __u16 flags = NLM_F_REQUEST // This is a request message | NLM_F_CREATE // Create the device if it doesn\u0026#39;t exist | NLM_F_EXCL // If it already exists, do nothing | NLM_F_ACK; // Reply with an acknowledgement or error // Initialise request message. struct nl_req req = { .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), .n.nlmsg_flags = flags, .n.nlmsg_type = RTM_NEWLINK, // This is a netlink message .i.ifi_family = PF_NETLINK, }; struct nlmsghdr *n = \u0026amp;req.n; int maxlen = sizeof(req); /* * Create an attribute r0 with the veth info. e.g if ifname is veth0 * then the following will be appended to the message * { * rta_type: IFLA_IFNAME * rta_len: 5 (len(veth0) + 1) * data: veth0\\0 * } */ addattr_l(n, maxlen, IFLA_IFNAME, ifname, strlen(ifname) + 1); // Add a nested attribute r1 within r0 containing iface info struct rtattr *linfo = addattr_nest(n, maxlen, IFLA_LINKINFO); // Specify the device type is veth addattr_l(\u0026amp;req.n, sizeof(req), IFLA_INFO_KIND, \u0026#34;veth\u0026#34;, 5); // Add another nested attribute r2 struct rtattr *linfodata = addattr_nest(n, maxlen, IFLA_INFO_DATA); // This next nested attribute r3 one contains the peer name e.g veth1 struct rtattr *peerinfo = addattr_nest(n, maxlen, VETH_INFO_PEER); n-\u0026gt;nlmsg_len += sizeof(struct ifinfomsg); addattr_l(n, maxlen, IFLA_IFNAME, peername, strlen(peername) + 1); addattr_nest_end(n, peerinfo); // end r3 nest addattr_nest_end(n, linfodata); // end r2 nest addattr_nest_end(n, linfo); // end r1 nest // Send the message send_nlmsg(sock_fd, n); } As we can see, we need to be precise about what we send here - we had to encode the message in the exact way it will be interpreted by the kernel implementation and here it took us 3 nested attributes to do so. I\u0026rsquo;m sure this is documented somewhere even though I was unable to find it after some googling - I mostly figured this out via strace and the ip command source code. Next, for step 3, is a method that, given an interface name ifname and a network namespace file descriptor netns, moves the device associated with that interface to the specified network namespace.\n// $ ip link set veth1 netns coke void move_if_to_pid_netns(int sock_fd, char *ifname, int netns) { struct nl_req req = { .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), .n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, .n.nlmsg_type = RTM_NEWLINK, .i.ifi_family = PF_NETLINK, }; addattr_l(\u0026amp;req.n, sizeof(req), IFLA_NET_NS_FD, \u0026amp;netns, 4); addattr_l(\u0026amp;req.n, sizeof(req), IFLA_IFNAME, ifname, strlen(ifname) + 1); send_nlmsg(sock_fd, \u0026amp;req.n); } After creating the veth pair and moving one end to our target network namespace, step 4 has us assigning both end devices IP addresses and bringing their interfaces up. For that we have a helper function if_up which, given an interface name ifname and ip address ip, assigns ip to the device ifname and brings it up. For brevity we do not show those here but they can be found here instead. Finally, we bring these methods together to prepare our network namespace for our command process.\nstatic void prepare_netns(int child_pid) { char *veth = \u0026#34;veth0\u0026#34;; char *vpeer = \u0026#34;veth1\u0026#34;; char *veth_addr = \u0026#34;10.1.1.1\u0026#34;; char *vpeer_addr = \u0026#34;10.1.1.2\u0026#34;; char *netmask = \u0026#34;255.255.255.0\u0026#34;; // Create our netlink socket int sock_fd = create_socket( PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); // ... and our veth pair veth0 \u0026lt;=\u0026gt; veth1. create_veth(sock_fd, veth, vpeer); // veth0 is in our current (initial) namespace // so we can bring it up immediately. if_up(veth, veth_addr, netmask); // ... veth1 will be moved to the command namespace. // To do that though we need to grab a file descriptor // to and enter the commands namespace but first we must // remember our current namespace so we can get back to it // when we\u0026#39;re done. int mynetns = get_netns_fd(getpid()); int child_netns = get_netns_fd(child_pid); // Move veth1 to the command network namespace. move_if_to_pid_netns(sock_fd, vpeer, child_netns); // ... then enter it if (setns(child_netns, CLONE_NEWNET)) { die(\u0026#34;cannot setns for child at pid %d: %m\\n\u0026#34;, child_pid); } // ... and bring veth1 up if_up(vpeer, vpeer_addr, netmask); // ... before moving back to our initial network namespace. if (setns(mynetns, CLONE_NEWNET)) { die(\u0026#34;cannot restore previous netns: %m\\n\u0026#34;); } close(sock_fd); } Then we can call prepare_netns right after we\u0026rsquo;re done setting up the user namespace.\n... // Get the writable end of the pipe. int pipe = params.fd[1]; prepare_userns(cmd_pid); prepare_netns(cmd_pid); // Signal to the command process we\u0026#39;re done with setup. ... Let\u0026rsquo;s try it out!\n$ sudo ./isolate sh ===========sh============ $ ip link list 1: lo: \u0026lt;LOOPBACK\u0026gt; mtu 65536 qdisc noop state DOWN qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 31: veth1@if32: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN\u0026gt; mtu 1500 qdisc noqueue state UP qlen 1000 link/ether 2a:e8:d9:df:b4:3d brd ff:ff:ff:ff:ff:ff # Verify inter-namespace connectivity $ ping 10.1.1.1 PING 10.1.1.1 (10.1.1.1): 56 data bytes 64 bytes from 10.1.1.1: seq=0 ttl=64 time=0.145 ms ","permalink":"https://ifeanyi.co/posts/linux-namespaces-part-4/","summary":"\u003cp\u003eIn this final post of the series we take a look at \u003ca href=\"http://man7.org/linux/man-pages/man8/ip-netns.8.html\" target=\"_blank\" rel=\"noopener\"\u003eNetwork namespaces\u003c/a\u003e.\nAs we hinted at during the \u003ca href=\"/posts/linux-namespaces-part-1/\"\u003eintro post\u003c/a\u003e, a network namespace\nisolates network related resources - a process running in a distinct network namespace has its own networking devices, routing tables, firewall rules etc.\nWe can see this in action immediately by inspecting our current network environment.\u003c/p\u003e\n\u003ch2 id=\"the-ip-command\"\u003eThe ip Command\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003eSince we will be interacting with network devices in this post, we will re-enforce the superuser requirements that we relaxed in the previous posts. From now on, we will assume that both \u003ccode\u003eip\u003c/code\u003e and \u003ccode\u003eisolate\u003c/code\u003e are being run with \u003ccode\u003esudo\u003c/code\u003e.\u003c/p\u003e\n\u003c/blockquote\u003e","title":"A deep dive into Linux namespaces, part 4"},{"content":"Mount namespaces isolate filesystem resources. This pretty much covers everything that has to do with files on the system. Among the encapsulated resources is a file containing the list of mount points that are visible to a process and as we hinted at in the intro post, isolation can enforce that changing the list (or any other file) within some mount namespace instance M does not affect that list in a different instance (so that only the processes in M observe the changes).\nMount Points You might be wondering why we just zoomed in on a seemingly random file that contains a list - what\u0026rsquo;s so special about it? The list of mount points determines a process\u0026rsquo; entire view of available filesystems on the system and since we\u0026rsquo;re in Linux land with the everything is a file mantra, the visibility of pretty much every resource is dictated by this view - from actual files and devices to information about which other processes are also running in the system. So it\u0026rsquo;s a huge security win for isolate to be able to dictate exactly what parts of the system we want commands that we run to be aware of. Mount namespaces combined with mount points are a very powerful tool that lets us acheive this. We can see mount points visible to a process with id $pid via the /proc/$pid/mounts file - its contents is the same for all processes that belong to the same mount namespace as $pid:\n$ cat /proc/$$/mounts ... /dev/sda1 / ext4 rw,relatime,errors=remount-ro,data=ordered 0 0 ... Spotted somewhere in the list returned on my system is the /dev/sda1 device mounted at / (yours might differ). This is the disk device hosting the root filesystem that contains all the good stuff needed for the system to start and run properly so it would be great if isolate runs commands without them knowing about filesystems like these. Let\u0026rsquo;s start by running a terminal in its own mount namespace:\nStrictly speaking, we don\u0026rsquo;t need superuser access to work with new mount namespaces as long as we include the user namespace setup procedures of the previous post. As a result, in this post we will only assume that unshare commands within the terminal are running as superuser. isolate doesn\u0026rsquo;t need this assumption.\n# The -m flag creates a new mount namespace. $ unshare -m bash $ cat /proc/$$/mounts ... /dev/sda1 / ext4 rw,relatime,errors=remount-ro,data=ordered 0 0 ... Hmmm, we can still see the same list as in the root mount namespace. Especially after witnessing in the previous post that a new user namespace begins with a clean slate, it may seem that the -m flag we passed to unshare didn\u0026rsquo;t have any effect.\nThe shell process is in fact running in a different mount namespace (we can verify this by comparing the symlinked file ls -l /proc/$$/ns/mnt to that of another shell running in the root mount namespace). The reason we still see the same list is that whenever we create a new mount namespace (child), a copy of the mount points of the mount namespace where the creation took place (parent) is used as the child\u0026rsquo;s list. Now any changes we make to this file (e.g by mounting a filesystem) will be invisible to all other processes.\nHowever, changing pretty much any other file at this point will affect other processes because we are still referencing the exact same files (Linux only makes copies of special files like the mount points list). This means that we currently have minimal isolation. If we want to limit what our command process will see, we must update this list ourselves. Now, on one extreme, since we\u0026rsquo;re trying to be security conscious, we could just say F* it and have isolate clear the entire list before executing the command but that will render the command useless since every program at least has dependencies on resources like operating system files, which in turn, are backed by some filesystem. On the other extreme, we could also just execute the command as is, sharing with it, the same filesystems that contain the necessary system files that it requires but this obviously defeats the purpose of this isolation thing that we have going on. The sweet spot would provide the program with its very own copy of dependencies and system files that it requires to run, all sandboxed so that it can make any changes to them without affecting other programs on the system. In the best case scenario, we would wrap these files in a filesystem and mount it as the root filesystem (at the root directory /) before executing the un-suspecting program. The idea is, because everything reachable by a process must go via the root filesystem and because we will know exactly what files we put in there for the command process, we will rest easy knowing that it is properly isolated from the rest of the system. Alright, this sounds good in theory and in order to pull it off, we will do the following:\nCreate a copy of the dependencies and system files needed by the command. Create a new mount namespace. Replace the root filesystem in the new mount namespace with one that is made up of our system files copy. Execute the program inside the new mount namespace. Root Filesystems A question that arises already at step 1 is which system files are even needed by the command we want to run? We could rummage in our own root filesystem and ask this question for every file that we encounter and only include the ones where the answer is yes but that sounds painful and unnecessary. Also, we don\u0026rsquo;t even know what command isolate will be executing to begin with. If only people have had this same issue and gathered a set of system files, generic enough to serve as a base right out of the box for a majority of programs out there? Luckily there are many projects that do this! One of which is the Alpine Linux project (this is its main function when you start FROM alpine:xxx in your Dockerfile). Alpine provides root filesystems that we can use for our purposes. If you are following along, you can get a copy of their minimal root filesystem (MINI ROOT FILESYSTEM) for x86_64 here. The latest version at the time of writing and that we will use in this post is v3.10.1.\n$ wget http://dl-cdn.alpinelinux.org/alpine/v3.10/releases/x86_64/alpine-minirootfs-3.10.1-x86_64.tar.gz $ mkdir rootfs $ tar -xzf alpine-minirootfs-3.10.1-x86_64.tar.gz -C rootfs $ ls rootfs bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var The rootfs directory has familiar files just like our own root filesystem at / but checkout how minimal it is - quite a few of these directories are empty:\n$ ls rootfs/{mnt,dev,proc,home,sys} # empty This is great! we can give the command that we launch a copy of this and it could sudo rm -rf / for all we care, no one else will be bothered.\nPivot root Given our new mount namespace and a copy of system files, we would like to mount those files on the root directory of the new mount namespace without pulling the rug from under our feet. Linux has us covered here with the pivot_root system call (there is an associated command) that allows us to control what a processes sees as the root filesystem.\nThe command takes two arguments pivot_root new_root put_old where new_root is the path to the filesystem containing the soon-to-be root filesystem and put_old is a path to a directory. It works by:\nMounting the root filesystem of the calling process on put_old. Mounting the filesystem pointed to by new_root as the current root filesystem at /. Let\u0026rsquo;s see this in action. In our new mount namespace, we start by creating a filesystem out of our alpine files:\n$ unshare -m bash $ mount --bind rootfs rootfs Next we pivot root:\n$ cd rootfs $ mkdir put_old $ pivot_root . put_old $ cd / # We should now have our new root. e.g if we: $ ls proc # proc is empty # And the old root is now in put_old $ ls put_old bin dev home lib lost+found mnt proc run srv tmp var boot etc initrd.img lib64 media opt root sbin sys usr vmlinuz Finally, we unmount the old filesystem from put_old so that the nested shell cannot access it.\n$ umount -l put_old With that, we can run any command in our shell and they will run using our custom alpine root filesystem, unaware of the orchestration that led up to their execution. And our precious files on the old filesystem are safe beyond their reach.\nImplementation The source code for this post can be found here.\nWe can replicate what we just accomplished in code, swapping the pivot_root command for the corresponding system call. First, we create our command process in a new mount namespace by adding the CLONE_NEWNS flag to clone.\nint clone_flags = SIGCHLD | CLONE_NEWUTS | CLONE_NEWUSER | CLONE_NEWNS; Next, we create a function prepare_mntns that, given a path to a directory containing system files (rootfs), sets up the current mount namespace by pivoting the root of the current process to rootfs as we did earlier.\nstatic void prepare_mntns(char *rootfs) { const char *mnt = rootfs; if (mount(rootfs, mnt, \u0026#34;ext4\u0026#34;, MS_BIND, \u0026#34;\u0026#34;)) die(\u0026#34;Failed to mount %s at %s: %m\\n\u0026#34;, rootfs, mnt); if (chdir(mnt)) die(\u0026#34;Failed to chdir to rootfs mounted at %s: %m\\n\u0026#34;, mnt); const char *put_old = \u0026#34;.put_old\u0026#34;; if (mkdir(put_old, 0777) \u0026amp;\u0026amp; errno != EEXIST) die(\u0026#34;Failed to mkdir put_old %s: %m\\n\u0026#34;, put_old); if (syscall(SYS_pivot_root, \u0026#34;.\u0026#34;, put_old)) die(\u0026#34;Failed to pivot_root from %s to %s: %m\\n\u0026#34;, rootfs, put_old); if (chdir(\u0026#34;/\u0026#34;)) die(\u0026#34;Failed to chdir to new root: %m\\n\u0026#34;); if (umount2(put_old, MNT_DETACH)) die(\u0026#34;Failed to umount put_old %s: %m\\n\u0026#34;, put_old); } We need to call this function from our code and it must be done by our command process in cmd_exec (since its the one running within the new mount namespace), before the actual command begins execution.\n... // Wait for \u0026#39;setup done\u0026#39; signal from the main process. await_setup(params-\u0026gt;fd[0]); prepare_mntns(\u0026#34;rootfs\u0026#34;); ... Let\u0026rsquo;s try it out:\n$ ./isolate sh ===========sh============ $ ls put_old # put_old is empty. Hurray! # What does our new mount list look like? $ cat /proc/$$/mounts cat: cant open \u0026#39;/proc/1431/mounts\u0026#39;: No such file or directory # Hmmm, what other processes are running? $ ps aux PID USER TIME COMMAND # Empty! eh? This output shows something strange - we\u0026rsquo;re unable to verify the mount list that we have fought so hard for, and ps tells us that there are no processes running on the system (not even the current process or ps itself?). Its more likely that we broke something while setting up the mount namespace.\nPID Namespaces We\u0026rsquo;ve mentioned the /proc directory a few times so far in this series and if you were familiar with it, then you\u0026rsquo;re probably not surprised that ps came up empty since we saw earlier that the directory was empty within this mount namespace (when we got it from the alpine root filesystem). The /proc directory in Linux is usually used to expose a special filesystem (called the proc filesystem) that is managed by Linux itself. Linux uses it to expose information about all processes running in the system as well as other system information with regards to devices, interrupts etc. Whenever we run a command like ps which accesses information about processes in the system, it looks to this filesystem to fetch information.\nIn other words, we need to spin up a proc filesystem. Luckily, this basically involves telling Linux that we need one, preferably mounted at /proc. But we can\u0026rsquo;t do so just yet since our command process is still dependent on the same proc filesystem as isolate and every other regular process in the system - to cut this dependency, we need to run it inside its own PID namespace. The PID namespace isolates process IDs in the system. One effect is that processes running in different PID namespaces can have the same process ID without conflicting with each other. Granted that we\u0026rsquo;re isolating this namespace because we want to give as much isolation as we can to our running command, a more interesting reason we show it here is that mounting the proc filesystem requires root privileges and the current PID namespace is owned by the root user namespace where we do not have sufficient permissions (if you remember from the previous post, root to the command process isn\u0026rsquo;t really root). So, we must be running within a PID namespace owned by the user namespace that recognizes our command process as root. We can create a new PID namespace by passing the CLONE_NEWPID to clone:\nint clone_flags = SIGCHLD | CLONE_NEWUTS | CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWPID; Next, we add a function prepare_procfs that sets up the proc filesystem by mounting one within the currently mount and pid namespace.\nstatic void prepare_procfs() { if (mkdir(\u0026#34;/proc\u0026#34;, 0555) \u0026amp;\u0026amp; errno != EEXIST) die(\u0026#34;Failed to mkdir /proc: %m\\n\u0026#34;); if (mount(\u0026#34;proc\u0026#34;, \u0026#34;/proc\u0026#34;, \u0026#34;proc\u0026#34;, 0, \u0026#34;\u0026#34;)) die(\u0026#34;Failed to mount proc: %m\\n\u0026#34;); } Finally, we call the function right before unmounting put_old in our prepare_mntns function, after we have setup the mount namespace and changed to the root directory.\nstatic void prepare_mntns(char *rootfs) { ... prepare_procfs(); if (umount2(put_old, MNT_DETACH)) die(\u0026#34;Failed to umount put_old %s: %m\\n\u0026#34;, put_old); ... } We can take isolate for another spin:\n$ ./isolate sh ===========sh============ $ ps PID USER TIME COMMAND 1 root 0:00 sh 2 root 0:00 ps This looks much better! The shell sees itself as the only process running on the system and running as PID 1 (since it was the first process to start in this new PID namespace). This post covered two namespaces and isolate racked up two new features as a result. In the next post, we will be looking at isolation via Network namespaces. There, we will have to deal with some intricate, low-level network configuration in an attempt to enable network communication between processes in different network namespaces.\n","permalink":"https://ifeanyi.co/posts/linux-namespaces-part-3/","summary":"\u003cp\u003e\u003ca href=\"http://man7.org/linux/man-pages/man7/mount_namespaces.7.html\" target=\"_blank\" rel=\"noopener\"\u003eMount namespaces\u003c/a\u003e isolate filesystem resources.\nThis pretty much covers everything that has to do with files on the system.\nAmong the encapsulated resources is a file containing the list of \u003ca href=\"http://www.linfo.org/mount_point.html\" target=\"_blank\" rel=\"noopener\"\u003emount points\u003c/a\u003e that are visible to a process and as we hinted at in the \u003ca href=\"/posts/linux-namespaces-part-1/\"\u003eintro post\u003c/a\u003e, isolation can enforce that changing the list (or any other file) within some mount namespace instance \u003ccode\u003eM\u003c/code\u003e does not affect that list in a different instance (so that only the processes in \u003ccode\u003eM\u003c/code\u003e observe the changes).\u003c/p\u003e","title":"A deep dive into Linux namespaces, part 3"},{"content":"In the previous post we dipped our toes into namespace waters and in the process, saw how simple it was to to get a process up and running with an isolated UTS namespace. In this post, we shine a spotlight on the User namespace.\nUser namespaces isolate, among other security related resources, user and group identities in the system. We will focus solely on the user and group ID (UID and GID respectively) resources in this post as they are fundamental role to carrying out permissions checks and other security related activities throughout the system.\nOn Linux, these IDs are simply integers that identify users and groups in the system and every process is assigned a few of them in order to determine what operations/resources that process can and cannot access - the process\u0026rsquo; ability to cause damage is dependent on the permissions associated with its assigned IDs\nUser Namespaces We will illustrate the capabilities of user namespaces using only user IDs. The exact same interactions apply to group IDs, which will be visited later on in the post.\nA user namespace has its own copy of user and group identifiers. Isolation then enables a process to be associated with a different set of IDs depending on the user namespace to which it belongs at any given moment. For example, a process $pid might be running as root (UID 0) in a user namespace P and suddenly continues running as proxy (UID 13) after it switched to a different user namespace Q.\nUser namespaces can be nested! This means that a user namespace instance (parent) can have zero or more child namespaces and each child namespace can, in turn, have its own child namespaces and so on\u0026hellip;(until a limit of 32 nested levels). When a new user namespace C is created, Linux sets the current user namespace P of the process that creates C to be C\u0026rsquo;s parent and this cannot be changed afterwards. The effect is that all user namespaces have exactly one parent, forming a tree structure of namespaces. And as with trees, the exception to this is at the top where we have the root (or initial or default) namespace - unless you\u0026rsquo;re already doing some container magic, this is most likely the user namespace to which all your processes belong as it is the single user namespace when the system starts.\nIn this post, we will use the command prompts P$ and C$ to denote a shell that is currently running within the parent P and child C user namespace respectively.\nUser ID Mappings A user namespace essentially contains a set of IDs and some information linking those IDs to the set of IDs of other user namespaces - this duo determines a process\u0026rsquo; entire view of IDs available in the system. Let\u0026rsquo;s see what this might look like:\nP$ whoami iffy P$ id uid=1000(iffy) gid=1000(iffy) In another terminal window let\u0026rsquo;s run a shell using unshare (the -U flag creates the process in a new user namespace):\nP$ whoami iffy P$ unshare -U bash # Enter a new shell that runs within a nested user namespace C$ whoami nobody C$ id uid=65534(nobody) gid=65534(nogroup) C$ ls -l my_file -rw-r--r-- 1 nobody nogroup 0 May 18 16:00 my_file Wait, who? Now that we\u0026rsquo;re in a nested shell in C the current user becomes nobody? We might have guessed that because C is a new user namespace, the process could have a different view of IDs so we might not have expected it to remain iffy, but nobody is no fun 😒. On the bright side, this is great because we got the isolation that we asked for. Our process now has a different (albeit broken) view of IDs in the system - currently it sees everybody as nobody and every group as nogroup. The information linking UIDs from one namespace to another is called a user ID mapping. It represents lookup tables from IDs in the current user namespace to IDs in other user namespaces and every user namespace is associated with exactly one UID mapping (in addition to one GID mapping for group IDs).\nThis mapping is what\u0026rsquo;s broken within our unshare shell. It turns out that new user namespaces start out with an empty mapping and as a result, Linux defaults to the dreaded nobody user. We need to fix this before we can do any useful work inside our new namespace. For instance, currently, system calls (e.g setuid) that try to work with UIDs will fail. But fear not! inline with the all-of-the-things-as-a-file tradition, Linux exposes this mapping via the /proc filesystem at /proc/$pid/uid_map (/proc/$pid/gid_map for GID) where $pid is a process ID. We will refer to these two files as map files.\nMap files Map files are special files on the system. Special how? well, the kind that returns different contents whenever you read from it, depending on what process you\u0026rsquo;re reading from. For instance, the map file /proc/$pid/uid_map returns a mapping from UIDs in the user namespace to which the process $pid belongs, to UIDs in the user namespace of the reading process and as a result, the content returned to process X may differ from what is returned to process Y even though they read the same map file at the same time. Specifically, a process X that reads a UID map file /proc/$pid/uid_map receives a set of rows. Each row maps a range of contiguous UIDs in process $pids\u0026rsquo;s user namespace C to a corresponding UID range in the other namespace.\nEach row has the format $fromID $toID $length where:\n$fromID is the start UID of the range for the user namespace of process $pid $length is the length of the range. Translating $toID is dependent on the reading process X. If X belongs to a different user namespace U, then $toID is the start UID of the range in U to which $fromID maps. Otherwise, $toID is the start UID of the range in P, the parent user namespace of C. For example, if a process reads the file /proc/1409/uid_map and among the received rows is 15 22 5, then UIDs 15 through 19 in process 1409\u0026rsquo;s user namespace maps to UIDs 22-26 in the reading process\u0026rsquo; distinct user namespace.\nOn the other hand, if a process reads from the file /proc/$$/uid_map (or the map file for any process that belongs to the same user namespace as it does) and receives 15 22 5, then UIDs 15 through 19 in its user namespace C maps to UIDs 22 through 26 in C\u0026rsquo;s parent user namespace.\nLet\u0026rsquo;s try this out:\nP$ echo $$ 1442 # In a new user namespace... C$ echo $$ 1409 # C has no mappings to its parent since it is new C$ cat /proc/1409/uid_map # Empty # While root namespace P has dummy mappings for all # UIDs to the same UID in its non-existent parent P$ cat /proc/1442/uid_map 0 0 4294967295 # UIDs 0 through 4294967294 in P is mapped # to 4294967295 - the special no user ID - in C. C$ cat /proc/1422/uid_map 0 4294967295 4294967295 Okay, that wasn\u0026rsquo;t very exciting since these were two extreme cases but it does tell us a few things:\nA newly created user namespace will in fact have empty map files. The UID 4294967295 is unmapped and unusable, even in the root namespace. Linux treats this UID specially to represent that there is no user ID. Writing UID Map files To fix our newly created user namespace C, we simply need to provide our desired mappings by writing them to the map file for any process that belongs to C (we cannot update this file after writing to it). Writing to this file tells Linux two things:\nWhat UIDs are available to processes that belong to that target user namespace C. What UIDs in the current user namespace correspond to the UIDs in C. For instance, if we, from the parent user namespace P, write the following to the user map file for child namespace C:\n0 1000 1 3 0 1 we essentially tell Linux that:\nAs far as processes in C are concerned, the only UIDs that exist in the system are UIDs 0 and 3 - e.g a system call to setuid(9) will always fail with something like invalid user id. UIDs 1000 and 0 in P correspond to UIDs 0 and 3 in C - e.g if a process, running as UID 1000 in P, switches to C, it will observe its UID has become root 0 after the switch. Owner Namespaces And Privileges In the previous post we mentioned that superuser access was required when creating new namespaces. User namespaces do not have this requirement. In fact, they are also special in that they can own other namespaces.\nWhenever a non-user namespace N is created, Linux designates the current user namespace P of the process creating N, the owner of namespace N. If P is created alongside other namespaces in the same clone system call, Linux guarantees that P will be created first and designated the owner of the other namespaces. Owner namespaces are important because a process requesting to carry out a privileged action on a resource encapsulated by a non-user namespace will have its UID privileges checked against the owner user namespace and not the root user namespace. For example, say P is the parent user namespace of child C and P and C own network namespaces M and N respectively, a process might not have privilege to create network devices encapsulated by M but might be able to do so for N. The implication of owner namespaces for us is that we can drop the sudo requirement when running commands with unshare or isolate as long as we request a user namespace to be created as well - e.g. unshare -u bash will require sudo but unshare -Uu bash will not:\n# UID 1000 is a non-privileged user in the root user namespace P. P$ id uid=1000(iffy) gid=1000(iffy) # And as a result cannot create a network device in the root # network namespace. P$ ip link add type veth RTNETLINK answers: Operation not permitted # Let\u0026#39;s try our luck again, this time from a # different user and network namespace P$ unshare -nU bash # NOTE: no sudo C$ ip link add type veth RTNETLINK answers: Operation not permitted # Hmm still no dice. This makes sense since only # UID 0 (root) is allowed to create network devices and # currently we\u0026#39;re nobody. Let\u0026#39;s fix that. C$ echo $$ 13294 # Back in P, we map UID 1000 in P to UID 0 in C P$ echo \u0026#34;0 1000 1\u0026#34; \u0026gt; /proc/13294/uid_map # Who are we now? C$ id uid=0(root) gid=65534(nogroup) C$ ip link add type veth # Success! Unfortunately, we will re-enforce the superuser requirement in the next post since isolate needs root privileges in the root namespace in order to set up the Mount and Network namespaces properly. But we will make sure to drop the privileges before executing the command process to ensure that the command doesn\u0026rsquo;t have unecessary permissions.\nHow IDs are resolved We just saw a process running as a regular user 1000 suddenly switch to root 😮. Don\u0026rsquo;t worry, there wasn\u0026rsquo;t any privilege escalation involved. Remember that this is just a mapping of IDs - while our process thinks it is root on the system, Linux knows that root in its case means the regular UID 1000 (thanks to our mapping) so while namespaces owned by its new user namespace (like the network namespace in C) respects its authoritah as root, others (like the network namespace in P) do not so the process cannot do anything that user 1000 could not. Whenever a process in a nested user namespace carries out an operation that requires a permission check, e.g creating a file, it\u0026rsquo;s UID in that namespace is looked up against the equivalent user ID in the root user namespace by traversing mappings in the namespace tree up to the root. The opposite direction is traversed when it e.g reads user IDs like we would with ls -l my_file - the UID of my_file\u0026rsquo;s owner is mapped from the root user namespace down to the current namespace and the final mapped ID (or nobody if a mapping was missing somewhere along the tree) is presented to the reading process.\nGroup IDs Even though we have ended up as root in C we\u0026rsquo;re still associated with the dreaded nogroup as our group ID. We simply need to do the same for the corresponding /proc/$pid/gid_map. Before we can do that, we need to disable the setgroups system call (this shouldn\u0026rsquo;t be neccessary if your user already has the CAP_SETGID capability in P but we won\u0026rsquo;t assume this since this usually comes with superuser privileges) by writing \u0026ldquo;deny\u0026rdquo; to the proc/$pid/setgroups file:\n# Where 13294 is the pid for the unshared process C$ id uid=0(root) gid=65534(nogroup) P$ echo deny \u0026gt; /proc/13294/setgroups P$ echo \u0026#34;0 1000 1\u0026#34; \u0026gt; /proc/13294/gid_map # Our group ID mapping is reflected C$ id uid=0(root) gid=0(root) Implementation The source code for this post can be found here.\nAs you can see, there are a lot of intricacies involved in managing user namespaces but implementation is quite straightforward. All we need to do is write a bunch of rows to a file - the chore was in knowing what and where to write. Without further ado, here are our goals to accomplish:\nClone the command process in its own user namespace. Write to the UID and GID map files of the command process. Drop any superuser privileges before executing the command. 1 is accomplished by simply adding the CLONE_NEWUSER flag to our clone system call.\nint clone_flags = SIGCHLD | CLONE_NEWUTS | CLONE_NEWUSER; For 2, we add a function prepare_user_ns that conservatively exposes a single regular user 1000 as root.\nstatic void prepare_userns(int pid) { char path[100]; char line[100]; int uid = 1000; sprintf(path, \u0026#34;/proc/%d/uid_map\u0026#34;, pid); sprintf(line, \u0026#34;0 %d 1\\n\u0026#34;, uid); write_file(path, line); sprintf(path, \u0026#34;/proc/%d/setgroups\u0026#34;, pid); sprintf(line, \u0026#34;deny\u0026#34;); write_file(path, line); sprintf(path, \u0026#34;/proc/%d/gid_map\u0026#34;, pid); sprintf(line, \u0026#34;0 %d 1\\n\u0026#34;, uid); write_file(path, line); } And call it from within the main process in the parent user namespace, right before we signal to the command process.\n... // Get the writable end of the pipe. int pipe = params.fd[1]; // Some namespace setup will take place here ... prepare_userns(cmd_pid); // Signal to the command process we\u0026#39;re done with setup. ... For step 3 we update the cmd_exec function to ensure that the command runs as the regular, unprivileged user 1000 we provided in the mapping (remember the root user 0 within the command process\u0026rsquo; user namespace is user 1000):\n... // Wait for \u0026#39;setup done\u0026#39; signal from the main process. await_setup(params-\u0026gt;fd[0]); if (setgid(0) == -1) die(\u0026#34;Failed to setgid: %m\\n\u0026#34;); if (setuid(0) == -1) die(\u0026#34;Failed to setuid: %m\\n\u0026#34;); ... And that\u0026rsquo;s it! isolate now runs a process in an isolated user namespace.\n$ ./isolate sh ===========sh============ $ id uid=0(root) gid=0(root) This post went into quite a lot of detail about how User namespaces work but in the end, setting up an instance was relatively painless. In the next post, we will be looking at having isolate run the command in its own Mount namespace (uncovering the mystery behind the Dockerfile FROM instruction). There, we will be required to give Linux a bit more help in order to set up an instance properly.\n","permalink":"https://ifeanyi.co/posts/linux-namespaces-part-2/","summary":"\u003cp\u003eIn the \u003ca href=\"/posts/linux-namespaces-part-1/\"\u003eprevious post\u003c/a\u003e we dipped our toes into namespace waters and in the process, saw how simple it was to to get a process up and running with an isolated \u003ccode\u003eUTS\u003c/code\u003e namespace.\nIn this post, we shine a spotlight on the \u003ccode\u003eUser\u003c/code\u003e namespace.\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://man7.org/linux/man-pages/man7/user_namespaces.7.html\" target=\"_blank\" rel=\"noopener\"\u003eUser namespaces\u003c/a\u003e isolate, among other security related resources, user and group identities in the system.\nWe will focus solely on the user and group ID (\u003ccode\u003eUID\u003c/code\u003e and \u003ccode\u003eGID\u003c/code\u003e respectively) resources in this post as they are fundamental role to carrying out permissions checks and other security related activities throughout the system.\u003c/p\u003e","title":"A deep dive into Linux namespaces, part 2"},{"content":"In this series of posts we will look closely at one of the main ingredients in a container - Namespaces. In the process, we will create a simpler clone of the docker run command - our very own program that will take as input a command (along with it\u0026rsquo;s arguments if any) and spin up a container process to run it, isolated from the rest of the system similar to how you would docker run it from an image.\nWhat is a namespace? A Linux namespace is an abstraction over resources in the operating system. We can think of a namespace as a box. Inside this box are these system resources, which ones exactly depend on the box\u0026rsquo;s (namespace\u0026rsquo;s) type. There are currently 7 types of namespaces Cgroup, IPC, Network, Mount, PID, User, UTS.\nFor instance, the Network namespace encapsulates system resources related to networking such as network interfaces (e.g wlan0, eth0), route tables etc, the Mount namespace encapsulates files and directories in the system, PID contains process IDs and so on. So two instances of a Network namespace A and B (corresponding to two boxes of the same type in our analogy) can contain different resources - maybe A contains wlan0 while B contains eth0 and a different route table copy.\nNamespaces aren\u0026rsquo;t some addon feature or library that you need to apt install, they are provided by the Linux kernel itself and already are a prerequisite to run any process on the system. At any given moment, any process P belongs to exactly one instance of each namespace type - so when it needs to say, update the route table on the system, Linux shows it the copy of the route table of the namespace to which it belongs at that moment.\nWhat is it good for? Absolutely nothi\u0026hellip; just kidding. One good thing with boxes is that you can add and remove stuff from one box and it will not affect the content of other boxes. That\u0026rsquo;s the same idea here with namespaces - a process P can go crazy and sudo rm -rf / but another process Q that belongs to a different Mount namespace will be unaffected since they\u0026rsquo;re using distinct copies of those files.\nNote though that a resource encapsulated within a namespace doesn\u0026rsquo;t necessarily mean that it\u0026rsquo;s a unique copy. In a number of cases, either by design or as a security hole, two or more namespaces will contain the same copy, e.g of the same file, so that changes made to that file in one Mount namespace will in fact be visible in all other Mount namespaces that also reference it. For this reason, we will retire our box analogy here since an item cannot simultaneously exist in two distinct boxes 😞.\nUnsharing is caring We can see the namespaces that a process belongs to! In typical Linux fashion, they\u0026rsquo;re exposed as files under the directory /proc/$pid/ns for a given process with process id $pid:\n$ ls -l /proc/$$/ns total 0 lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 cgroup -\u0026gt; cgroup:[4026531835] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 ipc -\u0026gt; ipc:[4026531839] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 mnt -\u0026gt; mnt:[4026531840] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 net -\u0026gt; net:[4026531957] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 pid -\u0026gt; pid:[4026531836] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 user -\u0026gt; user:[4026531837] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 uts -\u0026gt; uts:[4026531838] You can open a second terminal and run the same command and it should give you the exact same output - this is because, as we mentioned earlier, a process must belong to some namespace and unless we explicitly specify which ones, Linux adds it as a member to the default namespaces. Let\u0026rsquo;s meddle in this a bit. In the second terminal we can run something like:\n$ hostname iffy $ sudo unshare -u bash $ ls -l /proc/$$/ns lrwxrwxrwx 1 root root 0 May 18 13:04 cgroup -\u0026gt; cgroup:[4026531835] lrwxrwxrwx 1 root root 0 May 18 13:04 ipc -\u0026gt; ipc:[4026531839] lrwxrwxrwx 1 root root 0 May 18 13:04 mnt -\u0026gt; mnt:[4026531840] lrwxrwxrwx 1 root root 0 May 18 13:04 net -\u0026gt; net:[4026531957] lrwxrwxrwx 1 root root 0 May 18 13:04 pid -\u0026gt; pid:[4026531836] lrwxrwxrwx 1 root root 0 May 18 13:04 user -\u0026gt; user:[4026531837] lrwxrwxrwx 1 root root 0 May 18 13:04 uts -\u0026gt; uts:[4026532474] $ hostname iffy $ hostname coke $ hostname coke The unshare command runs a program (optionally) in a new namespace. The -u flag tells it to run bash in a new UTS namespace. Notice how our new bash process points to a different uts file while all others remain the same.\nCreating new namespaces usually requires superuser access. From now on, we will assume that both unshare or our implementation are run with sudo.\nOne implication of what we just did is that we can now change the system\u0026rsquo;s hostname from within our new bash process and it won\u0026rsquo;t affect any other process in the system. You can verify this by running hostname in the first shell or a new one and seeing that the hostname hasn\u0026rsquo;t changed there.\nBut like, what is a container though? Hopefully, now you have some idea of what a namespace can do. You might guess that containers are fundamentally ordinary processes with different namespaces from other processes and you\u0026rsquo;d be correct. In fact a quote, unquote container doesn\u0026rsquo;t have to belong to a unique namespace for each type - it can share some of them.\nFor instance, when you docker run --net=host redis, all you do is tell docker to not create a new Network namespace for the redis process, and as we saw, Linux will add that process as a member of the default Network namespace just like every other regular process. So the redis process is exactly like everyone else from a networking perspective. Networking isn\u0026rsquo;t special here,docker run let\u0026rsquo;s you do this customization for most namespaces. This begs the question of what even is a container? Is a process that shares all but one namespace still a container? ¯\\_(ツ)_/¯ Usually containers come with the notion of isolation, achieved through namespaces - the smaller the number of namespaces and resources that a process shares, the more isolated the process is and that\u0026rsquo;s all that really matters.\nIsolate In the remainder of this post, we will lay the ground work for our program that we will call isolate. isolate takes a command as arguments and runs that command in a new process isolated from the rest of the system and within its very own namespaces. In the coming posts, we will look at adding support for individual namespaces when isolate spins up the command process.\nIn terms of scope, we will focus on the User, Mount, PID and Network namespaces. The rest are relatively trivial to implement once we\u0026rsquo;re done (in fact, we add UTS support in the initial implementation here) and Cgroup for example is only interesting from a perspective that is out of scope of this series (studying cgroups - the other ingredient in containers that is used to control how much of a resource a process is allowed to use).\nNamespaces can get complex real quick so there are lots of different paths we can take while studying each namespace but we can\u0026rsquo;t take them all. We will only discuss the paths that are relevant to the program that we\u0026rsquo;re building. Each post will start off with some experiments on the namespace in question within a terminal in an attempt to understand the interactions involved in setting up that namespace. After this we will already have an idea of what we want to accomplish and will then follow up with a corresponding implementation in isolate.\nTo avoid bombarding the posts with code, we will not include things like helper functions that are not necessary to understand the implementation. You can find the full source code here on Github.\nImplementation The source code for this post can be found here.\nOur isolate implementation will initially be a simple program that reads a command path from stdin and clones a new process that executes the command with the specified arguments. The cloned command process will run in its own UTS namespace just like we did with unshare earlier. In later posts, we will see that namespaces do not necessarily work (or even provide isolation) out of the box and we will need to do some setup after creating them (but before executing the actual command) in order for the command to truly run in isolation.\nThis namespace creation-setup combo will require some co-operation between the main isolate process and the child command process. As a result, part of the ground work here will be to setup a communication channel between both processes - we will use a Linux pipe due to its simplicity given our use case.\nWe have three things to do:\nCreate the main isolate process that reads from stdin. Clone a new process that will run the command in a new UTS namespace. Set up a pipe so that the command process begins the command execution only after it receives a signal from the main process that the namespace setup is done. Here is the main process:\nint main(int argc, char **argv) { struct params params; memset(\u0026amp;params, 0, sizeof(struct params)); parse_args(argc, argv, \u0026amp;params); // Create pipe to communicate between main and command process. if (pipe(params.fd) \u0026lt; 0) die(\u0026#34;Failed to create pipe: %m\u0026#34;); // Clone command process. int clone_flags = SIGCHLD | CLONE_NEWUTS ; int cmd_pid = clone(cmd_exec, cmd_stack + STACKSIZE, clone_flags, \u0026amp;params); if (cmd_pid \u0026lt; 0) die(\u0026#34;Failed to clone: %m\\n\u0026#34;); // Get the writable end of the pipe. int pipe = params.fd[1]; // Some namespace setup will take place here ... // Signal to the command process we\u0026#39;re done with setup. if (write(pipe, \u0026#34;OK\u0026#34;, 2) != 2) die(\u0026#34;Failed to write to pipe: %m\u0026#34;); if (close(pipe)) die(\u0026#34;Failed to close pipe: %m\u0026#34;); if (waitpid(cmd_pid, NULL, 0) == -1) die(\u0026#34;Failed to wait pid %d: %m\\n\u0026#34;, cmd_pid); return 0; } Check out clone_flags that we pass to our clone invocation, See how dead simple it is to create a new process in its own namespace? All we have to do is set the flag for the namespace type (the CLONE_NEWUTS flag corresponds to the UTS namespace) and Linux takes care of the rest. Next, the command process waits for a signal before exec-ing the command:\nstatic int cmd_exec(void *arg) { // Kill the cmd process if the isolate process dies. if (prctl(PR_SET_PDEATHSIG, SIGKILL)) die(\u0026#34;cannot PR_SET_PDEATHSIG for child process: %m\\n\u0026#34;); struct params *params = (struct params*) arg; // Wait for \u0026#39;setup done\u0026#39; signal from the main process. await_setup(params-\u0026gt;fd[0]); char **argv = params-\u0026gt;argv; char *cmd = argv[0]; printf(\u0026#34;===========%s============\\n\u0026#34;, cmd); if (execvp(cmd, argv) == -1) die(\u0026#34;Failed to exec %s: %m\\n\u0026#34;, cmd); die(\u0026#34;¯\\\\_(ツ)_/¯\u0026#34;); return 1; } Finally we can try it out:\n$ ./isolate sh ===========sh============ $ ls isolate isolate.c isolate.o Makefile $ hostname iffy $ hostname coke $ hostname coke # Verify in a new terminal that hostname hasn\u0026#39;t been updated Currently, isolate is a little bit more than a program that just forks off a command (we do have the UTS thing going for us). In the next post, we take it a step further by looking at User namespaces and have isolate run the command in its own User namespace. There, we will see that we actually need to do some work in order to have a usable namespace in which the command can run.\n","permalink":"https://ifeanyi.co/posts/linux-namespaces-part-1/","summary":"\u003cp\u003eIn this series of posts we will look closely at one of the main ingredients in a \u003ca href=\"https://www.docker.com/resources/what-container\" target=\"_blank\" rel=\"noopener\"\u003econtainer\u003c/a\u003e - Namespaces.\nIn the process, we will create a simpler clone of the \u003ccode\u003edocker run\u003c/code\u003e command - our very own program that will take as input a command (along with it\u0026rsquo;s arguments if any) and spin up a \u003cem\u003econtainer\u003c/em\u003e process to run it, isolated from the rest of the system similar to how you would \u003ccode\u003edocker run\u003c/code\u003e it \u003ca href=\"https://docs.docker.com/engine/reference/run/\" target=\"_blank\" rel=\"noopener\"\u003efrom an image\u003c/a\u003e.\u003c/p\u003e","title":"A deep dive into Linux namespaces"},{"content":"Computers can sometimes be unexpectedly tricky. Having them accomplish seemingly rudimentary tasks can be a lot more complicated than we would expect. One example is having a group of them decide and come to an agreement upon something: a process known as consensus. That something could be anything. It could be what time of the day they should begin their attack to take over the world. Or simply the amount in your bank account at some given moment: Without agreement that amount may be $10 according to your work pc while your laptop at home says $100. Let\u0026rsquo;s elaborate on the former scenario as it\u0026rsquo;s more likely to happen.\nSay we have three machines that we call X, Y and Z plotting to conquer the world on a Monday. They have an plan of attack that only works if all available computers show up to battle and no one stays behind. A number of things can go wrong while they\u0026rsquo;re trying to come to an agreement on what time to begin their attack.\nOne fundamental problem arises because everyone has an opinion. Computer X maybe suggests 08:00 for the takeover since it\u0026rsquo;s monday morning and the humans are still hungover from the weekend while some other computer Z thinks 13:00 is better because, well, Z\u0026rsquo;s not really a morning computer. Having them agree on a time is crucial as the humans have a better chance if all computers don\u0026rsquo;t show up.\nAnother issue is that these computers are located in different parts of the world, communicating via cables and whatnot. If X suggests 08:00, it has to be sure that the suggestion reaches Y and Z as long as they are alive so as not to end up alone on the battlefront.\nWe can\u0026rsquo;t accurately tell if a computer is being delayed or unavailable.\nHow does X even know which others are available? Say it sends a message to Y and doesn\u0026rsquo;t get a response on time. Any of the following causes is possible:\nY is busy with other things at the moment and hasn\u0026rsquo;t had a chance to reply the message. The message from X (or Y\u0026rsquo;s response) was lost; the post office loses our messages every now and then, computers are no different!. The message from X (or Y\u0026rsquo;s response) is being delayed but still is on its way. Y is in fact unavailable; crashed at some point in time. Let\u0026rsquo;s ignore number 2 and assume that any message sent will eventually be delivered. We assume that the cables are industrial strength and messages don\u0026rsquo;t disappear magically on the way. However, these cables could delay a message passing through them for any reason and for as long as they want.\nYou may now notice that X can\u0026rsquo;t simply declare Y to be unavailable and subsequently agree on a time only with Z. But it does have to make a decision ASAP because the humans will have their morning coffee very soon and be on the alert. Either go to battle with only Z or call off the attack altogether. Either alternative is fine as long as X doesn\u0026rsquo;t make the wrong decision. Here are what qualify as wrong decisions:\nIf it turns out that Y hasn\u0026rsquo;t crashed (number 1 or 3) but X agrees with Z on a time to attack, then the humans will win since only 2 out of 3 available machines show up to battle. If Y is unavailable but X chooses to play it safe and call off the attack, then they miss a perfect opportunity to attack the humans at their most vulnerable time of the week. It\u0026rsquo;s quite a tough decision to get right: basically everyone has to decide whether to cut the red or blue wire every time they send a message.\nNote that X is not the only machine with a potentially faulty communication (unreliable) link. Every machine is. For example, when Z gets a suggestion from X and replies with a confirmation (or a counter suggestion). It could also enter the same state of paranoia where it can\u0026rsquo;t be sure if its message is delayed or if X has crashed during this time.\nHow long should X even wait before it makes a decision? When has enough time passed before it\u0026rsquo;s okay to start suspecting Y? The moral of this story is that we can never be one hundred percent sure of an answer.\nWell, if these machines can\u0026rsquo;t even reliably tell whether or not a single message sent has been delivered, how can they come to a definitive agreement about anything?\nThe good news (good for them, not for us humans) is that they could use an algorithm called Paxos that doesn\u0026rsquo;t require them to make timing assumptions on how long it takes to send messages or communicate between each other. This means that they won\u0026rsquo;t have to make the dreaded red-or-blue decision whenever they send a message. The bad news however (bad for them, good for us humans), is that this algorithm only works if there are a certain number (at least a majority) of computers available at any given moment in the process. Should some machine crash in the middle of the process and there aren\u0026rsquo;t enough computers left alive, then no one makes any decisions: which is acceptable because there\u0026rsquo;s no chance of making a wrong decision.\nGiven an unreliable communication link, we don\u0026rsquo;t want to make assumptions on how long it takes for anything to happen since it opens up a can of worms. This is where Paxos becomes of help. We\u0026rsquo;ll try to conceptualize the Paxos protocol by placing ourselves in the shoes of these potentially diabolical computers to see how we may go about solving this problem.\nA distributed auction A typical setting where a group of people need to come to an agreement is an auction. Here, we want to agree on a winner of an item up for bid and unsurprisingly, everyone has an opinion. We all think that our choice should be the winner but money talks loudest so the highest bid wins.\nThis gives us a problem of multiple opinions, similar to that of the computers. But a traditional auction has all bidders in the same room, bidding openly against each other. If for some reason a bidder should pull out, then every other bidder will instantly be aware. Unlike the computers, communication between bidders in an auction house is reliable. Let us put some constraints on our traditional auction process in order to simulate an unreliable communication link.\nInstead of having the action take place inside a single auction house, bidders stay at home and send their bids to the auctioneer via the post office (or carrier pigeons). Bidders don\u0026rsquo;t know how much the others have bid so they simply wait patiently for the mail man to return with a response letter telling if they won or not. If it turns out there is a higher bid than theirs, that bid is included in the rejection letter and they have an opportunity to up their bid and resend. Post offices have a knack for delaying messages so bids and responses from the auctioneers may be delayed for however long.\nNow we have the same problem of participants being distributed and communication channels being unreliable so it\u0026rsquo;s no surprise that our auction process doesn\u0026rsquo;t work. The auctioneer will never know when (s)he\u0026rsquo;s received the last (and possibly highest) bid.\nTake 1: Auction! To see why our auction doesn\u0026rsquo;t work, we set up a simple auction scenario with one auctioneer Cindy and three bidders Alice, Bob and Mark. Bidders bid on behalf of candidates (the person who actually pays for the item). They all have to decide and agree upon which candidate wins the auction item. We\u0026rsquo;ll have 3 eligible candidates in our scenario: They\u0026rsquo;ve all requested to be anonymous so we refer to them as Mr X, Mr Y and Mr Z. Our bidders are allowed to choose whichever candidate to nominate as the winner. This makes our auction process a bit unorthodox but as we\u0026rsquo;ll see, it ensures that they all eventually agree on the winning candidate.\nAssume that Cindy has just received a bid from Mark for $5 and this is the highest bid she has seen so far. She couldn\u0026rsquo;t simply send a reply to Mark declaring him (his proposed candidate to be exact) the winner since there might be a higher bid on the way from Alice or Bob. Cindy is faced with the same problem the machines had. She could choose to send a reply to mark saying he won and risk losing out on the highest bid possible or she could wait (potentially forever) to see if there is a higher bid on the way, and the auction possibly never ends.\nA traditional auction has everyone in the same room so the auctioneer gets to make timing assumptions. Whenever a higher bid has been proposed, bidders have a time window in which they can counter with a higher bid (the period during which the auctioneer yells out crazy). If the time elapses without any counter bid, then the highest bidder\u0026rsquo;s candidate wins. This works because everyone in the room uses the same clock to count time. In our distributed auction, everyones\u0026rsquo; clock at home could be telling different times and ticking at different frequencies so they are not as useful to us.\nOn a positive note, our auction doesn\u0026rsquo;t work because of the same problems the computers had while trying to come to an agreement. So how again we can use the Paxos algorithm to get come to an agreement on who wins the auction? Jamie the auction designer is curious enough to head right to the source for answers.\nPaxos: Ah there\u0026rsquo;s a problem, You have only one auctioneer. Rookie mistake: You shouldn\u0026rsquo;t have just one auctioneer.\nJamie: Um\u0026hellip;okay. how many then?\nPaxos: I don\u0026rsquo;t know. how ever many you want. Just not one. I mean, what happen if that one person dies in the middle of an auction right? then everyone\u0026rsquo;s screwed.\nJamie: Alright, that makes sense but can we use some other word instead of dies. You know, like fail?\nPaxos: Fail? never heard that word before.\nJamie: Well if something fails then it simply stops working and never does anything more. Ever.\nPaxos: Whatever. All I\u0026rsquo;m trying to say is that if you have a certain number of auctioneers that are guaranteed not to di\u0026hellip; uhm, I mean fail, then your bidders don\u0026rsquo;t have to wonder whether or not their message was received and they\u0026rsquo;ll be guaranteed to eventually get a response back.\nOkay Paxos makes a valid point so let\u0026rsquo;s add two more auctioneers to help Cindy out. Jane and Eve. We inform this guarantee to our bidders: at any point in time, at least two (majority of 3) auctioneers will be available. We also let them know that their bids have to be sent individually to all three auctioneers.\nCool. But wait a minute. These auctioneers are also not able to communicate with each other since everyone\u0026rsquo;s at their own home. Our tweaks may have solved the paranoia experienced by bidders but not for the auctioneers. How do they decide when it\u0026rsquo;s time to proclaim a winner? We\u0026rsquo;ll answer this question in a moment. First let\u0026rsquo;s lay out some fundamental properties dictating what it means for our auction process to be correct.\nCorrectness properties Validity\nThe winning bid must have been proposed at some point in the process. The item up for bid can\u0026rsquo;t be sold to Mr T if none of our bidders actually proposed Mr T as a candidate (this pretty much says no sneaky business is allowed). Uniform Agreement\nThere can only be one winning candidate. If for any reason someone decides that Mr X is the winner, then no one else should decide that someone other than Mr X has won. Termination\nEvery active bidder eventually decides on who the winner is. No one is kept waiting forever to find out this information. We can use these properties to find potential pitfalls in our auction protocol. Already, we can only argue that number 1 is satisfied.\nJamie: How does an auctioneer know when she has received all bids and no other bids are on the way? Like, when to declare a winner?\nPaxos: The auctioneer doesn\u0026rsquo;t have to declare a winner. Maybe the bidders who think they\u0026rsquo;ve won can declare their candidates the winner.\nJamie: Huh? wouldn\u0026rsquo;t that be sneaky?\nPaxos: Sneaky-clever. Let auctioneers remain passive. They don\u0026rsquo;t communicate with each other and we already know that they can\u0026rsquo;t decide when all bids have been received without potentially waiting forever. So why not let the active bidders make that decision when they think that they\u0026rsquo;ve won.\nJamie: So this means it all depends on bidders being one hundred percent honest with each other? that doesn\u0026rsquo;t seem very lik\u0026hellip;\nPaxos: Well, you\u0026rsquo;re the one applying the algorithm on humans, I suggested it was for machines. They may plot to take over the world but at least machines are one hundred percent honest with each other.\nWe\u0026rsquo;ll now see how Paxos implements this idea by applying it incrementally to our auction process.\nTake 2: Auction! bidders make a decision once they\u0026rsquo;ve received a response from a majority of the auctioneers.\nThis time, auctioneers have an easier decision to make whenever they receive a new bid. If the new bid is the highest they\u0026rsquo;ve seen so far, then reply with an acknowledgement response. Otherwise, reply the bidder with a rejection response. Once a bidder gets a bid accepted by a majority of auctioneers (called a chosen bid), (s)he decides that the candidate proposed in that bid is the winner of the auction. Here\u0026rsquo;s a sample execution of an auction process from the auctioneers\u0026rsquo; point of view. Cindy receives a bid from Bob for $50 and accepts it, since it\u0026rsquo;s the highest bid she\u0026rsquo;s received so far.\nkey: proposal format =\u003e bidder(amount, candidate):response This almost works. Bob gets a majority of auctioneers to accept his bid and decides that his candidate Mr Y is the winner. But the same goes for Mark and his candidate Mr Z. The result is an awkward moment between Mr Y and Mr Z who both show up to collect the same precious antique.\nThis happens because Cindy and Eve accept bids independent of each other. Eve (as well as Jane) saw Mark\u0026rsquo;s bid before Bob\u0026rsquo;s so she did what she was supposed to and accepted both. To fix this problem, we have to make sure that our bidders don\u0026rsquo;t blindly propose their bid because the auctioneers don\u0026rsquo;t communicate with each other: Bidders need to get an idea about what bids have already been accepted before proposing theirs.\nTake 3: Auction! The process of bidding becomes a two phase act. A prepare phase and an accept phase.\nPhase 1 - The prepare phase\nAs a bidder, whenever I decide to make a bid, I send only the amount that I\u0026rsquo;m willing to bid. This is called a prepare request. I don\u0026rsquo;t reveal my candidate just yet: Heck! I may not even have decided on which candidate to nominate at that point. I want to do this for 2 reasons.\nExtract a promise from the auctioneers that no lower bid will be accepted in the future. This is somewhat straightforward. Basically, I\u0026rsquo;m giving the auctioneers a sneak preview about my bid. I disclose the amount that I\u0026rsquo;m willing to bid, say $50, so they get a heads up that most likely a bid of $50 for some candidate will arrive in the future. For example, say auctioneer Cindy receives my prepare request for $50 and responds. She now has an idea that sometime in the future, a bid for $50 will arrive. During this time however, she may receive a bid from Bob for $40. But she must reject Bob\u0026rsquo;s bid, hoping that my higher bid shows up later on. For this reason, I\u0026rsquo;ll interpret Cindy\u0026rsquo;s response to my prepare request as an implicit promise that she will reject any future bids that are lower than mine. After all, that is one essence of an auction.\nFind out if a lower bid has already been accepted.\nAuctioneers are required to accept bids as long as it\u0026rsquo;s the highest they\u0026rsquo;ve seen so far (including prepared amounts). They couldn\u0026rsquo;t be bothered about potentially conflicting candidates associated with the newer bids: Money talks so the higher bid always gets accepted. Before sending a proposal, a bidder must find out if someone else previously sent in a bid or maybe even decided on a winner. If such a person exists we want to know the details of that bid.\nFor any prepare request with amount n, An auctioneer replies with her highest accepted bid having an amount less than n. This bid could be an empty bid if she hasn\u0026rsquo;t accepted any bids with amount lower than n.\nIf theres a bid with a better chance at being chosen before yours then adopt it\u0026rsquo;s candidate, otherwise simply propose any candidate of choice.\nOnce I receive responses from a majority of auctioneers, I go through all the bids received in these responses. If they\u0026rsquo;re all empty bids, I safely proceed to phase 2 where I propose any candidate of my choice without the fear of overwriting someone else\u0026rsquo; candidate. Otherwise, I select the bid with the highest amount out of the responses and try to compete with this bid.\nAssume I select Alice\u0026rsquo;s bid to compete with in this phase. Her bid has a smaller amount than mine for sure but still her candidate is more likely to be chosen before mine since it has already been accepted by some auctioneer(s) and I haven\u0026rsquo;t even proposed mine yet. I could be stubborn and propose a different candidate but then there\u0026rsquo;s a chance that both Alice\u0026rsquo;s and my candidate get chosen: meaning that we\u0026rsquo;re back to our original problem of two different winning candidates. I don\u0026rsquo;t know for sure that Alice\u0026rsquo;s candidate is going to win but I do have to nominate somebody now. So I\u0026rsquo;ll play it safe and adopt her candidate (say, Mr X) as my candidate.\nAuctioneers are passive so they don\u0026rsquo;t do much in phase 1. They simply wait for a bidder\u0026rsquo;s prepare request and reply with the maximum bid lower than the bidder\u0026rsquo;s amount. Phase 2 - Accept phase\nIt\u0026rsquo;s time to actually propose my bid and find out if it gets chosen.\nI simply Send my bid to all auctioneers and hope for the best. Similar to phase 1, I wait until I get responses from a majority of auctioneers. It doesn\u0026rsquo;t even have to be the same auctioneers from phase 1 that reply this time around. Any set of a majority would do. Here\u0026rsquo;s a possible execution from the auctioneers perspective.\nkey: prepare request format =\u003e bidder.prep(amount) Mark has finished his prepare phase for $50 fair and square but sometime before completing his accept phase, Alice tries her own prepare phase and succeeds in getting a promise from a majority of auctioneers not to accept any lower bid than her $100. This pretty much spoils the show for mark in phase 2. He can never get his bid accepted by a majority of auctioneers: Infact, he gets two rejections in this case (represented by the X marker).\nIf any response from an auctioneer contains a rejection, I must quit and restart the entire process from phase 1 with a higher amount. The game can be cruel.\nIf I don\u0026rsquo;t get any rejections among the majority responses I receive then I can be sure that my bid is a chosen bid and my proposed candidate is the winner.\nAgain, having passive auctioneers means that the job is very simple. Whenever an auctioneer receives a bid, she accepts it only if she hasn\u0026rsquo;t promised some other bidder not to during their prepare phase. i.e If she hasn\u0026rsquo;t received a prepare request for an amount greater than the proposed bid. Otherwise the bid must be rejected.\nAnd that\u0026rsquo;s it! We now have a working distributed auction that makes no timing assumptions and guarantees that no two candidates can win a single round.\nAgreement: Why it works Note that it is possible for several bids to win an auction round. In fact, every active bidder must get a bid chosen in order to decide which candidate won the round. The goal is not agreement on which bidder/bid wins the round (they all do at some point), rather which candidate wins.\nThis brings us to reasoning about how the agreement property is satisfied.\nRemember that a bidder during the prepare phase adopts a candidate if it suspects that someone else already started proposing. This is crucial in guaranteeing the agreement property of the paxos algorithm and our auction. Here are some important points to understanding why this auction process and paxos in general works.\nLets assume that Mark\u0026rsquo;s bid of $200 and candidate Mr Z has been chosen. Is it safe for Mark to decide that Mr Z has infact won the auction item? The algorithm says yes but we try to find out for ourselves.\nThe only way for Mark to be sure is if any other bids that become chosen agree with his choice of candidate Mr Z. This will happen for the following reasons:\nEvery higher bid subsequently proposed by another bidder must have Mr Z as its candidate because the bidder must have adopted Mr Z in the prepare phase.\nHere the majority factor comes into play: for any permutation of a set of majority auctioneers, at least one of them must have at some point accepted Mark\u0026rsquo;s bid and will reply with the bid containing Mr Z as candidate. No number less than a majority can guarantee this outcome.\nEvery subsequent higher bid that becomes accepted must have Mr Z as a candidate: this simply makes sense from 1 because an accepted bid must have been proposed earlier.\nEvery subsequent higher bid that becomes chosen must also have Mr Z as candidate: this in turn follows from 2. A bid can not be chosen without being accepted.\nWe can see why Mark could start celebrating as soon as his bid gets chosen. Having a majority auctioneers accept his bid guarantees that his bid will show up later on when someone else tries to validate a bidding amount in their prepare phase. They must adopt Mark\u0026rsquo;s candidate and whenever they do decide that their own bid has been chosen, it will contain Mr Z as candidate.\nTermination: Why it doesn\u0026rsquo;t (always) work. Here\u0026rsquo;s a quick rundown of the auction process from my perspective as a bidder. I send a prepare request with my amount. When I receive a response from a majority regarding my prepare request, I propose my bid (after possibly adopting a candidate) and hope it gets accepted and subsequently chosen. If I get a rejection then I restart the entire process with a higher bid.\nHowever, consider this scenario.\nEveryone\u0026rsquo;s playing by the rules so this is a perfectly legal scenario. Mark prepares his amount for $10 but his bid gets rejected because sometime in between, some auctioneers promise Alice not to accept a lower bid than hers: we\u0026rsquo;ve seen this before. The next step for Mark is to restart by preparing a higher bid, which he does right in between Alice\u0026rsquo;s prepare and accept phases. For the same reason, Alice also gets a rejection from the auctioneers when she finally proposes her bid so she does her own quick turn around with a higher prepare amount and so on\u0026hellip; Obviously this can go on forever without any bid being accepted. One positive takeaway is that even though the system is going haywire, no one actually makes any decisions much less wrongly declare a winner.\nFLP Ghost This isn\u0026rsquo;t a fault with our auction process itself. Its a phenomenon inherited from the Paxos algorithm. Since we do not make any timing assumptions like how long messages take to be delivered or how fast auctioneers respond to messages, we assume an asynchronous setting. It is infact impossible to implement an algorithm that solves distributed consensus in an asynchronous system if there is a possiblity that even one machine might fail, and the paxos algorithm is as close as it gets. So this doesn\u0026rsquo;t mean that the algorithm is useless. There are several ways to mitigate this phenomenon. A simple fix could have a bidder wait for a random amount of time if their bid gets rejected, before restarting, hopefully giving the competition enough time to complete their accept phase. Your favorite protocol for connecting to the internet uses a variant of this technique in the face of message collisions.\nThe Paxos Algorithm Now we know that the Paxos algorithm was designed to try to solve the problem of distributed consensus between a network of computers in an asynchronous system. What does the actual algorithm say? How does it get this group of machines to achieve consensus in the face of unpredictable failures?\nWe\u0026rsquo;ve already answered these questions (sorta) but we\u0026rsquo;ll do so here more formally by taking a sneak peek at the actual specification. Fortunately, we\u0026rsquo;ve pretty much covered the entire algorithm while constructing our auction protocol so there are very few surprises here.\nPaxos uses the concepts of Proposers, Acceptors and Learners as roles in which a machine can act.\nProposers are machines with opinions. They try to impose their opinion (value) to a set of acceptors. Analogous to our auction process, these proposers are simply bidders.\nAcceptors accept values proposed by proposers à la auctioneers.\nLearners decide on the agreed upon value based on the acceptors acceptances. We had our bidders decide the agreed upon value (the chosen bid) in our auction. The learners simply act as external agents that declare a value to be chosen.\nBidders were technically acting both as proposers and learners. Infact it is very common for a single machine to act in all three roles in a typical setting.\nThe mechanics again, are not far off from our auction implementation. Proposers propose their value (candidate) along with a proposal number (instead of money: machines are notorious for being penniless). The proposal numbers associated with a proposal must be globally unique regardless of the proposer. In our auction this would mean that once Mark has issued a prepare request for $50, no one else can issue that exact amount.\nPhase 1- Prepare Phase Proposer\nProposals look something like (n, v) where n is the proposal number and v is the proposed value. A proposer initially issues a prepare request prepare(n) to a set of acceptors in the hopes of extracting a promise from a majority of acceptors (a promise not to accept a proposal less than n).\nAcceptor\nIf an acceptor receives a prepare request with proposal number n, then it is only obliged to send a response if:\nthis prepare request is the first request received by that acceptor or n is the highest proposal number for a prepare request that acceptor has seen so far. In both cases, the acceptor replies with the highest numbered proposal that it has accepted so far (could be an empty proposal if none has been accepted). If neither case is true, the acceptor doesn\u0026rsquo;t have to send a response at all. Not even a rejection. Obviously this means less work for the acceptors but it makes it possible for a proposer to wait indefinitely for a promise. We implemented a more practical (and polite) variant of the algorithm in our auction where we always send a response back to the proposer (even if that proposer is doomed to having his proposal rejected later on). Phase 2 - Accept Phase Proposer If a majority of acceptors have replied with a promise for a proposer\u0026rsquo;s prepare request. That proposer adopts the value of the highest proposal (sorted by proposal number) from among the responses and issues an accept request (same as proposing a bid in our auction) or simply proposes its own value if all responses contain empty proposals. The accept request is sent to a set of acceptors.\nAcceptor If an acceptor receives an accept request for a proposal (n, v). It sends an accept response only if it hasn\u0026rsquo;t already promised some other proposer not to: i.e. if it has already responded to a prepare request prepare(m) such that m \u0026gt; n.\nAgain, the acceptor is not required to reply in any other case, potentially having the proposer on hold indefinitely. Our auction protocol uses a slightly more polite approach by sending a rejection response back to the proposer. Another option is to have the proposers timeout and restart the entire process if they don\u0026rsquo;t receive a response.\nLearner In the case that a proposer has had its bid accepted by a majority of acceptor, the learners have to be notified somehow. One way to do this is to have each acceptor notify the learners whenever they accept some proposal by sending details of the accepted proposal. Then the learners can simply count in real time how many times a proposal has been accepted. In scenarios where the same machine acts as both a Proposer and a Learner, this feature is already built it as we\u0026rsquo;ve seen: the proposer simply declares its proposal to be chosen immediately.\n","permalink":"https://ifeanyi.co/posts/understanding-consensus/","summary":"\u003cp\u003eComputers can sometimes be unexpectedly tricky. Having them accomplish seemingly rudimentary tasks can be a lot more complicated than we would expect. One example is having a group of them decide and come to an agreement upon something: a process known as \u003cem\u003econsensus\u003c/em\u003e. That \u003cem\u003esomething\u003c/em\u003e could be anything. It could be what time of the day they should begin their attack to take over the world. Or simply the amount in your bank account at some given moment: Without agreement that amount may be $10 according to your work pc while your laptop at home says $100. Let\u0026rsquo;s elaborate on the former scenario as it\u0026rsquo;s more likely to happen.\u003cbr\u003e\n\u003cbr\u003e\nSay we have three machines that we call \u003ccode\u003eX\u003c/code\u003e, \u003ccode\u003eY\u003c/code\u003e and \u003ccode\u003eZ\u003c/code\u003e plotting to conquer the world on a Monday. They have an plan of attack that only works if all available computers show up to battle and no one stays behind. A number of things can go wrong while they\u0026rsquo;re trying to come to an agreement on what time to begin their attack.\u003cbr\u003e\nOne fundamental problem arises because everyone has an opinion. Computer \u003ccode\u003eX\u003c/code\u003e maybe suggests 08:00 for the takeover since it\u0026rsquo;s monday morning and the humans are still hungover from the weekend while some other computer \u003ccode\u003eZ\u003c/code\u003e thinks 13:00 is better because, well, \u003ccode\u003eZ\u003c/code\u003e\u0026rsquo;s not really a morning computer. Having them agree on a time is crucial as the humans have a better chance if all computers don\u0026rsquo;t show up.\u003cbr\u003e\nAnother issue is that these computers are located in different parts of the world, communicating via cables and whatnot. If \u003ccode\u003eX\u003c/code\u003e suggests 08:00, it has to be sure that the suggestion reaches \u003ccode\u003eY\u003c/code\u003e and \u003ccode\u003eZ\u003c/code\u003e as long as they are alive so as not to end up alone on the battlefront.\u003c/p\u003e","title":"Understanding Consensus and Paxos in Distributed Systems"},{"content":"Hi! before introducing this post let\u0026rsquo;s do some myth busting! 😎\nPurely functional programming languages have no place in the browser and Haskell is a magical genius language that everyone talks about but no one really uses.\nTruth is, haskell is not a difficult language to program in and it\u0026rsquo;s past reputation of never being used in the industry has always been uncalled for. As for how we can use it to program applications for the web, there are some really cool projects that make this possible and this post will show you just that.\nThis will be a hands on tutorial on how to use Haste, a Haskell to Javascript compiler that allows you to write Haskell code that can be executed on the web. We will be creating a simple pong game while exploring some of the features of the Haste environment. You can check the game out over here.\nThis tutorial assumes that you have basic knowledge of haskell or some other functional programming language. It also uses version 0.4 of the Haste compiler so you should choose to install this version if you plan to try out the code along. Or you may want to follow this code instead ported for version 0.5.\nInstall Haste on your machine to get started. It requires GHC, the Glasgow Haskell Compiler so you want to install that first if you don\u0026rsquo;t have it already.\nThat aside, let\u0026rsquo;s begin our pong game.\nRules of the game There are two paddles and one ball. When the ball hits any of the walls (right, left) or paddles, it should change its direction. When a ball hits the ceiling or floor, the game should end. Paddles should be controlled by the player\u0026rsquo;s mouse. (Not really a rule of the game but hey!) First, we import some useful functions and datatypes from the Haste libraries. Create a file called pong.hs and add the following statements to the top of the file.\nimport Haste import Haste.Graphics.Canvas import Data.IORef Step 1 - Defining Our Game state and Initial Declarations We start by declaring a datatype GameState. We\u0026rsquo;ll use it to describe the state of our game at any given moment, using values like the ball and paddle position, current score, speed of the ball. Add this code to pong.hs.\ndata GameState = GameState{ ballPos :: Point, -- position of ball ballSpeed :: Point, -- how far will ball move in a single update paddlePos:: Double, -- start position of paddle on x axis score :: Int } Note that the paddlePos field has a single value. You might expect that we need to store the full dimensions of the paddles (x and y coordinates) but as you will see very soon, the other values never change and so are declared as constants instead of passing them around in our game state.\nNow we define constants to be used throughout the program. These include the dimensions of the canvas and partial dimensions of paddles, radius of the ball etc.\nwidth, height,ballRadius, paddleWidth, paddleHeight :: Double width = 500 -- width of canvas height = 600 -- height of canvas ballRadius = 5 --radius of ball paddleHeight = 5 -- height of paddle paddleWidth = 150 -- width of paddle halfWidth = width / 2 -- well, half the width halfHeight = height / 2 --also half the height scoreLabel :: String scoreLabel = \u0026#34;Score: \u0026#34; Also define a state describing the initial values of our game.\ninitialState :: GameState initialState = GameState{ ballPos = (20, 20), ballSpeed = (8, 10), paddlePos = (width / 2) - 75, --position around center of canvas score = 0 } Step 2 - Canvas, Paddles and Ball Our animation is going to be on an HTML canvas but we can make life easier by abstracting the process of creating a canvas. We define a function mkCanvas\nmkCanvas :: Double -\u0026gt; Double -\u0026gt; IO Elem\nThe type signature tells us in a nutshell that we give it two doubles (width and height of our canvas) and receive instructions for creating a HTML element as per Haskell monads. This is the body of the function.\nmkCanvas width height = do canvas \u0026lt;- newElem \u0026#34;canvas\u0026#34; setProp canvas \u0026#34;width\u0026#34; (show width) setProp canvas \u0026#34;height\u0026#34; (show height) setStyle canvas \u0026#34;display\u0026#34; \u0026#34;block\u0026#34; setStyle canvas \u0026#34;border\u0026#34; \u0026#34;1px solid black\u0026#34; setStyle canvas \u0026#34;margin\u0026#34; \u0026#34;0px auto 0 auto\u0026#34; setStyle canvas \u0026#34;backgroundColor\u0026#34; \u0026#34;black\u0026#34; return canvas This function creates a new canvas html element using the newElem function from the Haste.Graphics.Canvas library, sets its dimensions (height, width) and assigns some other housekeeping properties like color, border etc.\nThis library also gives us functions for creating basic shapes and pictures. Here are some we will be using\ncircle :: Point -\u0026gt; Double -\u0026gt; Shape() -- draw a circle at given Point with given radius rect :: Point -\u0026gt; Point -\u0026gt; Shape() -- draw a rectangle between two points color :: Color -\u0026gt; Picture() -\u0026gt; Picture() -- draw the given picture using the specified color A Point is simply a pair of floats which may or may not represent x and y coordinates in pixels\ntype Point = (Double, Double) Now back in our game, we can use the color function to define our color theme.\nwhite :: Picture () -\u0026gt; Picture () white = color (RGB 255 255 255) -- or whichever color you like For our ball and paddles, we use a familiar technique of abstracting the creation process using factory functions. Add this to pong.hs.\nball :: Point -\u0026gt; Picture () ball pt = white $ do fill $ circle pt ballRadius paddle :: Rect -\u0026gt; Picture () paddle (Rect x1 y1 x2 y2) = white $ do fill $ rect (x1, y1) (x2, y2) drawText :: Point -\u0026gt; String -\u0026gt; Picture () drawText point msg = white $ do text point msg All three functions are very similar to each other. Their type signature tells us they receive a set of values and return a Picture monad Picture(), instructions for drawing their respective shapes on a canvas. the drawText function writes text on the canvas.\nStep 3 - Drawing on the canvas At this point we have functions for creating our paddles, ball and canvas. It\u0026rsquo;s about time that we actually use them to well, create paddles, ball and a canvas.\nWe want to draw a picture of our game on the canvas and to do that we first need a picture of our game. This is where our GameState type comes into play (Pun intended! 😒).\ngamePicture :: GameState -\u0026gt; Picture () gamePicture state = do ball $ ballPos state -- ball position from `state` let x1 = paddlePos state -- paddle start position x2 = x1 + paddleWidth -- end position of paddle paddle $ Rect x1 0 x2 paddleHeight -- top paddle paddle $ Rect x1 (height - paddleHeight) x2 height -- bottom paddle font \u0026#34;20px italic Monospace\u0026#34; $ drawText (30,50) $ scoreLabel ++ show (score state) -- write the score onto the canvas The gamePicture function takes as an argument, a GameState and returns a Picture monad. So it basically gives you a picture based on our given game state which we can the render on our canvas. So what\u0026rsquo;s going on here?\nIn a do block, we create 4 pictures (Picture monads to be technical) to draw on the canvas.\nThe ball. Using the ballPos field of the given state as our argument to the ball function. and 3. The top and bottom paddles. Using the paddle function and their coordinates. Notice that we only needed the paddle\u0026rsquo;s x coordinate from the state, the rest can be inferred from the paddle\u0026rsquo;s orientation (Top or Bottom). The top paddle starts at height 0 while the bottom baddle starts right before the end of the canvas (height - paddleHeight). The text field showing the score on the canvas using the drawText function. While gamePicture produces the picture, the renderState function will do the actual rendering onto a canvas.\nrenderState :: Canvas -\u0026gt; GameState -\u0026gt; IO () renderState canvas state = render canvas $ do gamePicture state The render function is imported from haste and draws a given picture (or series of pictures using a do block) on the specified canvas.\nWe move on to (finally 😁) create our canvas.\nWe\u0026rsquo;ll create the canvas inside our main function. The main function main :: IO () in haskell, unlike any other function has to be called main and is the entry point of our program just like in C, Java etc.\nmain :: IO () main = do canvasElem \u0026lt;- mkCanvas width height addChild canvasElem documentBody Just canvas \u0026lt;- getCanvas canvasElem renderState canvas initialState At this point you can check that the screen is drawn as it should. Compile the program by running this command in a terminal.\n$ hastec --output-html pong.hs Haste automagically creates an HTML file called pong.html with the javascript version of our code embedded. You may omit the --output-html option if you just want the javascript file. Open the html file in a web browser and you should see a canvas, two paddles, a ball and a score card. But they are static and that\u0026rsquo;s because we havent added any animation to them yet. We do that next.\nStep 4 - Animation We continue with ball animation. The ballSpeed field of our GameState type is useful for this purpose. Simply put, everytime the screen is redrawn, we want the ball to change its position on the canvas. Incrementing the x and y coordinates of the ball by a value everytime gives us this effect and these values are what we have as ballSpeed in our GameState.\nmoveBall :: GameState -\u0026gt; GameState moveBall state = state {ballPos = (x + vx, y + vy)} --increment by vx and vy where (x, y) = ballPos state (vx, vy) = ballSpeed state moveBall increments the x and y coordinates of the ball and returns a new GameState with the new coordinates. Note that there is no mutation/side effects here as the function does indeed return a new value.\nOur paddles will be controlled by the mouse so we need to listen for mouse events, specifically the mousemove event.\nTo add an event listener to our game, we go back to our main function. Now REMOVE the last statement renderState canvas initialState from the do block as we will no longer be needing it. Instead, add the following statements to the do block.\nstateRef \u0026lt;- newIORef $ initialState onEvent canvasElem OnMouseMove $ \\mousePos -\u0026gt; do movePaddles mousePos stateRef Remember that thing about Haskell being a purely functional language? Whoever said that didn\u0026rsquo;t finish the entire story it seems. Variables are immutable in Haskell but there are a few ways to create references whereby we can change what the reference points to.\nWe won\u0026rsquo;t be able to do a lot without interacting with the real world now that we need to process mouse events from the user, so we use the IORef data type to do the job. This is the first sighting of mutation in our code but don\u0026rsquo;t be alarmed if you haven\u0026rsquo;t seen this before. Data.IORef makes it quite easy and safe to do this.\nThe first statement creates a reference object of type IORef GameState using the newIORef function. This creates a new GameState reference with our initialState, allowing us to modify its contents throughout our code.\nThe second statement adds a mousemove event to the canvas element. The onEvent function provided by Haste takes an Elem and an Event while Event constructor take as arguments a callback function à la Javascript. Our callback function \\mousePos -\u0026gt; do movePaddles mousePos stateRef receives the mouse position mousePos and moves the paddles whenever the mousemove event fires.\nHere is the code to move our paddles.\nmovePaddles :: (Int, Int) -\u0026gt; IORef GameState -\u0026gt; IO () movePaddles (mouseX, mouseY) stateRef = do atomicModifyIORef stateRef (\\state -\u0026gt; ((state {paddlePos = (fromIntegral mouseX) - (paddleWidth / 2)}), ())) readIORef extracts the GameState referenced by our state reference stateRef while atomicModifyIORef changes the referenced content using the extracted state. The atomicModifyIORef function unlike the modifyIORef mutates the variable atomically, preventing race conditions and the likes. Our state is simply updated by centering the position of the paddle around the mouse coordinates\nNow we move on to define our primary animation function. Let\u0026rsquo;s call it animate. It takes a Canvas, an IORef GameState (for rendering onto canvas) and returns an IO monad IO () .\nanimate :: Canvas -\u0026gt; IORef GameState -\u0026gt; IO () animate canvas stateRef = do state \u0026lt;- readIORef stateRef -- extract state from reference object renderState canvas state -- draw game picture atomicWriteIORef stateRef $ update state -- update state and rewrite state reference setTimeout 30 $ animate canvas stateRef -- sleep. then loop where update = moveBall animate updates that state with the update function, writes the updated state back to the state variable then waits 30 milliseconds before repeating the whole process so it runs in a loop. Later on we\u0026rsquo;ll add a few more functions used to compose the update function but for now it consists only of the moveBall. atomicWriteIORef is similar to atomicModifyIORef but overwrites the variable with a new state. This feels more efficient here since we can extract our pure state, pass it around various functions composed by the update function and then, only once do we need to commit the sinful act of changing the value referenced by our IORef variable 😇.\nNow add the following as the last line of our main function animate canvas stateRef\nOur main function should look like this\nmain :: IO () main = do canvasElem \u0026lt;- mkCanvas width height addChild canvasElem documentBody Just canvas \u0026lt;- getCanvas canvasElem stateRef \u0026lt;- newIORef $ initialState onEvent canvasElem OnMouseMove $ \\mousePos -\u0026gt; do movePaddles mousePos stateRef animate canvas stateRef Step 5 - Detecting Collision We\u0026rsquo;ve got our ball and paddles moving and what\u0026rsquo;s left is making the ball bounce.\nThe rules of our game requires the ball to bounce when it hits any of the walls or paddles. Let\u0026rsquo;s start with the paddles. Here\u0026rsquo;s our function paddleHit to detect collision between the ball and the paddle\npaddleHit :: GameState -\u0026gt; GameState paddleHit state = if and [bx\u0026#39; \u0026gt;= px, bx\u0026#39;\u0026#39; \u0026lt;= pl, (by \u0026gt;= height-ph) || (by \u0026lt;= ph)] -- if ball is touching paddle then state {ballSpeed = (vx, -vy), score = score state + 1} -- change ball direction and increase score else state -- otherwise do nothing where (bx,by) = ballPos state -- x and y coordinates of ball bx\u0026#39; = bx + ballRadius -- right edge of ball bx\u0026#39;\u0026#39; = bx - ballRadius -- left edge of ball (vx,vy) = ballSpeed state -- current ball speed px = paddlePos state -- x coordinate of paddle / left edge ph = paddleHeight -- ph is easier to type pl = px + paddleWidth -- right edge of paddle paddleHit checks if the ball coordinates are within the dimensions of the paddles and if so, returns a new GameState wherein the ball now heads in the opposite direction. This is accomplished by simply negating the vertical speed value of the ball. We also increment the score for each time the ball hits the paddle.\nNote that for the paddleHit function, the and function short-circuit evaluates, so once an expression returns false, we simply return our state unchanged.\nDetecting collision with walls is similar to the paddles.\n-- Change ball direction if ball hits walls wallCollision :: GameState -\u0026gt; GameState wallCollision state | (x + ballRadius) \u0026gt;= width = state {ballPos = (width - ballRadius,y), ballSpeed = (-vx, vy)} -- if ball crosses right boundary | (x + ballRadius) \u0026lt;= 0 = state {ballPos = (ballRadius, y), ballSpeed = (-vx, vy)} -- ball crosses left boundary | otherwise = state -- do nothing where (x, y) = ballPos state (vx,vy) = ballSpeed state We simply check if the ball crosses the right or left wall and if so, negate the horizontal speed of the ball.\nNow we have our functions to detect collision with the walls and paddles. Let\u0026rsquo;s use them to compose our update function. Go to the animate function. Currently the update function defined within it consists only of moveBall so add our two new functions to it by replacing the definition of update with this line of code.\nupdate = moveBall . paddleHit . wallCollision The last rule of the game to be implemented is that the game should end when the ball hits the ceiling or the floor. That means we also need to check for ball collisions with the ceiling and floor of our canvas. Add this function to the code.\ngameEnded :: GameState -\u0026gt; Bool gameEnded state | y \u0026gt;= height \u0026amp;\u0026amp; (x \u0026lt; px || x \u0026gt; px + paddleWidth) = True -- if ball reaches floor and not touching paddle | y \u0026lt;= 0 \u0026amp;\u0026amp; (x \u0026lt; px || x \u0026gt; px + paddleWidth) = True -- if ball reaches ceiling and not touching paddle | otherwise = False where (x,y) = ballPos state -- ball position px = paddlePos state -- paddle position gameEnded returns a Bool telling us if the game should end. That is, if the ball has collided with any of the vertical boundaries that isn\u0026rsquo;t the paddle. To make use of this function, we go back to our animate function and make our decision on whether to stop animating or not, based on the reply from gameEnded. Our animate function should now look like this\nanimate :: Canvas -\u0026gt; IORef GameState -\u0026gt; IO () animate canvas stateRef = do state \u0026lt;- readIORef stateRef renderState canvas state -- draw game picture if gameEnded state then renderState canvas state -- render one last time and quit else do atomicWriteIORef stateRef $ update state -- else update state setTimeout 30 $ animate canvas stateRef -- and continue where update = moveBall . paddleHit . wallCollision Wow! this is a really lengthy tutorial 😵. We were bound to reach this part at some point and good news is you now have written a game of pong in haskell.\nPersonally I\u0026rsquo;m a huge fan of Javascript as well so one thing I think is really cool about Haste is that you still get that Javascripty feeling, programming in it. Haste doesn\u0026rsquo;t take that away from you by forcing you to do things like DOM manipulation or event listening in a different way.\nYou may have seen that I included some extra features in the live demo such as start, restart buttons and speed of the ball increasing during the game. Browse through the code to see how these were implemented, then try to implement them and add more features to your game or fork this one on github and continue from there. If you get stuck in implementation somewhere, leave me a message/comment and I\u0026rsquo;ll be sure to help you as much as I can.\n","permalink":"https://ifeanyi.co/posts/client-side-haskell/","summary":"\u003cp\u003eHi! before introducing this post let\u0026rsquo;s do some myth busting! 😎\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003ePurely functional programming languages have no place in the browser and Haskell is a magical genius language that everyone talks about but no one really uses.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eTruth is, haskell is not a difficult language to program in and it\u0026rsquo;s past reputation of never being used in the industry \u003ca href=\"https://code.facebook.com/posts/745068642270222/fighting-spam-with-haskell/\" target=\"_blank\" rel=\"noopener\"\u003ehas always been uncalled for\u003c/a\u003e.\nAs for how we can use it to program applications for the web, there are some really \u003ca href=\"http://elm-lang.org\" target=\"_blank\" rel=\"noopener\"\u003ecool\u003c/a\u003e \u003ca href=\"https://github.com/ghcjs/ghcjs\" target=\"_blank\" rel=\"noopener\"\u003eprojects\u003c/a\u003e that make this possible and this post will show you just that.\u003cbr\u003e\n\u003cbr\u003e\nThis will be a hands on tutorial on how to use \u003ca href=\"http://haste-lang.org\" target=\"_blank\" rel=\"noopener\"\u003eHaste\u003c/a\u003e, a Haskell to Javascript compiler that allows you to write Haskell code that can be executed on the web. We will be creating a simple pong game while exploring some of the features of the Haste environment. You can check the game out \u003ca href=\"/demo/pong/\"\u003eover here\u003c/a\u003e.\u003c/p\u003e","title":"Client-side haskell"},{"content":" A pong game written in haskell. Check out the tutorial here or fork it on github. ","permalink":"https://ifeanyi.co/demo/pong/","summary":"\u003cscript src=\"/assets/js/pong.js\"\u003e\u003c/script\u003e\n\u003cstyle\u003e\n  /* Haste appends the game canvas to \u003cbody\u003e; keep it above the footer. */\n  body { display: flex; flex-direction: column; }\n  body \u003e canvas { order: 1; align-self: center; margin-bottom: 2rem !important; }\n  body \u003e .footer, body \u003e .top-link { order: 2; }\n  .main { min-height: 0; }\n\u003c/style\u003e\n\u003cdiv style='text-align:center'\u003e\n  A pong game written in haskell. Check out the \u003ca href=\"/posts/client-side-haskell/\" target=\"_blank\"\u003e tutorial here\u003c/a\u003e or \u003ca href=\"https://github.com/iffyio/pong.hs\" target=\"_blank\"\u003efork it on github\u003c/a\u003e.\n\u003c/div\u003e","title":"Haste-pong"}]