development.rst (15164B)
1 Development 2 ########### 3 4 As a developer working on Liminix, or implementing a service or 5 module, you probably want to test your changes more conveniently 6 than by building and flashing a new image every time. This section 7 documents various affordances for iteration and experiments. 8 9 In general, packages and tools that run on the "build" machine are 10 available in the ``buildEnv`` derivation and can most easily 11 be added to your environment by running :command:`nix-shell`. 12 13 14 15 Emulated devices 16 **************** 17 18 Liminix has a ``qemu`` device, which generates images suitable for 19 running on your build machine using the free `QEMU machine emulator <http://www.qemu.org>`_. 20 This is useful for developing userland without needing to keep 21 flashing or messing with U-Boot: it also enables testing against 22 emulated network peers using `QEMU socket networking <https://wiki.qemu.org/Documentation/Networking#Socket>`_, 23 which may be preferable to letting Liminix loose on your actual LAN. 24 To build it, 25 26 .. code-block:: console 27 28 nix-build -I liminix-config=path/to/your/configuration.nix --arg device "import ./devices/qemu" -A outputs.default 29 30 This creates a :file:`result/` directory containing a :file:`vmlinux` 31 and a :file:`rootfs`, and also a shell script :file:`run.sh` which 32 invokes QEMU to run that kernel with that filesystem. It connects the Liminix 33 serial console and the `QEMU monitor <https://www.qemu.org/docs/master/system/monitor.html>`_ to stdin/stdout. Use ^P (not ^A) to switch to the monitor. 34 35 If you run with ``--background /path/to/some/directory`` as the first 36 parameter, it will fork into the background and open Unix sockets in 37 that directory for console and monitor. Use :command:`nix-shell --run 38 connect-vm` to connect to either of these sockets, and ^O to 39 disconnect. 40 41 .. _qemu-networking: 42 43 Networking 44 ========== 45 46 VMs can network with each other using QEMU 47 socket networking. We observe these conventions, so that we can run 48 multiple emulated instances and have them wired up to each other in 49 the right way: 50 51 * multicast 230.0.0.1:1234 : access (interconnect between router and "isp") 52 * multicast 230.0.0.1:1235 : lan 53 * multicast 230.0.0.1:1236 : world (the internet) 54 55 Any VM started by a :command:`run.sh` script is connected to "lan" and 56 "access", and the emulated border network gateway (see below) runs 57 PPPoE and is connected to "access" and "world". 58 59 .. _border-network-gateway: 60 61 Border Network Gateway 62 ---------------------- 63 64 In pkgs/routeros there is a derivation to install and configure 65 `Mikrotik RouterOS <https://mikrotik.com/software>`_ as a PPPoE access 66 concentrator connected to the ``access`` and ``world`` networks, so that 67 Liminix PPPoE client support can be tested without actual hardware. 68 69 This is made available as the :command:`routeros` command in 70 ``buildEnv``, so you can do something like:: 71 72 mkdir ros-sockets 73 nix-shell 74 nix-shell$ routeros ros-sockets 75 nix-shell$ connect-vm ./ros-sockets/console 76 77 to start it and connect to it. Note that by default it runs in the 78 background. It is connected to "access" and "world" virtual networks 79 and runs a PPPoE service on "access" - so a Liminix VM with a 80 PPPOE client can connect to it and thus reach the virtual internet. 81 [ check, but pretty sure this is not the actual internet ] 82 83 `Liminix does not provide RouterOS licences and it is your own 84 responsibility if you use this to ensure you're compliant with the 85 terms of Mikrotik's licencing. It may be supplemented or replaced in 86 time with configurations for RP-PPPoE and/or Accel PPP.` 87 88 Hardware devices 89 **************** 90 91 .. _serial: 92 93 U-Boot and serial shenanigans 94 ============================= 95 96 Every device that we have so far encountered in Liminix uses `U-Boot, 97 the "Universal Boot Loader" <https://docs.u-boot.org/en/latest/>`_ so 98 it's worth knowing a bit about it. "Universal" is in this context a 99 bit of a misnomer, though: encountering *mainline* U-Boot is very rare 100 and often you'll find it is a fork from some version last updated 101 in 2008. Upgrading U-Boot is more or less complicated depending on the 102 device and is outside scope for Liminix. 103 104 To speak to U-Boot on your device you'll usually need a serial 105 connection to it. This is device-specific. Usually it involves 106 opening the box, locating the serial header pins (TX, RX and GND) and 107 connecting a USB TTL converter to them. 108 109 The Rolls Royce of USB/UART cables is the `FTDI cable 110 <https://cpc.farnell.com/ftdi/ttl-232r-rpi/cable-debug-ttl-232-usb-rpi/dp/SC12825?st=usb%20to%20uart%20cable>`_, 111 but there are cheaper alternatives based on the PL2303 and CP2102 chipsets. Or 112 get creative and use the `UART GPIO pins <https://pinout.xyz/>`_ on a Raspberry Pi. Whatever you do, make sure 113 that the voltages are compatible: if your device is 3.3V (this is 114 typical but not universal), you don't want to be sending it 5v or 115 (even worse) 12v. 116 117 Run a terminal emulator such as Minicom on the computer at other end 118 of the link. 115200 8N1 is the typical speed. 119 120 .. NOTE:: 121 122 TTL serial connections typically have no form of flow control and 123 so don't always like having massive chunks of text pasted into 124 them - and U-Boot may drop characters while it's busy. So don't 125 necessarily expect to copy-paste large chunks of text into the 126 terminal emulator and have it work just like that. 127 128 If using Minicom, you may find it helps to bring up the "Termimal 129 settings" dialog (C^A T), then configure "Newline tx delay" to 130 some small but non-zero value. 131 132 When you turn the router on you should be greeted with some messages 133 from U-Boot, followed by the instruction to hit some key to stop 134 autoboot. Do this and you will get to the prompt. If you didn't see 135 anything, the strong likelihood is that TX and RX are the wrong way 136 around. If you see garbage, try a different speed. 137 138 Interesting commands to try first in U-Boot are :command:`help` and 139 :command:`printenv`. 140 141 To do anything useful with U-Boot you will probably need a way to get 142 large binary files onto the device, and the usual way to do this is by 143 adding a network connection and using TFTP to download them. It's 144 quite common that the device's U-Boot doesn't speak DHCP so it will 145 need a static LAN address. You might also want to keep it away from 146 your "real" LAN: see :ref:`bng` for some potentially useful tooling 147 to use it on an isolated network. 148 149 150 TFTP 151 ==== 152 153 .. _tftp server: 154 155 How you get your image onto hardware will vary according to the 156 device, but is likely to involve taking it apart to add wires to 157 serial console pads/headers, then using U-Boot to fetch images over 158 TFTP. The OpenWrt documentation has a `good explanation <https://openwrt.org/docs/techref/hardware/port.serial>`_ of what you may expect to find on 159 the device. 160 161 There is a rudimentary TFTP server bundled with the system which runs 162 from the command line, has an allowlist for client connections, and 163 follows symlinks, so you can have your device download images direct 164 from the :file:`./result` directory without exposing :file:`/nix/store/` to the 165 internet or mucking about copying files to :file:`/tftproot`. If the 166 permitted device is to be given the IP address 192.168.8.251 you might 167 do something like this: 168 169 .. code-block:: console 170 171 nix-shell --run "tufted -a 192.168.8.251 result" 172 173 Now add the device and server IP addresses to your configuration: 174 175 .. code-block:: nix 176 177 boot.tftp = { 178 serverip = "192.168.8.111"; 179 ipaddr = "192.168.8.251"; 180 }; 181 182 and then build the derivation for ``outputs.default`` or 183 ``outputs.mtdimage`` (for which it will be an alias on any device 184 where this is applicable). You should find it has created 185 186 * :file:`result/firmware.bin` which is the file you are going to flash 187 * :file:`result/flash.scr` which is a set of instructions to U-Boot to 188 download the image and write it to flash after erasing the appropriate 189 flash partition. 190 191 .. NOTE:: 192 193 TTL serial connections typically have no form of flow control and 194 so don't always like having massive chunks of text pasted into 195 them - and U-Boot may drop characters while it's busy. So don't 196 necessarily expect to copy-paste the whole of :file:`boot.scr` into 197 a terminal emulator and have it work just like that. You may need 198 to paste each line one at a time, or even retype it. 199 200 201 For a faster edit-compile-test cycle, you can build a TFTP-bootable 202 image instead of flashing. In your device configuration add 203 204 .. code-block:: nix 205 206 imports = [ 207 ./modules/tftpboot.nix 208 ]; 209 210 and then build ``outputs.tftpboot``. This creates a file in 211 ``result/`` called ``boot.scr``, which you can copy and paste into 212 U-Boot to transfer the kernel and filesystem over TFTP and boot the 213 kernel from RAM. 214 215 216 .. _bng: 217 218 Networking 219 ========== 220 221 You probably don't want to be testing a device that might serve DHCP, 222 DNS and routing protocols on the same LAN as you (or your colleagues, 223 employees, or family) are using for anything else, because it will 224 interfere. You also might want to test the device against an 225 "upstream" connection without having to unplug your regular home 226 router from the internet so you can borrow the cable/fibre/DSL. 227 228 ``bordervm`` is included for this purpose. You will need 229 230 * a Linux machine with a spare (PCI or USB) ethernet device which you can dedicate to Liminix 231 232 * an L2TP service such as https://www.aa.net.uk/broadband/l2tp-service/ 233 234 You need to "hide" the Ethernet device from the host - for PCI this 235 means configuring it for VFIO passthru; for USB you need to unload the 236 module(s) it uses. I have this segment in configuration.nix which you 237 may be able to adapt: 238 239 .. code-block:: nix 240 241 boot = { 242 kernelParams = [ "intel_iommu=on" ]; 243 kernelModules = [ 244 "kvm-intel" "vfio_virqfd" "vfio_pci" "vfio_iommu_type1" "vfio" 245 ]; 246 247 postBootCommands = '' 248 # modprobe -i vfio-pci 249 # echo vfio-pci > /sys/bus/pci/devices/0000:01:00.0/driver_override 250 ''; 251 blacklistedKernelModules = [ 252 "r8153_ecm" "cdc_ether" 253 ]; 254 }; 255 services.udev.extraRules = '' 256 SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="8153", OWNER="dan" 257 ''; 258 259 Then 260 you can execute :command:`run-border-vm` in a ``buildEnv`` shell, 261 which starts up QEMU using the NixOS configuration in 262 :file:`bordervm-configuration.nix`. 263 264 In this VM 265 266 * your Liminix checkout is mounted under :file:`/home/liminix/liminix` 267 268 * TFTP is listening on the ethernet device and serving 269 :file:`/home/liminix/liminix`. The server IP address is 10.0.0.1 270 271 * a PPPOE-L2TP relay is running on the same ethernet card. When the 272 connected Liminix device makes PPPoE requests, the relay spawns 273 L2TPv2 Access Concentrator sessions to your specified L2TP LNS. 274 Note that authentication is expected at the PPP layer not the L2TP 275 layer, so the PAP/CHAP credentials provided by your L2TP service can 276 be configured into your test device - bordervm doesn't need to know 277 about them. 278 279 To configure bordervm, you need a file called :file:`bordervm.conf.nix` 280 which you can create by copying and appropriately editing :file:`bordervm.conf-example.nix` 281 282 .. note:: 283 284 If you make changes to the bordervm configuration after executing 285 :command:`run-border-vm`, you need to remove the :file:`border.qcow2` disk 286 image file otherwise the changes won't get picked up. 287 288 289 Running tests 290 ************* 291 292 You can run all of the tests by evaluating :file:`ci.nix`, which is the 293 input I use in Hydra. Note that it expects Nixpkgs stable `and` unstable 294 as inputs, because it builds the qemu device against both. 295 296 .. code-block:: console 297 298 nix-build --argstr liminix `pwd` --arg nixpkgs "<nixpkgs>" \ 299 --argstr unstable `pwd`/../unstable-nixpkgs/ ci.nix 300 301 To run a single named test, use the ``-A`` flag. For example, ``-A pppoe`` 302 303 304 305 306 Troubleshooting 307 *************** 308 309 Diagnosing unexpectedly large images 310 ==================================== 311 312 Sometimes you can add a package and it causes the image size to balloon 313 because it has dependencies on other things you didn't know about. Build the 314 ``outputs.manifest`` attribute, which is a JSON representation of the 315 filesystem, and you can run :command:`nix-store --query` on it. 316 317 .. code-block:: console 318 319 nix-build -I liminix-config=path/to/your/configuration.nix \ 320 --arg device "import ./devices/qemu" -A outputs.manifest \ 321 -o manifest 322 nix-store -q --tree manifest 323 324 325 Contributing 326 ************ 327 328 Contributions are welcome, though in these early days there may be a 329 bit of back and forth involved before patches are merged: 330 Please get in touch somehow `before` you invest a lot of time into a 331 code contribution I haven't asked for. Just so I know it's expected 332 and you're not wasting time doing something I won't accept or have 333 already started on. 334 335 336 Nix language style 337 ================== 338 339 This section describes some Nix language style points that we 340 attempt to adhere to in this repo. 341 342 * favour ``callPackage`` over raw ``import`` for calling derivations 343 or any function that may generate one - any code that might need 344 ``pkgs`` or parts of it. 345 346 * prefer ``let inherit (quark) up down strange charm`` over 347 ``with quark``, in any context where the scope is more than a single 348 expression or there is more than one reference to ``up``, ``down`` 349 etc. ``with pkgs; [ foo bar baz]`` is OK, 350 ``with lib; stdenv.mkDerivation { ... }`` is usually not. 351 352 * ``<liminix>`` is defined only when running tests, so don't refer to it 353 in "application" code 354 355 * the parameters to a derivation are sorted alphabetically, except for 356 ``lib``, ``stdenv`` and maybe other non-package "special cases" 357 358 * indentation is whatever emacs nix-mode says it is. 359 360 * where a ``let`` form defines multiple names, put a newline after the 361 token ``let``, and indent each name two characters 362 363 * to decide whether some code should be a package or a module? 364 Packages are self-contained - they live in ``/nix/store/eeeeeee-name`` 365 and don't directly change system behaviour by their presence or 366 absense. modules can add to 367 ``/etc`` or ``/bin`` or other global state, create services, all that 368 side-effecty stuff. Generally it should be a package unless it 369 can't be. 370 371 372 373 Copyright 374 ========= 375 376 The Nix code in Liminix is MIT-licenced (same as Nixpkgs), but the 377 code it combines from other places (e.g. Linux, OpenWrt) may have a 378 variety of licences. I have no intention of asking for copyright 379 assignment: just like when submitting to the Linux kernel you retain 380 the copyright on the code you contribute. 381 382 383 Code of Conduct 384 =============== 385 386 Please govern yourself in Liminix project venues according to the 387 `Code of Conduct <https://gti.telent.net/dan/liminix/src/commit/7bcf6b15c3fdddafeda13f65b3cd4a422dc52cd3/CODE-OF-CONDUCT.md>`_ 388 389 390 Where to send patches 391 ===================== 392 393 394 Liminix' primary repo is https://gti.telent.net/dan/liminix but you 395 can't send code there directly because it doesn't have open registrations. 396 397 * There's a `mirror on Github <https://github.com/telent/liminix>`_ for 398 convenience and visibility: you can open PRs against that 399 400 * or, you can send me your patch by email using `git send-email <https://git-send-email.io/>`_ 401 402 * or in the future, some day, we will have federated Gitea using 403 ActivityPub.