aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/guide/cryptexisting.md152
-rw-r--r--src/guide/iproute2tun.md132
-rw-r--r--src/guide/kppp.md376
-rw-r--r--src/guide/krbnfs.md124
-rw-r--r--src/guide/ovpnip6.md69
-rw-r--r--src/guide/vf6.md122
-rw-r--r--src/guide/wifi103.md64
-rw-r--r--src/guides.md17
-rw-r--r--src/index.md153
-rw-r--r--src/password_generator.md54
-rw-r--r--src/rsdsl.md375
-rw-r--r--src/rustkrazy.md120
12 files changed, 1758 insertions, 0 deletions
diff --git a/src/guide/cryptexisting.md b/src/guide/cryptexisting.md
new file mode 100644
index 0000000..a77eba2
--- /dev/null
+++ b/src/guide/cryptexisting.md
@@ -0,0 +1,152 @@
+% Encrypting existing drives
+
+# Disclaimer
+**It is not easily possible to use most of the methods described here
+to encrypt existing drives without having to make and restore a backup.**
+
+# Preparation: Making a backup
+It is necessary to create a backup of your drive as encrypting will
+erase your data.
+
+## SquashFS
+I like to use SquashFS as I don't have much backup
+space:
+
+```sh
+apt update
+apt install squashfs-tools
+
+mksquashfs /media/drive/mountpoint /media/backup/mountpoint/drive.sqsh
+```
+
+The above commands all need to be run as root.
+
+A major disadvantage of SquashFS is its slowness. It uses all CPU cores
+but still takes a long time to complete depending on how much data is
+being squashed.
+
+## tar
+If you can afford to store a raw copy, you can create it with `tar`.
+The `tar` command is faster than `cp` or `rsync` for copying many
+large files. Here's how to use it:
+
+```sh
+tar -c -C /media/drive/mountpoint . | \
+tar --same-owner -xp -C /media/backup/location
+```
+
+Make sure you run this as root.
+
+This is much faster compared to squashing but it requires much more
+storage space.
+
+# Wiping
+This is optional but it's highly recommended to do if unencrypted data
+used to be stored on the drive.
+Some encryption tools such as OS installers do this automatically, but
+pure cryptsetup does not. To be safe, wipe manually:
+
+```sh
+dd bs=1M if=/dev/urandom of=/dev/sdX
+```
+
+Once again only root can do this.
+
+Replace `/dev/sdX` with the device file of the drive you want to encrypt.
+You can specify a partition number if you only want to wipe a single
+partition.
+
+If you're still using an old kernel (<4.8) this is going to be slow.
+Replace `/dev/urandom` with `/dev/zero` to counter this.
+
+# Encrypting
+There are three methods I have used.
+
+## LVM + LUKS
+This is recommended for drives with an operating system.
+
+Do a complete reinstall and select the "encrypted LVM" option when
+partitioning. Make sure to use a secure passphrase that you can still
+remember.
+
+This sets up LVM and LUKS.
+
+## LUKS
+Use this for external drives that are always connected to the same
+machine.
+
+Run the following commands as root:
+
+```sh
+cryptsetup luksFormat -c aes-xts-plain64 -s 512 -h sha512 -y /dev/sdXY
+cryptsetup luksOpen /dev/sdXY sdXY-crypt
+mkfs.ext4 /dev/mapper/sdXY-crypt
+cryptsetup luksClose sdXY-crypt
+```
+
+where sdXY is the name of the device file of the partition.
+
+## VeraCrypt volume
+This is useful if you want to use your drive in other places or on
+other platforms. Follow the VeraCrypt instructions for this.
+
+# Restoring the backup
+No matter how you made your backup, `tar` is the way to restore it.
+Before you do that you have to take care of some other things.
+
+## SquashFS
+Mount the SquashFS image:
+
+```sh
+mount /media/squashfs/location/drive.sqsh /media/backup/mountpoint
+```
+
+You now need to mount the encrypted device. This is quite easy to do
+with VeraCrypt volumes. When you mount one the mountpoint is usually
+`/media/veracryptX`. For LUKS it works like this:
+
+```sh
+cryptsetup luksOpen /dev/sdXY sdXY-crypt
+mount /dev/mapper/sdXY-crypt /media/drive/mountpoint
+```
+
+LVM + LUKS is slightly different:
+
+```sh
+cryptsetup luksOpen /dev/sdXY sdXY-crypt
+lvchange -ay hostname-vg/partitionname
+mount /dev/hostname-vg/partitionname /media/drive/mountpoint
+```
+
+Now restore the backup:
+
+```sh
+tar -c -C /media/backup/mountpoint . | \
+tar --same-owner -xp -C /media/drive/mountpoint
+```
+
+# Cleaning up
+Now unmount the encrypted volume (if you don't want to use it yet)
+and delete the SquashFS. Unmounting VeraCrypt volumes is easy
+enough to not be documented here.
+
+## Unmounting LUKS
+```sh
+umount /media/drive/mountpoint
+cryptsetup luksClose sdXY-crypt
+```
+
+## Unmounting LVM + LUKS
+```sh
+umount /media/drive/mountpoint
+lvchange -an hostname-vg/partitionname
+cryptsetup luksClose sdXY-crypt
+```
+
+***WARNING: Store the SquashFS image on an encrypted drive
+or wipe it securely! A simple `rm` won't do, especially with
+solid state storage!***
+
+[Return to Guide List](/cgi-bin/guides.lua)
+
+[Return to Index Page](/cgi-bin/index.lua)
diff --git a/src/guide/iproute2tun.md b/src/guide/iproute2tun.md
new file mode 100644
index 0000000..3c049d9
--- /dev/null
+++ b/src/guide/iproute2tun.md
@@ -0,0 +1,132 @@
+% Write your own ip-tunnel
+
+# Introduction
+
+In this post we're going to explore how to create IP-in-IP tunnels
+without writing a userspace encapsulation driver.
+The main advantage here is keeping the userspace code clean and simple.
+
+# IP-in-IP tunnels
+
+This is a type of managed tunnel. It works by simply adding an outer IP header
+to the datagrams. The local and remote IP addresses have to be configured
+beforehand. There is no encryption or handshaking.
+It's about as simple as it gets.
+
+You can tunnel a version of IP inside of itself or the other version.
+This allows for the following combinations to be implemented:
+
+* IPv4 in IPv4: ipip / 4in4
+* IPv6 in IPv6: ip6ip6 / 6in6
+* IPv6 in IPv4: sit / 6in4
+* IPv4 in IPv6: ipip6 / 4in6
+
+Inter-protocol tunnels are much more common than their intra-protocol variants.
+IPv6-in-IPv4 lies at the core of many IPv6 transition mechanisms
+including 6in4 using a tunnel broker or legacy 6to4. IPv4-in-IPv6
+is a core component of DS-Lite.
+
+These tunnels require kernel support and can be configured
+using the `ip tunnel` subcommand.
+
+Support is enabled by building Linux with the following flags:
+
+```
+CONFIG_NET_IP_TUNNEL=y
+CONFIG_INET_TUNNEL=y
+CONFIG_INET6_TUNNEL=y
+CONFIG_IPV6_TUNNEL=y
+CONFIG_IPV6_SIT=y
+CONFIG_NFT_TUNNEL=y
+```
+
+# So how does it work?
+
+Unlike most other types of network interfaces tunnels don't require netlink
+configuration. Creating links like VLANs does require this,
+but tunnels don't. Netlink is still used to change configuration parameters
+like the IP addresses but it's not involved in providing the creation
+and deletion API.
+
+# Let's reimplement it
+
+This could probably be implemented using unsafe Rust code without the need
+for C bindings, but they do make our life much easier.
+
+Tunnel control works using the `ioctl` syscall on any dummy IPv4 or IPv6 socket.
+You should probably use the outer protocol, though IPv6 might work
+for everything.
+
+The control socket is created as follows:
+
+```
+int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
+```
+
+not including any error handling. `IPPROTO_IP` is zero.
+
+Here's the `ioctl` we have to prepare to create a 4in4 tunnel:
+
+```
+struct ip_tunnel_parm p;
+
+strcpy(p.name, "footnl0");
+p.iph.version = 4; // outer protocol
+p.iph.ihl = 5;
+p.iph.ttl = 64;
+p.iph.protocol = IPPROTO_IPIP; // inner protocol
+p.iph.saddr = /* our address as big-endian u32 */
+p.iph.daddr = /* remote address as big-endian u32 */
+p.link = if_nametoindex("ppp0"); // parent interface
+
+struct ifreq ifr;
+
+strcpy(ifr.ifr_name, "tunl0"); // default name for our tunnel type
+ifr.ifr_ifru.ifru_data = (char *) &p;
+
+ioctl(fd, SIOCADDTUNNEL, &ifr);
+```
+
+This will create the tunnel assuming you have the required permissions.
+The default interface name for 4in4 is `tunl0`. This is different
+for the other tunnel types. From what I understand `sit0`
+is used if the inner protocol is IPv6, but if it doesn't work
+you'll have to read the iproute2 source code and test it for yourself.
+One easy way to do this is making a debug build and attaching `gdb`
+to set a breakpoint at the `ioctl` call and inspect the `ifr` struct.
+
+The MTU is set automatically but bringing the interface up
+and configuring the internal addressing is up to you.
+Also unlike the more complex PPP driver the tunneling code
+does not delete the interface when your application exits.
+If this is unwanted behavior you need to call a deletion function
+before exiting which can even be automated using Rust's destructors.
+
+An interesting fact to note is that the MTU is automatically calculated
+from the overhead and the parent MTU. Because of this it's always going to be
+correct, even if you're tunneling through PPPoE.
+
+# Deletion
+
+Initialise a socket like you did when creating the tunnel.
+The request and `ioctl` call are different though:
+
+```
+struct ip_tunnel_parm p;
+
+strcpy(p.name, "footnl0");
+
+struct ifreq ifr;
+
+strcpy(ifr.ifr_name, "footnl0");
+ifr.ifr_ifru.ifru_data = (char *) &p;
+
+ioctl(fd, SIOCDELTUNNEL, &ifr);
+```
+
+Thankfully all of this is much simpler than PPP
+once you figure out how it works. Have fun with your cleanly created tunnels!
+
+[Return to Guide List](/cgi-bin/guides.lua)
+
+[Return to Index Page](/cgi-bin/index.lua)
diff --git a/src/guide/kppp.md b/src/guide/kppp.md
new file mode 100644
index 0000000..059af0f
--- /dev/null
+++ b/src/guide/kppp.md
@@ -0,0 +1,376 @@
+% Write your own PPP(oE) client with kernel mode tunneling
+
+# Introduction
+
+In this post we're going to explore how PPPoE works
+and how to write your own kernel-aware client for it.
+
+If you've ever configured a PPP connection on a regular router
+using something like OpenWrt, you are probably familiar
+with the components that make it work.
+
+There's `pppd`, the main program. It establishes a connection
+and provides you with the `ppp0` interface.
+To do so it delegates some mystery work to the kernel.
+It also needs a user mode .so plugin for PPPoE support.
+
+# PPPoE vs. PPP
+
+PPP stands for Point-to-Point Protocol. It first became popular
+with dial-up connections. The serial line you used to connect
+to the modem was guaranteed to be a connection between exactly
+two machines.
+
+PPP is still in use today by many DSL and some G.PON connections.
+The modems now usually come with two Ethernet ports
+for your downstream devices. Due to the nature of any Ethernet link
+this means that the connection between your router and the ISP
+is no longer guaranteed to be P2P.
+
+PPPoE establishes a point-to-point session over Ethernet,
+allowing PPP to be used on Ethernet links. PPP frames are encapsulated
+in PPPoE packets.
+
+PPPoE knows five packet types. It uses a four-way handshake to connect:
+
+```
+1. C -> (Ethernet broadcast) : PPPoE Active Discovery Initiation (PADI)
+2. (all servers) -> C : PPPoE Active Discovery Offer (PADO)
+3. C -> S (any of the offers): PPPoE Active Discovery Request (PADR)
+4. S -> C : PPPoE Active Discovery Session-confirmation (PADS)
+```
+
+A PPP session is then started with a different EtherType.
+
+Either side can force the session to be terminated at any time
+by sending a `PPPoE Active Discovery Terminate (PADT)`.
+This usually happens after the higher level PPP session is terminated
+or if PPP termination fails or is unavailable (which it is in early phases).
+
+# How PPP works
+
+PPP itself is simply a collection of protocols that can affect the state
+of other protocols.
+
+There are different types of PPP protocols:
+
+* LCP
+* Authentication
+* NCPs
+* Data Link Layer
+
+LCP as well as most (if not all) NCPs are option negotiation protocols.
+They consist of the following packets:
+
+* Configure-Request
+* Configure-Ack
+* Configure-Nak
+* Configure-Reject
+* Terminate-Request
+* Terminate-Ack
+* Code-Reject
+
+Both sides send a Configure-Request with configuration options.
+
+If a peer can't accept a value but has a suggestion for a valid value,
+it replies with a Configure-Nak containing all options this applies to.
+The sender may then use some or all of the suggested values and retry,
+or give up.
+
+If a peer can't accept a value because it must not be set
+(this often applies to boolean options that don't have a value)
+it replies with a Configure-Reject containing all options this applies to.
+The sender may then unset some or all of the options and retry,
+or give up.
+
+If the configuration is acceptable, the peer replies with a Configure-Ack
+containing the same options.
+
+If a peer decides to close the protocol, it sends a Terminate-Request
+with optional data (e.g. reason string). If it receives a Terminate-Ack
+or doesn't receive one after multiple retransmissions,
+it closes the protocol anyway.
+
+A Code-Reject signals an error condition likely caused by a bug
+or incompatible protocol versions which don't actually exist.
+
+## Link Control Protocol (LCP)
+
+LCP configures link information. This usually includes reducing the MRU
+and exchanging magic numbers. In addition to this most ISPs request
+authentication.
+
+LCP has more packets than the ones mentioned above:
+
+* Protocol-Reject
+* Echo-Request
+* Echo-Reply
+* Discard-Request
+
+A Protocol-Reject is sent in response to an invalid or unsupported NCP
+or authentication protocol being used. It tells the sender to stop
+trying to use that protocol.
+
+An Echo-Request is replied to with an Echo-Reply. If no reply is received
+the sender usually terminates the connection after a few attempts.
+These packets contain the magic number of the sender.
+This can be used to detect error conditions like a link that's looped back.
+See [RFC 1661](https://rfc-editor.org/rfc/rfc1661) for details.
+Both sides are free to choose not to send Echo-Requests.
+
+A Discard-Request is a no-op and can be used to analyze link performance.
+
+### Common options
+
+With PPPoE both peers exchange a Maximum Receive Unit (MRU) of 1492
+instead of the default 1500. This option is used to ask the peer
+to send smaller packets. PPPoE actually violates the PPP RFC
+because a peer is still required to be able to receive the full
+1500 bytes in case link synchronization is lost.
+However this doesn't cause any issues in the real world
+since packets of that size are only exchanged while the link is synchronized.
+
+The value 1492 is the result of this calculation:
+
+```
+MRU = Ethernet_MTU - PPP_header_size - PPPoE_header_size
+ = 1500 - 2 - 6
+ = 1500 - 8
+ = 1492
+```
+
+The magic number is a random non-zero value as described in the RFC
+that is unique to each peer.
+
+The authentication option can be unset for no authentication
+or set to one of the various authentication protocols
+and detailed configuration data for it.
+The most common protocols are PAP and CHAP.
+CHAP requires the option to contain a hashing algorithm,
+this is usually set to MD5 (password authentication for internet access
+is unnecessary anyway).
+
+## Authentication
+
+This is skipped if no authentication is required according to the LCP exchange.
+
+If authentication fails, the authentication protocol attempts to
+notify the client before using LCP to terminate the connection.
+
+### Password Authentication Protocol (PAP)
+
+This is a very simple protocol and extremely common.
+You simply send your credentials in plain text and the server tells you
+if you were right.
+
+### Challenge-Handshake Authentication Protocol (CHAP)
+
+CHAP is also quite common and usually uses MD5 as its hashing algorithm.
+The server sends a challenge which is just a long random sequence of bytes.
+The client then calculates `H(challenge|packet id|password)`
+where `H` is the hash function. A `|` denotes a concatenation.
+The `packet id` is the identifier (a kind of sequence number)
+of the challenge packet.
+
+The client then responds with the calculated hash and the username.
+The server replies, telling it if the password was correct.
+
+## Network Control Protocols (NCPs)
+
+Once the PPP link has been established and authenticated
+at least one network protocol needs to be configured.
+There's a whole collection of these but we only care about IPCP and IPv6CP.
+
+Once an NCP enters the 'Opened' state its corresponding data protocol
+may be used to exchange traffic.
+
+### Internet Protocol Control Protocol (IPCP)
+
+This is the configuration protocol for native IPv4.
+Both peers request the configuration options they want to use for themselves.
+
+For the ISP server this is the default gateway address,
+although it is technically not needed since the link is guaranteed
+to be point-to-point anyway.
+
+The client implementation requests the IP address `0.0.0.0`
+and optionally sets both DNS servers to zero as well.
+The ISP then nak's this configuration, suggesting the actual values.
+The client requests these values and receives an ack.
+
+### Internet Protocol Version 6 Control Protocol (IPv6CP)
+
+This is similar to IPCP but it only exchanges 64-bit interface identifiers
+that are used with the `fe80::/64` link-local prefix.
+This connection does not provide internet connectivity
+but it can be used to obtain a prefix using DHCPv6-PD
+as well as the default gateway using regular RAs.
+DHCPv6 provides additional information like DNS servers
+or an AFTR address for use with DS-Lite.
+
+Notice that the router itself doesn't receive a WAN address?
+This is normal behavior for some reason.
+You can either make use of a LAN side address if your router assigns them
+to itself, or you can derive a WAN address from your delegated prefix.
+
+# So how do we implement a client?
+
+There are two ways in which we can do this.
+The first one is handling everything in userspace
+and offering a TUN device to the OS.
+This is what [rsdsl_pppoe](https://github.com/rsdsl/pppoe.git) does.
+However the overhead is quite substantial, even in Rust.
+Its safety constraints also make efficient packet tunneling very challenging.
+The current implementation suffers from bufferbloat so the latency increases
+to more than 400 ms as soon as the the connection is under load.
+
+The Linux kernel has native support for both PPP and PPPoE.
+However this seems to be made specifically for `pppd`
+and there is pretty much no documentation at all, and trust me -
+reading kernel code is not fun. Nonetheless I found out how it works
+and made a sys crate for it.
+
+Kernel mode tunneling is likely to be more performant
+and significantly simplifies the code base. Here's how it works.
+
+# Kernel mode PPPoE
+
+For this to work the kernel has to have support. Most distros use modules
+for this, but a minimal platform like [rustkrazy](/cgi-bin/rustkrazy.lua)
+needs to compile native support into the kernel
+by setting the following options:
+
+```
+CONFIG_PPP=y
+CONFIG_PPPOE=y
+```
+
+We will still have to handle the aforementioned configuration protocols
+and packets but the kernel is going to take care of the interface
+and data transmission and reception.
+
+Due to its complexity the kernel interfacing is handled by C bindings
+which are seemingly safely wrapped by Rust code.
+Since we want to know how the kernel interface works
+we're going to look at the C code.
+
+Control of this feature is done using sockets.
+
+## Discovery socket
+
+This is going to be used internally for PPPoE discovery packets.
+The socket is created like this:
+
+```
+int sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_PPP_DISC));
+
+int broadcast = 1;
+setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof broadcast);
+```
+
+not including error handling. The broadcast option is needed
+because PADI packets are sent to the broadcast MAC address.
+
+Next an `ioctl` call is used to get the hardware address of the interface:
+
+```
+struct ifreq ifr;
+
+memset(ifr.ifr_name, 0, IFNAMSIZ);
+strncpy(ifr.ifr_name, "eth1", IFNAMSIZ - 1);
+
+ioctl(sock, SIOCGIFHWADDR, &ifr);
+```
+
+resulting in the MAC address being stored in `ifr.ifr_hwaddr.sa_data`.
+
+Another `ioctl` is used to get the index of the interface
+since `bind` requires an index rather than a name:
+
+```
+ioctl(sock, SIOCGIFINDEX, &ifr);
+```
+
+which is then used to bind actually bind the socket:
+
+```
+struct sockaddr_ll sa;
+
+sa.sll_family = AF_PACKET;
+sa.sll_protocol = htons(ETH_P_PPP_DISC);
+sa.sll_ifindex = ifr.ifr_ifindex;
+
+bind(sock, (struct sockaddr *) &sa, sizeof sa);
+```
+
+This socket can then be converted to Rust's `socket2::Socket`
+and used normally.
+
+## Session initialization
+
+Once PPPoE has established a session we need to actually get the kernel
+to tunnel traffic. To do this we first create a transport socket
+for the PPP frames:
+
+```
+int sock = socket(AF_PPPOX, SOCK_STREAM, PX_PROTO_OE);
+
+struct sockaddr_pppox sp;
+
+sp.sa_family = AF_PPPOX;
+sp.sa_protocol = PX_PROTO_OE;
+sp.sa_addr.pppoe.sid = htons(/* pppoe_session_id */);
+memcpy(sp.sa_addr.pppoe.dev, "eth1", 4 + 1);
+memcpy(sp.sa_addr.pppoe.remote, /* server_mac */, 6);
+
+connect(sock, (const struct sockaddr *) &sp, sizeof sp);
+```
+
+This is a so-called generic PPP channel which is simply a transport
+for PPP frames that automatically handles PPPoE headers.
+To actually use it we need its channel ID:
+
+```
+int chindex;
+ioctl(sock, PPPIOCGCHAN, &chindex);
+```
+
+Next we open a control file descriptor that's going to be attached
+to the PPP channel:
+
+```
+int ctlfd = open("/dev/ppp", O_RDWR);
+
+ioctl(ctlfd, PPPIOCATTCHAN, &chindex);
+```
+
+This is where LCP and authentication packets are going to arrive. However
+the full PPP header is included (PPPoE is not).
+
+Finally we create a generic PPP unit. This is the actual `ppp0` interface
+and it's where the traffic actually ends up. We create it and connect it
+to the channel we created earlier as its transport:
+
+```
+int pppdevfd = open("/dev/ppp", O_RDWR);
+
+int ifunit = -1;
+ioctl(pppdevfd, PPPIOCNEWUNIT, &ifunit);
+ioctl(ctlfd, PPPIOCCONNECT, &ifunit);
+```
+
+This is where the NCPs are going to arrive.
+
+In summary we now have a socket for discovery packets
+as well as two PPP file descriptors for link synchronization and authentication
+as well as the network configuration protocols.
+What's cool is that the kernel automatically creates a working `ppp0`
+interface for us. It's down initially but it can manually be brought up
+and configured with the addresses by [netlinkd](https://github.com/rsdsl/netlinkd.git).
+
+This was very frustrating to find out and took a long time.
+Have fun writing your own kernel mode PPPoE clients using this knowledge.
+
+[Return to Guide List](/cgi-bin/guides.lua)
+
+[Return to Index Page](/cgi-bin/index.lua)
diff --git a/src/guide/krbnfs.md b/src/guide/krbnfs.md
new file mode 100644
index 0000000..bc388f1
--- /dev/null
+++ b/src/guide/krbnfs.md
@@ -0,0 +1,124 @@
+% Kerberized NFS: access denied by server while mounting
+
+# Introduction
+Protecting a NFS share with Kerberos is not very easy to do but definitely
+doable with a good setup manual. A very helpful website is
+https://wiki.ubuntuusers.de although some of the pages
+have since been archived.
+
+# Setup
+The hostnames are different in my actual setup and will certainly be
+different for you.
+
+There are two machines involved. The first one is the server.
+It's running a krb5 KDC and admin server as well as a NFS server.
+The NFS export is configured to allow any source address
+but requires krb5i or krb5p security.
+
+The client computer is running a krb5 client and a NFS client with
+the necessary rpc daemons.
+
+#### Kerberos principals
+* admin/admin, has full access to kadmin
+* himbeerserverde, a regular user
+* host/srv.himbeerserver.de, server host key
+* host/clt.himbeerserver.de, client host key
+* nfs/srv.himbeerserver.de, server NFS key
+* nfs/clt.himbeerserver.de, client NFS key
+
+The users are synced across other clients and the server using LDAP.
+The clients use SSSD to cache credentials. This way they can operate
+without a permanent connection to the LDAP server. They also keep working
+in case of a server failure.
+The server uses local auth for the actual accounts. The other accounts
+are not intended to be logged into. A LDAP failure will only result
+in a broken NFS.
+
+I'm aware this isn't the best solution. I'm probably going to come up
+with a better one in about half a decade.
+
+# The Error
+This is the command I use to mount the NFS share:
+
+```sh
+sudo mount -t nfs4 -o sec=krb5i,async,soft srv.himbeerserver.de:/media/ssd /mnt/himbeerserverde/nfs
+```
+
+This suddenly resulted in the above error. I couldn't really figure out
+what was going on. This has happened several times and could sometimes be
+fixed by rebooting both machines. Unfortunately rebooting didn't help
+most of the time.
+
+# Debugging
+The logs are not very helpful for debugging this error.
+Adding `-vvvv` to the mount command outputs more but still only shows
+that permission was denied, not why it's happening.
+Looking at the traffic with wireshark I didn't see any Kerberos packets.
+
+The syslog eventually lead me to the systemd service `auth-rpcgss-module`.
+It failed to start. The reason was a kernel update that had been installed
+but not yet activated. Rebooting fixed this by restoring synchronization
+of the kernel version and the modules' required kernel version.
+
+I'm not sure if that module is required but given its name it seems to be.
+Reading the krb5 logs (using `journalctl -xeu krb5-kdc.service`) I could
+see that the KDC refused to issue service tickets to the server.
+There were attempts from the client to get a service ticket earlier that
+day that were also denied. In both cases the reason was failing authentication.
+
+The fact that the server was experiencing the issue made me think that it
+was a host authentication issue that had nothing to do with the user.
+This later turned out to be correct.
+
+# The Solution
+After spending days googling for a solution and trying different things
+I decided to completely reconfigure host-related principals.
+Here's exactly what I did:
+
+Server:
+```sh
+srv# rm /etc/krb5.keytab
+srv# kadmin -p admin/admin
+kadmin: purgekeys host/srv.himbeerserver.de
+kadmin: purgekeys nfs/srv.himbeerserver.de
+kadmin: delprinc host/srv.himbeerserver.de
+kadmin: delprinc nfs/srv.himbeerserver.de
+kadmin: addprinc -randkey host/srv.himbeerserver.de
+kadmin: addprinc -randkey nfs/srv.himbeerserver.de
+kadmin: ktadd host/srv.himbeerserver.de
+kadmin: ktadd nfs/srv.himbeerserver.de
+kadmin: quit
+srv# systemctl restart nfs-kernel-server rpc-gssd rpc-svcgssd
+```
+
+It's important to restart rpc-gssd to make it reload the keytab.
+I'm not sure if restarting rpc-svcgssd is necessary.
+Purging the user keys is *probably* not needed either but you can
+do it if the above steps didn't work.
+
+Client (repeat for all affected clients with the corresponding keys):
+```sh
+clt# rm /etc/krb5.keytab
+clt# kadmin -p admin/admin
+kadmin: purgekeys host/clt.himbeerserver.de
+kadmin: purgekeys nfs/clt.himbeerserver.de
+kadmin: delprinc host/clt.himbeerserver.de
+kadmin: delprinc nfs/clt.himbeerserver.de
+kadmin: addprinc -randkey host/clt.himbeerserver.de
+kadmin: addprinc -randkey nfs/clt.himbeerserver.de
+kadmin: ktadd host/clt.himbeerserver.de
+kadmin: ktadd nfs/clt.himbeerserver.de
+kadmin: quit
+clt# systemctl restart rpc-gssd
+```
+
+Once again purging the user keys is *probably* not needed but you
+can do it if the above steps didn't work.
+
+Now mount the NFS share again. If it still doesn't work, reboot
+the server and the client. If that doesn't fix it unfortunately
+I can't help you.
+
+[Return to Guide List](/cgi-bin/guides.lua)
+
+[Return to Index Page](/cgi-bin/index.lua)
diff --git a/src/guide/ovpnip6.md b/src/guide/ovpnip6.md
new file mode 100644
index 0000000..10921d5
--- /dev/null
+++ b/src/guide/ovpnip6.md
@@ -0,0 +1,69 @@
+% Setting up OpenVPN IPv6 support
+
+# The different kinds of IPv6 support
+OpenVPN supports IPv6 in two different ways. It can listen on
+an IPv6 socket so that IPv6 clients can connect to it.
+This way you can run OpenVPN servers that are IPv6 only.
+
+This does not allow connected clients to access hosts via IPv6.
+In order to achieve that the server needs to assign addresses
+that are routable on the internet.
+
+# Listening on an IPv6 socket
+This is quite easy to set up. Open your `/etc/openvpn/server.conf`
+and append a 6 to the proto line. For example `proto udp`
+becomes `proto udp6`.
+
+# Assigning addresses from a prefix
+The OpenVPN server can assign IPv6 addresses from a prefix.
+I recommend a /64 subnet, but OpenVPN supports smaller prefixes
+such as /112 as well. If you have a bigger subnet such as /60
+you can make it a /64 by filling it up with zeros.
+
+To enable this feature add this to your `/etc/openvpn/server.conf`:
+
+```
+server-ipv6 2001:db8:0:123::/64
+```
+
+## Static addresses
+*WARNING: If you're using the client config dir to set static IPv4
+addresses you have to set static IPv6 addresses as well:*
+
+```
+ifconfig-ipv6-push 2001:db8:0:123::abcd/64 2001:db8:0:123::1
+```
+
+where `abcd` is the IFID you'd like the client to get.
+
+## Pushing routes
+Now we have to route IPv6 traffic through the tunnel.
+Traffic to the subnet of GUAs (2000::/3) always has to be routed
+through the tunnel. If you have an ULA prefix or anything else
+you'd like to go through the tunnel simply add another
+line to the config and use that prefix.
+
+Add this to `/etc/openvpn/server.conf`:
+
+```
+push "route-ipv6 2000::/3"
+```
+
+# Routing
+The OpenVPN IPv6 prefix either needs to be NATed or routed.
+If it's a subnet of the IPv6 prefix assigned by your ISP
+everything should work right away. Otherwise you have to configure
+IPv6 NAT which is a dirty solution but should work.
+
+# Firewall
+Don't forget to protect the OpenVPN IPv6 subnet with a firewall.
+This is NOT a security issue of IPv6, IPv4 needs a firewall too.
+
+You can allow certain requests to go through. This way you can
+"forward" IPv6 ports from a location that supports it to another
+location that doesn't support it but is connected to the OpenVPN
+server.
+
+[Return to Guide List](/cgi-bin/guides.lua)
+
+[Return to Index Page](/cgi-bin/index.lua)
diff --git a/src/guide/vf6.md b/src/guide/vf6.md
new file mode 100644
index 0000000..548b2a3
--- /dev/null
+++ b/src/guide/vf6.md
@@ -0,0 +1,122 @@
+% IPv6 mit Vodafone DSL
+
+# Einführung
+
+Seit kurzem (2023) habe ich einen DSL-Anschluss bei Vodafone.
+Leider wurde auf diesem nur IPv4 geschaltet.
+Wie verbreitet diese Anschlussart ist oder warum sie bei Neuverträgen
+noch vergeben wird ist mir leider nicht bekannt. Allerdings lässt sich IPv6
+am DSL-Anschluss von Vodafone ohne große Probleme freischalten.
+
+# Anschlussarten
+
+Im Festnetzbereich für Privatkunden gibt es bei Vodafone drei verbreitete
+Anschlusstypen: IPv4 only, Dual Stack und DS-Lite. Dabei wird Dual Stack
+nie automatisch geschaltet. Ob man IPv4 only oder DS-Lite bekommt
+ist bei Vertragsabschluss leider nicht ersichtlich.
+
+Sollte IPv4 only nicht der Standard sein, ist die Ursache bei mir
+wahrscheinlich entweder der Vertragsabschluss über ein Vergleichsportal
+oder die Wahl der Option "Eigenhardware Kunde" bei der Routerwahl.
+Letzteres kommt deshalb in Frage, weil es auf im Kabel-Segment
+zu einer anderen Art der Adressvergabe führt.
+
+# Anschlussart prüfen
+
+Welche Protokolle geschaltet werden, lässt sich im alten Kundenportal einsehen.
+Dazu meldet man sich auf https://dsl.vodafone.de an und navigiert zu
+`Meine Produkte > Internet`. Dort steht unter dem Punkt "Konfiguration",
+wie man angebunden ist. Ist dort "IPv4" zu sehen, so bekommt man zwar
+eine dynamische, öffentliche IPv4-Adresse, aber keinen IPv6-Zugang.
+
+# Technische Informationen zur Nichtverfügbarkeit
+
+Der Verbindungsaufbau erfolgt per PPPoE. Dabei handeln beide Seiten
+die Protokolle aus, die sie verwenden möchten, und tauschen dabei
+Konfigurationen aus. Hierzu kommen IPCP (für natives IPv4)
+und IPv6CP (für IPv6) zum Einsatz.
+
+An einem IPv4 only-Anschluss sendet der Access Concentrator keine
+Anfrage zur IPv6CP-Aushandlung. Sendet der eigene Router eine,
+so gibt es eine LCP Protocol-Reject als Antwort. Dementsprechend
+können keine IPv6-Pakete ausgetauscht werden.
+
+Interessant ist, dass falsche Zugangsdaten zumindest vorübergehend
+zu Dual Stack führen. Davon ist mehrfach im Forum zu lesen
+und ich selbst konnte das an meinem Anschluss auch reproduzieren.
+Falsche Zugangsdaten führen nicht dazu, dass die Verbindung abgelehnt wird.
+Stattdessen bekommt man Dual Stack, wird allerdings von den Providerseitigen
+Firewalls nicht ins Internet gelassen. Unverschlüsseltes HTTP
+wird auf eine Fehlerseite umgeleitet.
+
+Dies zu testen ermöglicht es, die potenzielle Verfügbarkeit von IPv6
+an seinem Anschluss zu überprüfen.
+
+# IPv6 freischalten lassen
+
+Die Aktivierung von IPv6 kann man leider nicht selbst vornehmen.
+Hierzu kontaktiert man die Hotline unter `0800 172 1212`.
+Falls man gefragt wird, ob es um die Rufnummer geht, von der man anruft,
+antwortet man mit "Nein". Stattdessen wird die Kundennummer angegeben.
+Beim Thema wählt man zuerst "Anderes", anschließend "Vertrag"
+und dann "Technische Frage". Dann schildert man, dass aktuell nur IPv4
+im Kundenportal eingestellt ist, und dass man auf Dual Stack umgestellt
+werden möchte. Falls die Frage nach "öffentlich" oder "privat" gestellt wird,
+sollte man diese mit "öffentlich" beantworten. Ansonsten bekommt man DS-Lite,
+was allein schon aus Performance-Gründen meistens die schlechtere Wahl ist.
+
+# (Kundenseitige) Aktivierung
+
+Die Umstellung des Adressierungstyps erfolgt erst in der Nacht.
+Danach sollte an der zuvor genannten Stelle im Kundenportal
+unter Konfiguration der Wert "IPv6/v4 public" stehen, es sei denn, man hat sich
+für etwas anderes (z.B. "IPv6/v4 private" für DS-Lite) entschieden.
+
+Um diese Änderung tatsächlich anzuwenden, muss die Verbindung einmal neu
+aufgebaut werden. Im Zweifel geschieht dies durch einen manuellen
+Neustart des Routers. Selbstverständlich muss dazu natives IPv6 vom Router
+unterstützt werden und eingeschaltet sein.
+
+# Router-Konfiguration
+
+Bei gängigen Modellen sollten die Standardeinstellungen bereits ausreichen.
+Folgende Einstellungen sind für Bastlerfirmwares oder im Fehlerfall zu setzen:
+
+* DHCPv6 Rapid Commit: Ein
+* Bestimmte Präfixlänge anfordern: Ja, /56 (alternativ sollte auch Nein funktionieren)
+* DS-Lite: Aus (public) / Ein (private)
+* AFTR für DS-Lite: per DHCPv6 beziehen
+
+Rapid Commit ist vermutlich am wichtigsten. Ob auch ohne Rapid Commit
+eine Verbindung über IPv6 zustande kommt, habe ich nicht überprüft.
+
+# Was bekommt man?
+
+Es werden IPv6 Router Advertisements verwendet, um das Default-Gateway einzurichten.
+Allerdings ist das bei einem Point-to-Point-Tunnel eigentlich egal, da eine Route
+ohne Gateway über die PPP-Schnittstelle ebenso funktioniert.
+
+Per DHCPv6 wird maximal ein /56-Präfix und zwei DNS-Server zugewiesen.
+Bei DS-Lite gibt es zusätzlich einen AFTR.
+
+Ob das zugewiesene Präfix statisch ist, konnte ich noch nicht feststellen.
+Vermutlich ist dies nur bedingt der Fall.
+
+Bemerkenswert ist, dass keine einzelne WAN-Adresse an den Router selbst
+vergeben wird, anders als es bei Kabel-Internet der Fall ist.
+Dies lässt sich umgehen, indem man ein Subnetz der WAN-Schnittstelle zuweist.
+Ich verwende dazu das erste /64-Netz und nutze `::1` als Interface Identifier.
+Es sollte außerdem möglich sein, eine der LAN-Adressen des Routers
+für diesen Zweck mitzunutzen.
+
+# VoIP
+
+Anders als bei Kabel unterstützen die SIP-Registrars Dual Stack.
+Ist der Client (bzw. die Telefonanlage) auch dazu fähig, sollte man beides
+einrichten. In meinem Fall wird clientseitig nur IPv4 unterstützt,
+sodass sich nichts ändert. Sollte diese Möglichkeit eines Tages verschwinden,
+ist die Nutzung eines einfachen L4-Proxys ein funktionierender Workaround.
+
+[Return to Guide List](/cgi-bin/guides.lua)
+
+[Return to Index Page](/cgi-bin/index.lua)
diff --git a/src/guide/wifi103.md b/src/guide/wifi103.md
new file mode 100644
index 0000000..d929c28
--- /dev/null
+++ b/src/guide/wifi103.md
@@ -0,0 +1,64 @@
+% rtl8812au WiFi driver setup on RPi@5.10.103-v7l
+
+# The Problem
+WiFi drivers on Linux are already annoying enough, and it's gotten even worse
+with the 5.10.103 kernel. This version is no longer compatible with the
+[install-wifi script](http://downloads.fars-robotics.net/wifi-drivers/install-wifi).
+On top of that some versions of the rtl8812au driver I'm using drop IPv6 Multicast,
+breaking NDP and preventing you from automatically connecting to the IPv6 internet.
+Fortunately aircrack-ng maintains a working version of the driver. However it has
+to be compiled from source. Here's how.
+
+# Kernel Headers
+You may need to install the raspberry pi kernel headers.
+The apt package name is `raspberrypi-kernel-headers`.
+If you're using the 64-bit RPi OS, make sure to install
+the arm64 version of the package.
+Use `apt list raspberrypi-kernel-headers` to check if you have
+the correct version installed.
+
+# Installing
+Run the following shell commands. If you aren't using sudo, run commands that
+require root access in some other way.
+
+```sh
+sudo apt update && sudo apt install -y git dkms
+
+git clone https://github.com/aircrack-ng/rtl8812au.git
+cd rtl8812au/
+
+sed -i 's/CONFIG_PLATFORM_I386_PC = y/CONFIG_PLATFORM_I386_PC = n/g' Makefile
+sed -i 's/CONFIG_PLATFORM_ARM_RPI = n/CONFIG_PLATFORM_ARM_RPI = y/g' Makefile
+export ARCH=arm
+sed -i 's/^MAKE="/MAKE="ARCH=arm\ /' dkms.conf
+
+sudo make dkms_install
+```
+
+**For 64-bit, these are the commands to run:**
+
+```sh
+sudo apt update && sudo apt install -y git dkms
+
+git clone https://github.com/aircrack-ng/rtl8812au.git
+cd rtl8812au/
+
+sed -i 's/CONFIG_PLATFORM_I386_PC = y/CONFIG_PLATFORM_I386_PC = n/g' Makefile
+sed -i 's/CONFIG_PLATFORM_ARM64_RPI = n/CONFIG_PLATFORM_ARM64_RPI = y/g' Makefile
+export ARCH=arm64
+sed -i 's/^MAKE="/MAKE="ARCH=arm64\ /' dkms.conf
+
+sudo make dkms_install
+```
+
+If the last command gives an error because the DKMS module already exists,
+remove any existing installations of the driver.
+
+# Loading
+The driver should now automatically be loaded. It seems to be
+loaded at boot time automatically, but I haven't tested it yet.
+If you can confirm or disprove this please let me know.
+
+[Return to Guide List](/cgi-bin/guides.lua)
+
+[Return to Index Page](/cgi-bin/index.lua)
diff --git a/src/guides.md b/src/guides.md
new file mode 100644
index 0000000..8004b87
--- /dev/null
+++ b/src/guides.md
@@ -0,0 +1,17 @@
+% Guides
+
+These are setup guides that cover things I find interesting enough to share
+or that I've struggled with for a long time.
+
+[Return to Index Page](/cgi-bin/index.lua)
+
+# List
+* [rtl8812au on Raspberry Pi with kernel 5.10.103-v7l](/cgi-bin/guide/wifi103.lua)
+* [Kerberized NFS: How to fix "access denied by server"](/cgi-bin/guide/krbnfs.lua)
+* [OpenVPN IPv6](/cgi-bin/guide/ovpnip6.lua)
+* [Encrypting existing drives](/cgi-bin/guide/cryptexisting.lua)
+* [Write your own PPP(oE) client with kernel mode tunneling](/cgi-bin/guide/kppp.lua)
+* [Write your own ip-tunnel](/cgi-bin/guide/iproute2tun.lua)
+* [IPv6 mit Vodafone DSL](/cgi-bin/guide/vf6.lua) (DE)
+
+[Return to Index Page](/cgi-bin/index.lua)
diff --git a/src/index.md b/src/index.md
new file mode 100644
index 0000000..1a70855
--- /dev/null
+++ b/src/index.md
@@ -0,0 +1,153 @@
+% Hi, I'm Himbeer!
+
+# Introduction
+I'm a hobbyist programmer and sysadmin.
+I like networking, especially IPv6 (which is why this page is IPv6-only).
+Because of this I'm using my own networking hardware and software
+written in Rust. You can read more about it [here](/cgi-bin/rsdsl.lua).
+
+I created [mt-multiserver-proxy](/cgi-bin/work.lua?project=minetestproxy),
+a reverse proxy for the Minetest network protocol. It connects multiple
+Minetest servers together. Since Minetest does almost everything in a single
+thread this can help increase performance by taking advantage of multi-core
+CPUs.
+
+I also made some small web apps in the past that I'll eventually publish here.
+
+I use Artix, Debian and Raspberry Pi OS actively,
+but I'd probably be able to use some other distros if I wanted to.
+I've used Ubuntu and OpenWrt in the past.
+My window manager is
+[bspwm](https://github.com/baskerville/bspwm) with a custom setup.
+You can find everything
+[here](https://github.com/HimbeerserverDE/bspwm-setup).
+
+# Guides
+I occasionally upload setup guides for services that are difficult to
+understand, configure or maintain. They are listed [here](/cgi-bin/guides.lua).
+
+# PGP Keys
+
+* **2152D04DBFFE3567CD15F58BA3D3E205DA0B0401**: Commit signing. [download](/pgp/2152D04DBFFE3567CD15F58BA3D3E205DA0B0401.asc)
+* **F740BD951299D26B8B4FF28B2E6C9B91802EDCC4**: Public [email](mailto:himbeerserverde@gmail.com). [download](/pgp/F740BD951299D26B8B4FF28B2E6C9B91802EDCC4.asc)
+
+# Profiles
+## Code Hosting
+* **HimbeerGit**: [git.himbeerserver.de](https://git.himbeerserver.de) | Independent mirror. As of now GitHub is still preferred.
+* **GitHub:** [HimbeerserverDE](https://github.com/HimbeerserverDE)
+* **GitLab:** [HimbeerserverDE](https://gitlab.com/HimbeerserverDE)
+* **Bitbucket:** [HimbeerserverDE](https://bitbucket.org/HimbeerserverDE)
+* **MeseHub:** [HimbeerserverDE](https://git.minetest.land/HimbeerserverDE)
+
+## Preferred Communication Services
+* **Email (S/MIME or PGP supported):** [himbeerserverde@gmail.com](mailto:himbeerserverde@gmail.com)
+* **Matrix:** [\@himbeer:himbeerserver.de](https://matrix.to/#/@himbeer:himbeerserver.de)
+
+## Social
+* **Discord:** Himbeer#3585 | **IGNORED AND LIKELY TO BE DELETED.** Switch to [Matrix](https://matrix.to/#/@himbeer:himbeerserver.de)
+* **YouTube:** [HimbeerserverDE](https://www.youtube.com/channel/UCRuSC9WNapuA4Gm-kU_gjGA)
+* **IRC (libera.chat):** HimbeerserverDE
+* **IRC (oftc.net):** HimbeerserverDE
+* **Minetest (forum, in-game, ContentDB):** HimbeerserverDE
+
+# Work
+_If you have any suggestions on what to put on this list please
+[contact me](#profiles)!_
+
+* [mt-multiserver-proxy](/cgi-bin/work.lua?project=minetestproxy), a reverse
+proxy for Minetest
+* [mt-multiserver-chatcommands](/cgi-bin/work.lua?project=minetestproxy#commands),
+a plugin providing standard chat commands for mt-multiserver-proxy
+* [A simple online password generator](/cgi-bin/password_generator.lua)
+* [This website](/cgi-bin/work.lua?project=www3)
+* [Rustkrazy](/cgi-bin/rustkrazy.lua), an ecosystem for developing minimalistic
+Linux-based pure Rust appliances
+* [rsdsl](/cgi-bin/rsdsl.lua), a Vodafone DSL router written from scratch in Rust
+
+## Minetest mods
+* [dynamic_liquid](/cgi-bin/work.lua?project=dynamicliquid), a fork of
+[minetest-mods/dynamic_liquid](https://github.com/minetest-mods/dynamic_liquid)
+* [waterworks](/cgi-bin/work.lua?project=waterworks), a fork of
+[FaceDeer/waterworks](https://github.com/FaceDeer/waterworks) that actually
+does what the documentation says
+
+# Skills
+This is a list of the languages and tools I'm able to use at the moment.
+
+## Languages
+* C
+* C++
+* Go
+* HTML
+* CSS
+* JavaScript
+* Lua
+* Java
+* Bash
+* PHP
+* SQL
+* Rust
+* Python
+
+## Software
+
+### Server
+* Apache
+* nginx
+* SQLite3
+* MySQL
+* MariaDB
+* PostgreSQL
+* fail2ban
+* slapd
+* Kerberos
+* DNSMASQ
+* WIDE-DHCPv6-Client
+* dibbler-server
+* LWDS-Lite
+* OpenVPN
+* FOG
+* PXE
+* iPXE
+* LTSP
+* NFS
+
+### Client
+* ldap-utils
+* sssd
+* PAM
+* GRUB2
+* GCC
+* G++
+* GNU Make
+* CMake
+* GDB
+* GIMP
+* Blender
+* OpenShot
+* NetworkManager
+* Kerberos
+* NFS
+
+## APIs / Libraries / Frameworks
+* node.js
+* jQuery
+* OpenGL core v3.0+
+* GLFW
+* linmath.h
+* Minetest
+* Flask
+
+# Friends
+This is a list of my friends with links to their main online presence
+if they have one.
+
+* [Fleckenstein](https://lizzy.rs)
+* [DerZombiiie](https://derzombiiie.com)
+* [j45](https://j1233.minetest.land)
+* [TheodorSmall](https://github.com/TheodorSmall)
+* [SC++](https://github.com/scplusplus)
+* [Rapunzel](https://github.com/RapunzelE)
+* [anon5](https://github.com/anon55555)
+* Typischer
+* yayyer
diff --git a/src/password_generator.md b/src/password_generator.md
new file mode 100644
index 0000000..1cc7761
--- /dev/null
+++ b/src/password_generator.md
@@ -0,0 +1,54 @@
+% Password Generator
+
+This page generates a few passwords *on the server* and displays them to the user.
+The code can be found [on GitHub](https://github.com/HimbeerserverDE/www/blob/master/cgi-bin/password_generator.lua).
+
+# Security issue
+
+**This generator is extremely insecure.**
+
+For convenience reasons the generator internally uses Lua's `math.random`
+and seeds it with cryptographically secure random data.
+
+It gets this data by reading 64 bytes from `/dev/random`
+and adding their ASCII codes together in a loop.
+In this step the number of possible seeds is reduced
+from `256^64` to just `256*64`.
+
+It is trivial to use this knowledge to generate all possible seeds
+and the passwords generated from them.
+This only takes about a second even on my slow machine. The list
+can then be used in a dictionary attack.
+
+**DO NOT USE THIS! A proper generator like the one in KeePassXC
+is a much more secure and convenient option!**
+
+# 32 Letters, digits, punctuation characters
+* `${strongest1}`
+* `${strongest2}`
+* `${strongest3}`
+* `${strongest4}`
+* `${strongest5}`
+
+# 32 Letters, digits
+* `${strong1}`
+* `${strong2}`
+* `${strong3}`
+* `${strong4}`
+* `${strong5}`
+
+# 32 Letters
+* `${medium1}`
+* `${medium2}`
+* `${medium3}`
+* `${medium4}`
+* `${medium5}`
+
+# 16 Letters, digits
+* `${weak1}`
+* `${weak2}`
+* `${weak3}`
+* `${weak4}`
+* `${weak5}`
+
+[Return to Index Page](/cgi-bin/index.lua)
diff --git a/src/rsdsl.md b/src/rsdsl.md
new file mode 100644
index 0000000..f38c524
--- /dev/null
+++ b/src/rsdsl.md
@@ -0,0 +1,375 @@
+% The rsdsl Project
+
+# About
+The rsdsl project is a collection of Rust programs
+that form a customized, yet simple router for Vodafone Germany DSL connections.
+It is designed to run on [Rustkrazy](/cgi-bin/rustkrazy.lua).
+
+# Repositories
+Up-to-date versions of all components and some common or forked libraries
+can be found on [my own git server](https://git.himbeerserver.de/?a=project_list;pf=rsdsl)
+or on [GitHub](https://github.com/rsdsl).
+
+# Platforms
+All Rustkrazy platforms should be supported,
+but testing is currently limited to the Raspberry Pi 3B.
+
+# Why
+You may wonder why one would rewrite an entire router
+when there are existing solutions for it.
+OpenWrt is one such option. I had been using it for months
+without any major issues. However the majority of its components
+are written in C, a memory unsafe programming language.
+It is also quite complex for what it does.
+On top of this writing custom protocol implementations is a lot of fun.
+
+The network structure and ISP were about to change anyway.
+This is why I decided to build rsdsl for the new network.
+
+# Hardware
+The LAN side of the router is connected via its builtin Ethernet interface.
+It is connected to a VLAN capable switch
+and tagged VLAN (802.1q) is enabled on the port.
+
+The WAN connection is done using a USB to Ethernet dongle
+that connects to a dedicated DSL modem.
+The modem takes care of tagging the packets with VLAN ID 7.
+
+# Operation diagram
+Here's the core concept of how the components work together.
+
+```
++-------+ +------+ +--------+
+| ISP | | dnsd | <---> | dhcp4d |
++-------+ +------+ +--------+
+ ^
+ |
+ | ip6 ll +---------+ aftr +--------+
+ | +--------->| dhcp6 |--------->| dslite |
+ | | +---------+ +--------+
+ | | | ^
+ V | V ip6 prefix |
++--------+ ip4 +----------+ ip4 conf |
+| pppoe2 | ----> | netlinkd |<------------+
++--------+ conf +----------+
+ | ^ |
+ V ip4 addr | |
++------+ | |
+| 6in4 | ----------+ | ip6 conf
++------+ |
+ ^ |
+ | V
++-----+ +-------+ +------------+
+| ntp | | radvd | | netfilterd |
++-----+ +-------+ +------------+
+```
+
+# Components
+To make the router work a small number of components are needed.
+These are either background services or oneshot setup commands.
+Some of them are optional depending on the environment the router is used in
+and personal preference.
+
+## pppoe2
+This is the second most important program running on the system.
+It is what connects to the outside world.
+To do so it utilizes the PPPoE standard, only implementing a bare minimum.
+PPP has a wide variety of authentication protocols to choose from, out of which
+this implementation supports CHAP-MD5 and PAP.
+With this ISP only CHAP is used in practice.
+If the session disconnects or can't be established in the first place
+the client will retry until it succeeds.
+It uses IPCP to establish a native IPv4 connection to the provider.
+A link-local native IPv6 connection is established using IPv6CP.
+
+Once connected the service creates a virtual network interface called `ppp0`.
+It lets the kernel handle the PPPoE encapsulation which surprisingly doesn't
+provide a significant performance boost compared to the old unoptimized
+userspace implementation. It caps out at about 90 megabits per second
+with an average of 80. This is acceptable for a 100/40 plan.
+
+The client does not configure the interface or even bring it up on its own.
+This is the responsibility of netlinkd. It does however write
+the IP(v6)CP results to `/tmp/pppoe.ip_config` in a JSON format.
+This includes the assigned address as well as the primary
+and secondary DNS servers. The IPv6 information also contains
+the link-local address of the ISP peer. Both sections are optional.
+
+To establish the connection the client uses a JSON configuration file
+located at `/data/pppoe.conf` containing the username and password
+for the connection. This is backwards compatible to the old config
+that contained the physical interface to run on.
+This field is now simply ignored.
+
+## netlinkd
+This is easily the most essential part of the entire project.
+It is responsible for configuring the network interfaces, routing
+and other network related settings. Most of them require communication
+with the kernel via the netlink protocol (the route protocol to be exact)
+which is what the `ip` command from iproute2 uses.
+
+It first configures the LAN interface and VLANs with a static IPv4 address each.
+In addition to this all LAN side networks are configured with the static
+link-local IPv6 address `fe80::1/64`. The kernel can potentially configure
+an additional EUI-64 based link-local address.
+It then waits for the WAN config to become available in the aforementioned
+`/tmp/pppoe.ip_config` file. It also monitors the file for changes
+in case `pppoe2` reconnects. The public IPv4 address usually changes
+when reconnecting but it's somewhat inconsistent.
+The local IPv6 link-local address of the WAN connection is always random
+for the sake of simplicity, but making it static wouldn't provide any benefits
+anyway.
+
+The WAN interface is then configured with the public IPv4 address as a /32,
+assuming there is native IPv4 connectivity.
+This is because unlike DHCP the IPCP does not provide subnet mask information.
+Therefore we must not assume that there even is a subnet.
+In recent versions the routing has become much simpler.
+A single default route sends all traffic down the `ppp0` interface
+without a gateway. This is a point-to-point link after all.
+
+If native IPv6 is available the link-local address if configured
+as a /64. A default route is also created. Just like the IPv4 route
+the IPv6 route doesn't use a gateway and instead relies on the interface alone.
+
+Once `dhcp6` provides or updates its lease `netlinkd` picks it up
+and subnets the prefix to as many /64s as it needs.
+This is done sequentially rather than using the VLAN ID (or zero)
+as the subnet ID, allowing small prefixes (e.g. /61) to work
+but I'd like to switch `6in4` and IPv4 to the same layout for consistency.
+The subnets are then assigned to the interfaces, reserving the first one
+for the WAN side (instead of respecting a PD exclude hint if present).
+
+The router has no other way to get clean IPv6 connectivity for itself
+since neither SLAAC nor DHCPv6 can be used to obtain a single WAN address.
+It could use one of its LAN side addresses but having a dedicated WAN address
+is cleaner and very handy when implementing DS-Lite.
+
+Recent versions allow devices behind the router to access the DSL modem
+at `192.168.1.1`.
+This was made possible by adding the IPv4 address `192.168.1.2/24`
+to the `eth1` interface and setting up NAT for it.
+Yes, I'm just as surprised about this subnet choice as you are, but it works.
+This extra configuration step is necessary with DSL modems since they can't
+see inside the PPPoE session. They use the underlying Ethernet link
+for administrative access.
+
+## netfilterd
+Netfilter is a kernel system for packet filtering, logging, mangling and more.
+It's basically the Linux firewall. The popular `iptables` and `nftables` tools
+use it as their backend.
+Packet filtering is not strictly required for the internet connection to work,
+but with IPv4 NAT is. Having this component also allows for port forwarding rules
+to exist. On top of this it is used to secure the internal networks
+from the outside world and from each other with some exceptions.
+See the [source code](https://github.com/rsdsl/netfilterd/blob/master/src/main.rs)
+for the exact ruleset.
+
+Notably ICMP and ICMPv6 are always allowed for debugging purposes.
+IPv4 NAT is done on outbound traffic as normal.
+This does not apply to DS-Lite traffic since the AFTR is perfectly capable of
+handling NAT for us as per RFC 6333. Avoiding double NAT helps reduce
+the overhead introduced by such a carrier-grade NAT solution
+and is comparable to IPv6 privacy wise.
+
+This alone does make the IPv4 internet reachable
+but some services still won't work.
+The reason is the MTU of PPPoE connections. Since PPP(oE) is a tunneling
+protocol there is some overhead that reduces the effective maximum packet size.
+In this case it is reduced from 1500 (Ethernet maximum) to 1492.
+The clients do not know about this and will happily set the TCP MSS
+which is dependent on the MTU and tells the peer the packet size
+we can deal with to its maximum value. The MSS is generally 40 bytes smaller
+than the MTU, leading to a maximum of 1460. This is too much for our WAN tunnel
+to handle though. If the server sends a huge response the ISP will have to
+drop it. For whatever reason automatic path MTU discovery (PMTUD) doesn't work.
+
+My first idea to solve this was to tell clients about the lower MTU using DHCP.
+This worked on some devices but Apple in particular ignores this option.
+For this reason I removed the MTU option from DHCP and implemented
+a hack called "MSS clamping". This is a firewall rule that changes
+any TCP packets by setting their MSS to the minimum of the MTU of the
+interface the packet came from and the one it is routed to.
+Now all services should be reachable.
+
+This MSS clamping is applied to other WAN interfaces as well
+so DS-Lite and 6in4 should work flawlessly too.
+
+## dhcp4d
+Network clients need a way to configure themselves. Some devices don't support
+manual configuration. Even if they did it's annoying to manage
+and the subnetting scheme can't easily be changed later.
+This DHCPv4 server hands out IPv4 addresses of the correct subnet
+with a lease time of 12 hours. The leases are stored
+at `/data/dhcp4d.leases_INTERFACE` in a JSON format.
+If a lease file gets corrupted for any reason it is discarded
+and overwritten with the current state from memory
+or an empty structure if there is none.
+The server keeps track of client IDs and hostnames if sent.
+These are stored in their corresponding lease data structure.
+
+The addresses are generated based on the client ID. For this reason
+the same client will usually end up with the same address every time
+even if it expired. Collisions are handled by generating a new address
+until no collisions are left.
+
+The only gateway and DNS server advertised is the router itself.
+
+## dhcp6
+A link-local IPv6 connection over PPP is of no use on its own.
+The router needs a prefix for downstream clients as well as a means of
+discovering a DS-Lite AFTR. This is where DHCPv6 comes into play.
+
+If a native IPv6 connection has been established this service requests
+a prefix delegation of size /56, the DNS servers and the AFTR name.
+The result is written to `/tmp/dhcp6.lease` in a simple JSON format
+but only two DNS servers are included in the file.
+The valid and preferred lifetimes are stored as well but not used
+as of now. The AFTR is optional.
+
+The client automatically renews the lease after the time sent by the server
+has passed. If this fails it starts over.
+
+## dnsd
+Having an internal DNS resolver is not required but it does have one major
+advantage. It is aware of the DHCP leases. Broken lease files are ignored,
+leaving the task of fixing them to `dhcp4d`.
+If an A record is queried the resolver checks the lease database in memory
+for the hostname. If it finds a lease it reports it back to the client
+without ever forwarding the request to the hardcoded upstream server.
+The ISP provided nameservers are ignored for simplicity reasons.
+This program is fully capable of resolving AAAA records
+and accepting IPv6 packets. The network design makes it impossible
+to resolve local AAAA records though.
+It also results in NXDOMAIN errors below the IPv4 address in nslookups
+if no IPv6 addresses exist for a given hostname.
+Amazingly it doesn't add any measurable latency.
+If a local hostname is present on multiple interfaces,
+the interface the request originated from is prioritised
+when choosing the response.
+
+## dslite
+Dual Stack Lite is a common transition mechanism found in Germany.
+It is used to provide IPv4 connectivity to IPv6-only customers
+using carrier-grade NAT. Unfortunately this comes with many disadvantages
+related to performance and accessing the network via IPv4.
+
+The rsdsl project comes with a DS-Lite implementation to ensure
+the availability of IPv4. Without it most services won't work.
+
+DS-Lite is always configured if available but a default route is only created
+if there is no native IPv4 connection.
+
+The AFTR is discovered using DHCPv6. The lease file contains the FQDN.
+If this is unset no tunnel is configured.
+DHCPv6 is not capable of providing a resolved IPv6 address.
+The client uses the IPv6 DNS servers provided by the ISP
+to resolve the FQDN and use it as the remote tunnel endpoint.
+The ISP servers are used because the hostname may not be available
+to public resolvers.
+
+The tunnel is a simple 4in6 tunnel using the WAN side IPv6 address
+as its local endpoint. The internal address is `192.0.0.2/29`
+and the default route uses `192.0.0.1` as the gateway.
+
+### VoIP
+Many ISPs provide phone service. Nowadays VoIP is very common for this.
+My previous ISP did not have IPv6 capable VoIP gateways.
+This has changed. The servers of the new ISP support both protocols.
+
+This is a problem if your client is still stuck on IPv4. The particular one
+I'm using is the Cisco SPA112 hardware appliance. A firmware update
+adding IPv6 support was promised but the product has reached its end of life
+without ever receiving such an update.
+
+Once native IPv4 is lost there will be connectivity issues
+affecting at least inbound calls. A possible solution is to run a simple proxy
+on the router that translates the port ranges for SIP and RTP to the WAN side
+IPv6 address of the router, essentially forming a bidirectional port mapper.
+Of course any other form of dual-stack capable proxy should work as well.
+
+Or maybe the ISP is going to assign private IPv4 addresses for this purpose?
+This is unlikely but it would also work. A solution on the AFTR side
+would likely be possible too.
+
+## ntp
+Some services on the router (namely `6in4`) require a somewhat accurate
+system clock in order to establish encrypted connections.
+This simple NTP client waits for the PPPoE connection to come up
+and makes up to 3 attempts to get the time from a basic NTP server
+(no support for NTPv4 or SNTP). The fractional part is ignored for simplicity.
+If the unix epoch and the actual time are mixed in your logs this is why.
+
+## 6in4
+Since the ISP doesn't offer native IPv6 this service
+configures the local endpoint of a
+Hurricane Electric / [tunnelbroker.net](https://tunnelbroker.net) tunnel.
+The configuration has to be provided to it at `/data/he6in4.conf`
+in a simple JSON format. It needs to contain the tunnel server
+IPv4 address, the peering /64 from the configuration page,
+the routed /64, the routed /48 (this is NOT optional) as well as
+the update URL for the endpoint address.
+
+This program only works if native IPv4 is available.
+If native IPv6 is available it still establishes the tunnel
+but doesn't add a default route. This hopefully won't break anything.
+We'll see once native IPv6 becomes available here.
+I'd recommend against using this if native connectivity is provided
+by your ISP.
+
+On startup a virtual tunnel interface named `he6in4` is created
+and configured. In addition to this the LAN interfaces including VLANs
+are automatically configured with predictable subnets
+and the first IPv6 address from their subnet.
+The subnets are assigned from the routed /48 with the exception of
+the unVLANed LAN interface which is additionally assigned the
+routed /64.
+The tunneling once again takes place in kernel mode
+and is unsurprisingly more efficient than PPPoE.
+However this is negated by the fact that many tunnel servers
+have terrible or unstable latency.
+
+The MTU of the tunnel is limited to 1472 instead of 1480 for the reasons
+mentioned above. This needs to be set in the tunnelbroker configuration menu.
+
+This program also takes care of calling the update URL
+to inform HE of our current IPv4 address. It needs DNS resolution (see code)
+and NTP to work.
+
+## radvd
+Just like with IPv4 our client hosts need a way to get the IPv6 configuration.
+The most compatible and convenient way of achieving this is through SLAAC.
+This service periodically multicasts Router Advertisements (RA)
+to the local networks, advertising itself as the gateway and DNS server.
+It also sends a multicast if it receives a Router Solicitation (RS)
+from a connecting host. Unicast is not used here out of laziness.
+
+# Interesting observations
+## IPv4 dynamicness
+The public IPv4 address doesn't seem to change if the connection
+is re-established as quickly as possible. This is not reliable.
+Most of the time it changes if reconnecting takes more than a few seconds.
+Again this is behavior that cannot be relied on.
+When it changes all octets completely change in most cases. In most other cases
+only the last octet is changed.
+
+I don't know about IPv6 yet.
+
+## IPv6 support
+Dialing PPPoE with invalid credentials yields a public /56 IPv6 prefix
+and a default gateway. However this is of no use as the ISP blocks all traffic
+due to the invalid credentials. Using the correct credentials
+results in a Protocol-Reject on IPv6CP negotiation if attempted
+and the AC doesn't send a Configure-Request of its own.
+
+The ISP's web portal says `Configuration: IPv4`. Apparently other values
+include `Dual Stack` and `DS Lite`, though they seem to be quite rare.
+According to the forums even the employees don't know what's going to be used
+for new contracts, but `Dual Stack` is never the default.
+
+Since the AC I'm connected to seems to be IPv6 capable I'm going to ask
+for Dual Stack to be enabled. Just to be safe I implemented DS-Lite.
+You never know if they stick to your orders and being prepared is certainly
+better than suddenly being unable to reach half of the internet.
+
+[Return to Index Page](/cgi-bin/index.lua)
diff --git a/src/rustkrazy.md b/src/rustkrazy.md
new file mode 100644
index 0000000..24a312e
--- /dev/null
+++ b/src/rustkrazy.md
@@ -0,0 +1,120 @@
+% The Rustkrazy Project
+
+# About
+Rustkrazy is heavily inspired by [Gokrazy](https://gokrazy.org).
+It can turn your Rust programs into appliances.
+
+# Repositories
+The repositories on [my own git server](https://git.himbeerserver.de/?a=project_list;pf=rustkrazy)
+and on [GitHub](https://github.com/rustkrazy) are kept up-to-date.
+
+# Why
+Just like Gokrazy this project can help with eliminating
+the annoyances of administrating unsafe C software.
+The simplicity of the system and reduction of components
+to a bare minimum also makes maintenance and updating
+much safer and easier.
+
+# Supported Platforms
+Officially supported and tested platforms:
+
+* x86_64 (tested: QEMU)
+* RPi 64-bit (tested: RPi 3B, RPi 4B)
+
+# How it works
+A Rustkrazy image consists of 4 MBR partitions:
+
+* /boot: 256 MiB FAT32 (LBA), contains kernel, cmdline, RPi config.txt, dtbs
+and RPi firmware
+* /: 256 MiB SquashFS, rootfs A, contains mountpoints, init and user-defined programs
+* /: 256 MiB SquashFS, rootfs B, contains mountpoints, init and user-defined programs
+* /data: remaining ext4, persistent writable data storage and config for the applications
+
+The MBR is a simple x86 single-stage bootloader that directly accesses specific
+sectors of the boot partition. Raspberry Pis use the firmware files instead.
+
+The A/B partitioning scheme allows for a safe update mechanism to be implemented.
+
+# Usage
+Please refer to the respective repositories of the components
+for their documentation. The image related executables have command-line help.
+
+The packer is used to generate images from scratch.
+The updater modifies an existing installation over the network.
+Regular users won't have to interact with any of the other tools.
+
+# The /boot Partition
+Since we want a usable environment we need a kernel.
+The Linux kernel is a good choice for this.
+It resides on the boot partition of the image.
+The `packer` and `updater` commands download pre-compiled binaries
+from the `kernel` repository to save time.
+The upstream Linux kernel from [kernel.org](https://kernel.org) is used.
+It is booted with the parameters listed in `/boot/cmdline.txt`
+regardless of the platform.
+Depending on the platform it's either the MBR or the firmware files
+that make booting it possible. On Raspberry Pis it additionally requires
+the dtbs that are automatically added to the image.
+This filesystem is writable by the running system.
+
+# Root A/B
+There are two root partitions. Only one of them can be active at the same time.
+New images use partition A as the active rootfs by default.
+Updates using the new `admind` push the new binaries to the inactive root partition
+and switch the system to it. This is applied by an automatic reboot.
+If anything breaks you can simply switch back to the other partition
+to use the old version of your software and overwrite the broken one with your fix.
+
+This not only makes safe auto-updates possible. It makes them feasible.
+The kernel and firmware are updated to the latest pre-compiled version in the process.
+
+# The / Partitions
+They contain the mountpoints for other filesystems like /proc, /sys, /dev, /tmp
+and /boot. Their /bin directory is home to the user-defined binaries and the init.
+
+# The /data Partition
+This is the perfect place for non-volatile storage of program information.
+If a program cannot work without a configuration file this is where to put it.
+It is never touched by system updates, but the different program versions
+should be able to deal with their own file formats correctly.
+
+# Inits
+When building an image you need to specify an init.
+For simple single-program images it is often enough to use the program
+as the init unless you need access to (special) filesystems.
+Otherwise you need a dedicated init. It is always moved to /bin/init
+by the image generators. Rustkrazy offers its own
+init system implementation but there is no default and you're free
+to use whatever you want. The rustkrazy init takes care of mounting
+any interesting filesystems not including external media
+and restarts services as soon as they exit. It considers all files
+in /bin excluding /bin/init (itself) a service.
+
+# Git crate names
+Cargo needs to know the exact names of the crates to install them.
+With crate registries like crates.io this isn't a problem.
+However there is no easy way to discover the crate name
+from a git URL and it's possible for more than one to exist
+per repository.
+It is for this reason that the URLs may be succeeded by a percentage sign
+and the actual crate name, e.g. `https://github.com/rustkrazy/init.git%rustkrazy_init`.
+If omitted the image manipulation commands default to the repository name
+which is "init" in our example.
+
+# Security
+All processes on the system run as root by default.
+For the time being please consider implementing your own account system
+if you need more security.
+
+# admind
+The admind provides an authenticated management API over HTTPS.
+It can be used to remotely flash instances using the `updater` program.
+It also adds support for remote rebooting, shutdown, switching root partitions,
+manually flashing the block device or partitions, reading files and writing files.
+Reading files is especially useful since the `rustkrazy_init`
+writes program logs and stderr to `/tmp/SERVICE.log` and `/tmp/SERVICE.err`
+respectively (separated for simplicity of code) where `SERVICE`
+is the binary file name. This API allows you to remotely access the logs
+if you need to.
+
+[Return to Index Page](/cgi-bin/index.lua)