diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/guide/cryptexisting.md | 152 | ||||
-rw-r--r-- | src/guide/iproute2tun.md | 132 | ||||
-rw-r--r-- | src/guide/kppp.md | 376 | ||||
-rw-r--r-- | src/guide/krbnfs.md | 124 | ||||
-rw-r--r-- | src/guide/ovpnip6.md | 69 | ||||
-rw-r--r-- | src/guide/vf6.md | 122 | ||||
-rw-r--r-- | src/guide/wifi103.md | 64 | ||||
-rw-r--r-- | src/guides.md | 17 | ||||
-rw-r--r-- | src/index.md | 153 | ||||
-rw-r--r-- | src/password_generator.md | 54 | ||||
-rw-r--r-- | src/rsdsl.md | 375 | ||||
-rw-r--r-- | src/rustkrazy.md | 120 |
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) |