liminix

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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.