liminix

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

developer.rst (12347B)


      1 Developer Manual
      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 manual
      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 In a ``buildEnv`` nix-shell, you can use the :command:`mips-vm` command
     31 to run Qemu with appropriate options. It connects the Liminix
     32 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.
     33 
     34 .. code-block:: console
     35 
     36     nix-shell --run "mips-vm result/vmlinux result/squashfs"
     37 
     38 If you run with ``--background /path/to/some/directory`` as the first
     39 parameter, it will fork into the background and open Unix sockets in
     40 that directory for console and monitor.  Use :command:`connect-vm`
     41 (also in the ``buildEnv`` environment) to connect to either of these
     42 sockets, and ^O to disconnect.
     43 
     44 Networking
     45 ==========
     46 
     47 VMs can network with each other using QEMU
     48 socket networking.  We observe these conventions, so that we can run
     49 multiple emulated instances and have them wired up to each other in
     50 the right way:
     51 
     52 * multicast 230.0.0.1:1234  : access (interconnect between router and "isp")
     53 * multicast 230.0.0.1:1235  : lan
     54 * multicast 230.0.0.1:1236  : world (the internet)
     55 
     56 A VM started with :command:`mips-vm` is connected to "lan" and "access", and
     57 the emulated border network gateway (see below) runs PPPoE and is
     58 connected to "access" and "world".
     59 
     60 Border Network Gateway
     61 ----------------------
     62 
     63 In pkgs/routeros there is a derivation to install and configure
     64 `Mikrotik RouterOS <https://mikrotik.com/software>`_ as a PPPoE access
     65 concentrator connected to the ``access`` and ``world`` networks, so that
     66 Liminix PPPoE client support can be tested without actual hardware.
     67 
     68 This is made available as the :command:`routeros` command in
     69 ``buildEnv``, so you can do something like::
     70 
     71     mkdir ros-sockets
     72     nix-shell
     73     nix-shell$ routeros ros-sockets
     74     nix-shell$ connect-vm ./ros-sockets/console
     75 
     76 to start it and connect to it. Note that by default it runs in the
     77 background. It is connected to "access" and "world" virtual networks
     78 and runs a PPPoE service on "access" - so a Liminix VM with a
     79 PPPOE client can connect to it and thus reach the virtual internet.
     80 [ check, but pretty sure this is not the actual internet ]
     81 
     82 `Liminix does not provide RouterOS licences and it is your own
     83 responsibility if you use this to ensure you're compliant with the
     84 terms of Mikrotik's licencing. It may be supplemented or replaced in
     85 time with configurations for RP-PPPoE and/or Accel PPP.`
     86 
     87 Hardware devices
     88 ****************
     89 
     90 How you get your image onto hardware will vary according to the
     91 device, but is likely to involve taking it apart to add wires to
     92 serial console pads/headers, then using U-Boot to fetch images over
     93 TFTP.  The OpenWrt documentation has a `good explanation <https://openwrt.org/docs/techref/hardware/port.serial>`_ of what you may expect to find on
     94 the device.
     95 
     96 There is a rudimentary TFTP server bundled with the system which runs
     97 from the command line, has an allowlist for client connections, and
     98 follows symlinks, so you can have your device download images direct
     99 from the :file:`./result` directory without exposing :file:`/nix/store/` to the
    100 internet or mucking about copying files to :file:`/tftproot`. If the
    101 permitted device is to be given the IP address 192.168.8.251 you might
    102 do something like this:
    103 
    104 .. code-block:: console
    105 
    106     nix-shell --run "tufted -a 192.168.8.251 result"
    107 
    108 Now add the device and server IP addresses to your configuration:
    109 
    110 .. code-block:: nix
    111 
    112   boot.tftp = {
    113     serverip = "192.168.8.111";
    114     ipaddr = "192.168.8.251";
    115   };
    116 
    117 and then build the derivation for ``outputs.default`` or
    118 ``outputs.flashimage`` (for which it will be an alias on any device
    119 where this is applicable). You should find it has created
    120 
    121 * :file:`result/firmware.bin` which is the file you are going to flash
    122 * :file:`result/flash.scr` which is a set of instructions to U-Boot to
    123   download the image and write it to flash after erasing the appropriate
    124   flash partition.
    125 
    126 .. NOTE::
    127 
    128    TTL serial connections typically have no form of flow control and
    129    so don't always like having massive chunks of text pasted into
    130    them - and U-Boot may drop characters while it's busy. So don't
    131    necessarily expect to copy-paste the whole of :file:`boot.scr` into
    132    a terminal emulator and have it work just like that. You may need
    133    to paste each line one at a time, or even retype it.
    134 
    135 
    136 For a faster edit-compile-test cycle, you can build a TFTP-bootable
    137 image instead of flashing. In your device configuration add
    138 
    139 .. code-block:: nix
    140 
    141   imports = [
    142     ./modules/tftpboot.nix
    143   ];
    144 
    145 and then build ``outputs.tftpboot``. This creates a file in
    146 ``result/`` called ``boot.scr``, which you can copy and paste into
    147 U-Boot to transfer the kernel and filesystem over TFTP and boot the
    148 kernel from RAM.
    149 
    150 
    151 Networking
    152 ==========
    153 
    154 You probably don't want to be testing a device that might serve DHCP,
    155 DNS and routing protocols on the same LAN as you (or your colleagues,
    156 employees, or family) are using for anything else, because it will
    157 interfere. You also might want to test the device against an
    158 "upstream" connection without having to unplug your regular home
    159 router from the internet so you can borrow the cable/fibre/DSL.
    160 
    161 ``bordervm`` is included for this purpose. You will need
    162 
    163 * a Linux machine with a spare (PCI or USB) ethernet device which you can dedicate to Liminix
    164 
    165 * an L2TP service such as https://www.aa.net.uk/broadband/l2tp-service/
    166 
    167 You need to "hide" the Ethernet device from the host - for PCI this
    168 means configuring it for VFIO passthru; for USB you need to unload the
    169 module(s) it uses. I have this segment in configuration.nix which you
    170 may be able to adapt:
    171 
    172 .. code-block:: nix
    173 
    174   boot = {
    175     kernelParams = [ "intel_iommu=on" ];
    176     kernelModules = [
    177       "kvm-intel" "vfio_virqfd" "vfio_pci" "vfio_iommu_type1" "vfio"
    178     ];
    179 
    180     postBootCommands = ''
    181       # modprobe -i vfio-pci
    182       # echo vfio-pci > /sys/bus/pci/devices/0000:01:00.0/driver_override
    183     '';
    184     blacklistedKernelModules = [
    185       "r8153_ecm" "cdc_ether"
    186     ];
    187   };
    188   services.udev.extraRules = ''
    189     SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="8153", OWNER="dan"
    190   '';
    191 
    192 Then
    193 you can execute :command:`run-border-vm` in a ``buildEnv`` shell,
    194 which starts up QEMU using the NixOS configuration in
    195 :file:`bordervm-configuration.nix`.
    196 
    197 In this VM
    198 
    199 * your Liminix checkout is mounted under :file:`/home/liminix/liminix`
    200 
    201 * TFTP is listening on the ethernet device and serving
    202   :file:`/home/liminix/liminix`.  The server IP address is 10.0.0.1
    203 
    204 * a PPPOE-L2TP relay is running on the same ethernet card.  When the
    205   connected Liminix device makes PPPoE requests, the relay spawns
    206   L2TPv2 Access Concentrator sessions to your specified L2TP LNS.
    207   Note that authentication is expected at the PPP layer not the L2TP
    208   layer, so the PAP/CHAP credentials provided by your L2TP service can
    209   be configured into your test device - bordervm doesn't need to know
    210   about them.
    211 
    212 To configure bordervm, you need a file called :file:`bordervm.conf.nix`
    213 which you can create by copying and appropriately editing  :file:`bordervm.conf-example.nix`
    214 
    215 .. note::
    216 
    217     If you make changes to the bordervm configuration after executing
    218     :command:`run-border-vm`, you need to remove the :file:`border.qcow2` disk
    219     image file otherwise the changes won't get picked up.
    220 
    221 
    222 Running tests
    223 *************
    224 
    225 You can run all of the tests by evaluating :file:`ci.nix`, which is the
    226 input I use in Hydra. Note that it expects Nixpkgs stable `and` unstable
    227 as inputs, because it builds the qemu device against both.
    228 
    229 .. code-block:: console
    230 
    231     nix-build --argstr liminix `pwd`  --arg  nixpkgs "<nixpkgs>" \
    232      --argstr unstable `pwd`/../unstable-nixpkgs/ ci.nix
    233 
    234 To run a single named test, use the ``-A`` flag. For example, ``-A pppoe``
    235 
    236 
    237 
    238 
    239 Troubleshooting
    240 ***************
    241 
    242 Diagnosing unexpectedly large images
    243 ====================================
    244 
    245 Sometimes you can add a package and it causes the image size to balloon
    246 because it has dependencies on other things you didn't know about. Build the
    247 ``outputs.manifest`` attribute, which is a JSON representation of the
    248 filesystem, and you can run :command:`nix-store --query` on it.
    249 
    250 .. code-block:: console
    251 
    252     nix-build -I liminix-config=path/to/your/configuration.nix \
    253       --arg device "import ./devices/qemu" -A outputs.manifest \
    254       -o manifest
    255     nix-store -q --tree manifest
    256 
    257 
    258 Contributing
    259 ************
    260 
    261 Contributions are welcome, though in these early days there may be a
    262 bit of back and forth involved before patches are merged:
    263 Please get in touch somehow `before` you invest a lot of time into a
    264 code contribution I haven't asked for.  Just so I know it's expected
    265 and you're not wasting time doing something I won't accept or have
    266 already started on.
    267 
    268 
    269 Nix language style
    270 ==================
    271 
    272 This section describes some Nix language style points that we
    273 attempt to adhere to in this repo.
    274 
    275 * favour ``callPackage`` over raw ``import`` for calling derivations
    276   or any function that may generate one - any code that might need
    277   ``pkgs`` or parts of it.
    278 
    279 * prefer ``let inherit (quark) up down strange charm`` over
    280   ``with quark``, in any context where the scope is more than a single
    281   expression or there is more than one reference to ``up``, ``down``
    282   etc.  ``with pkgs; [ foo bar baz]`` is OK,
    283   ``with lib; stdenv.mkDerivation { ... }`` is usually not.
    284 
    285 * ``<liminix>`` is defined only when running tests, so don't refer to it
    286   in "application" code
    287 
    288 * the parameters to a derivation are sorted alphabetically, except for
    289   ``lib``, ``stdenv`` and maybe other non-package "special cases"
    290 
    291 * indentation is whatever emacs nix-mode says it is.
    292 
    293 * where a ``let`` form defines multiple names, put a newline after the
    294   token ``let``, and indent each name two characters
    295 
    296 * to decide whether some code should be a package or a module?
    297   Packages are self-contained - they live in ``/nix/store/eeeeeee-name``
    298   and don't directly change system behaviour by their presence or
    299   absense. modules can add to
    300   ``/etc`` or ``/bin`` or other global state, create services, all that
    301   side-effecty stuff.  Generally it should be a package unless it
    302   can't be.
    303 
    304 
    305 
    306 Copyright
    307 =========
    308 
    309 The Nix code in Liminix is MIT-licenced (same as Nixpkgs), but the
    310 code it combines from other places (e.g. Linux, OpenWrt) may have a
    311 variety of licences.  I have no intention of asking for copyright
    312 assignment: just like when submitting to the Linux kernel you retain
    313 the copyright on the code you contribute.
    314 
    315 
    316 Code of Conduct
    317 ===============
    318 
    319 Please govern yourself in Liminix project venues according to the
    320 `Code of Conduct <https://gti.telent.net/dan/liminix/src/commit/7bcf6b15c3fdddafeda13f65b3cd4a422dc52cd3/CODE-OF-CONDUCT.md>`_
    321 
    322 
    323 Where to send patches
    324 =====================
    325 
    326 
    327 Liminix' primary repo is https://gti.telent.net/dan/liminix but you
    328 can't send code there directly  because it doesn't have open registrations.
    329 
    330 * There's a `mirror on Github <https://github.com/telent/liminix>`_ for
    331   convenience and visibility: you can open PRs against that
    332 
    333 * or, you can send me your patch by email using `git send-email <https://git-send-email.io/>`_
    334 
    335 * or in the future, some day, we will have federated Gitea using
    336   ActivityPub.