diff --git a/doc/manual/chapters/create.adoc b/doc/manual/chapters/create.adoc index 91a448b..9a781c4 100644 --- a/doc/manual/chapters/create.adoc +++ b/doc/manual/chapters/create.adoc @@ -1,3 +1,15 @@ -There are several ways to create a new simulation scenario in NSE2, depending on your preferences and requirements. -This section covers the three main methods: using the `netedit` tool, converting CSV/JSON files, and manually creating a scenario file. -Depending on whether you prefer a graphical interface, command-line tools, or manual editing, you can choose the method that best suits your workflow. +The recommended way to create an NSE2 scenario is to start with a scenario in +the https://github.com/esa/ccsds-dtn-reference-scenarios[CCSDS DTN Reference +Scenarios] format. The repository provides the scenario data as CSV files; +these can be converted directly into the NSE2 contact plan and Docker Compose +files. The generated files keep node names and links synchronized, avoiding the +need to understand the specifics of the <> and +<>. + +The CSV scenario can be used unchanged, adapted before conversion, or used as +the basis for a more substantial modification. Alternatively, an already +converted scenario from NSE2's `scenarios/` directory can be copied and +adapted. Creating both files manually should generally be avoided, but remains +useful for small examples or special-purpose topologies. `netedit` is another +alternative for graphical topology editing, though its GraphML conversion to NSE2 +files is currently not yet supported. diff --git a/doc/manual/chapters/create/convert.adoc b/doc/manual/chapters/create/convert.adoc index 6651dae..cb61863 100644 --- a/doc/manual/chapters/create/convert.adoc +++ b/doc/manual/chapters/create/convert.adoc @@ -1,89 +1,122 @@ -== Generate Files from CSV +The following commands implement the CCSDS CSV workflow described above. Use +the same contact CSV to generate both a contact plan and a Docker Compose +topology. -The helper scripts in `tools/helpers` can generate a contact plan and a -Docker Compose file from the same contact CSV. The CSV contains one link -per row with the columns `source`, `destination`, `start`, `end`, -`bandwidth`, `delay`, and an optional label. The scripts skip the header -row. +The generators are available as the `csv_to_ccp` and `csv_to_compose` commands +(or directly under `tools/helpers/` in a source checkout). -=== Generate a Contact Plan +==== Contact CSV -Use `csv_to_ccp.py` to create a `.ccp` file: +The input CSV has a header followed by one row per directed link: + +[cols="1,1,1,1,1,1,1",options="header"] +|=== +| Source | Destination | Start | End | Bandwidth | Delay | Label + +| Node name | Node name | Seconds | Seconds | Bits per second | Milliseconds | Optional +|=== + +The header row is skipped. The label column is optional. Loss and jitter are +not CSV columns and are generated as zero. A row with `start=0` and `end=-1` +becomes a fixed link. Matching rows in opposite directions are combined into a +symmetric link where their timing and link properties match. + +Use the `Label` column to distinguish multiple links between the same pair of +nodes when they have different properties. Generated Docker network and +interface names are based on the node names and, where needed, the link label. +Keep node names short; if a generated name exceeds Linux's 15-character +interface-name limit, it is replaced by a 12-character MD5 prefix. A long +common prefix can sometimes be avoided with `--strip-prefix PREFIX`; link +labels ending in `high` or `low` are shortened to `hi` or `lo` when names are +constructed. + +==== Generate a Contact Plan [source,bash] ---- -csv_to_ccp.py contacts.csv --output contacts.ccp +csv_to_ccp contacts.csv --output contacts.ccp ---- -Options: +The available options are: -* `--output`, `-o`: output file; `-` (the default) writes to standard output. +* `--output`, `-o`: output file; `-` writes to standard output. * `--strip-prefix PREFIX`: remove `PREFIX` from source and destination names. -* `--speedup FACTOR`: divide contact timestamps by `FACTOR`, useful for a - shorter test run. The default is `1`. -* `--bw-scale FACTOR` (also `--bandwidth-scale`): multiply bandwidths by - `FACTOR`. The default is `1`. - -Rows with a start time of `0` and an end time of `-1` become fixed links. -Matching reverse-direction rows are combined into a symmetric link using -`=`. When a node pair has multiple labels, the label is used to generate a -dedicated destination interface such as `dev:n1_n2_payload`. +* `--speedup FACTOR`: divide contact times by `FACTOR` to shorten a scenario. +* `--bw-scale FACTOR` or `--bandwidth-scale FACTOR`: scale bandwidth values. -=== Generate a Compose File +When multiple labelled links connect the same pair of nodes, the label is used +to select a destination interface such as `dev:n1_n2_payload`. -Use `csv_to_compose.py` with the same CSV: +==== Generate a Compose Topology [source,bash] ---- -csv_to_compose.py contacts.csv --output compose.yml +csv_to_compose contacts.csv --output compose.yml ---- -Options: +The optional node mapping JSON supplies stable metadata for each CSV node. Its +entries contain `node_label`, `node_name`, and `node_id`: -* `--output`, `-o`: output file; `-` writes to standard output. -* `--strip-prefix PREFIX`: remove `PREFIX` from node names. -* `--nodes FILE` (also `--mapping`): JSON file containing node labels, names, - and IDs. -* `--name NAME`, `-n`: Compose project name. -* `--image IMAGE`, `-i`: container image; the default is `alpine`. -* `--entrypoint COMMAND`, `-e`: container entrypoint. The default installs - `iproute2` and `bash`, then keeps the container running. -* `--build PATH`, `-b`: use a build directory instead of an image. -* `--base-subnet PREFIX`: first two subnet octets; the default is `172.33`. -* `--export-graphml FILE`, `-g`: additionally export the topology as GraphML. -* `--node-volumes DIRECTORY`: create per-node data directories and mount them - into the containers. -* `--mount-compose`: mount the generated Compose file read-only in each - container. - -The generated Compose topology uses one Docker bridge network for each link. -Every such network connects exactly two nodes; this keeps each link isolated -and gives both endpoints a predictable interface. Network names are based on -the sorted node names, and, when several differently labelled links connect -the same pair, also include the link label. Direction suffixes `_ul` and -`_dl` are removed and `high`/`low` are shortened to `hi`/`lo`. - -Linux limits network interface names to 15 characters including the -terminating null byte. Therefore names with 14 or more characters are -replaced by the first 12 characters of their MD5 hash, with a warning printed -by the script. Use `--strip-prefix` when node labels share a long common -prefix, for example: +[source,json] +---- +[ + { + "node_label": "n1", + "node_name": "spacecraft-1", + "node_id": "ipn:1.0" + } +] +---- + +If a mapping is not supplied, metadata is generated from the CSV labels. Use +the mapping when the CCSDS scenario provides stable names and IDs. + +The available `csv_to_compose` options are: + +* `--output`, `-o`: Compose output file; `-` writes to standard output. +* `--strip-prefix PREFIX`: remove `PREFIX` from node labels. +* `--nodes FILE` or `--mapping FILE`: read node metadata from JSON. +* `--name NAME`, `-n`: set the Compose project name. +* `--image IMAGE`, `-i`: select the node image; the default is `alpine`. +* `--entrypoint COMMAND`, `-e`: override the default Alpine entrypoint. +* `--base-subnet PREFIX`: choose the first two subnet octets; the default is + `172.33`. +* `--node-volumes DIRECTORY`: create and mount a data directory per node. +* `--build PATH`, `-b`: build nodes from the specified path instead of an image. +* `--mount-compose`: mount the generated Compose file read-only in each node. +* `--export-graphml FILE`, `-g`: additionally write the generated topology as + GraphML. This is an output option; the script does not read GraphML as input. + +The generator creates one Docker bridge network for each link, with predictable +interfaces for the endpoints. + +==== Generate Both Files + +Generate the two NSE2 inputs from the same CCSDS contact CSV: [source,bash] ---- -csv_to_compose.py --strip-prefix eo --nodes nodes.json \ - --output compose.yml actual_contacts.csv +csv_to_ccp --strip-prefix eo actual_contacts.csv --output contacts.ccp +csv_to_compose --strip-prefix eo --nodes nodes.json \ + --name earth-orbit actual_contacts.csv --output compose.yml ---- -Removing the prefix can keep the generated network and interface names short -and human-readable instead of forcing the hash fallback. +Both generators record the generation date and command in their output. Review +the generated files before starting the scenario, then use the normal NSE2 +workflow from <>. -Both scripts write the generation date and complete command to the generated -file. A typical scenario therefore uses the same CSV to generate both files: +==== Shortening Realistic Contact Plans + +As an alternative to `csv_to_ccp` 's time and bandwidth scaling, `random_contacts` +keeps the topology and link properties but replaces long, realistic contact +windows with short random ones for a compact test run: [source,bash] ---- -csv_to_ccp.py --strip-prefix eo --output contacts.ccp actual_contacts.csv -csv_to_compose.py --strip-prefix eo --nodes nodes.json \ - --name earth-orbit --output compose.yml actual_contacts.csv +random_contacts contacts.ccp contacts-short.ccp \ + --length 300 --min-contact 5 --max-contact 20 --seed 42 ---- + +The `--length` option sets the plan duration; `--min-contact`, `--max-contact`, +and `--seed` control the generated windows. Fixed links and all link properties +are preserved. diff --git a/doc/manual/chapters/create/manually.adoc b/doc/manual/chapters/create/manually.adoc index de98ef0..7151d1e 100644 --- a/doc/manual/chapters/create/manually.adoc +++ b/doc/manual/chapters/create/manually.adoc @@ -1,7 +1,10 @@ -Creating a new simulation scenario from scratch is a straight forward process, but it requires a good understanding of the Docker Compose file format and the specific requirements for network simulation. The following sections will guide you through the steps to manually create a scenario using Docker Compose. +Manual creation is mainly useful for small examples or special-purpose +topologies. The following sections describe the files that can make up an NSE2 +scenario and how to create them using Docker Compose. -For a concrete example, you can refer to the simple scenario in the `scenarios/simple` directory of the NSE2 repository, which contains a basic setup with three nodes connected in a line. -It is described in detail in <>, but you can also use it as a starting point for your own scenarios. +For a concrete example, refer to the simple scenario in the +`scenarios/simple` directory. It contains three nodes connected in a line and +is described in detail in <>. ==== Create a Docker Compose File @@ -25,4 +28,4 @@ If no actions are needed, the actions file can be empty or not used at all. The visualization file is a JSON file that defines a scenario description, the nodes with their visualization properties, and where live link information can be found. It is used to visualize the network topology and the active links between the nodes. The visualization file is typically named `viz.json` and is placed in the same directory as the Compose file. The details about the visualization file format can be found in <>. -If no visualization is needed, the visualization file can be empty or not used at all. \ No newline at end of file +If no visualization is needed, the visualization file can be empty or not used at all. diff --git a/doc/manual/chapters/create/netedit.adoc b/doc/manual/chapters/create/netedit.adoc index 501b6d4..90edb9d 100644 --- a/doc/manual/chapters/create/netedit.adoc +++ b/doc/manual/chapters/create/netedit.adoc @@ -46,14 +46,11 @@ TIP: In the link properties dialog, you can also set the link to be a dynamic li NOTE: Some advanced NSE2 features such as multiple parallel links between the same pair of nodes, or using specific network interfaces for the links, are not supported by `netedit` yet. You can still use the `compose.yml` file to define these features manually. -Via the _File_ menu, you can save the current topology to a GraphML file, which can then be converted to a Docker Compose file. +Via the _File_ menu, you can save the current topology to a GraphML file. ==== Converting GraphML to Docker Compose **TODO** -==== Running the Scenario - -Once you have a proper Docker Compose file, you can run the scenario using the `nse2_topo` command. This command will start the Docker containers defined in the Compose file and set up the network topology as specified. - -Of course, you can also add a contact plan, actions file, and visualization file as described in <> to enhance the simulation experience. \ No newline at end of file +NSE2 currently has no helper scripts for converting GraphML to a Docker Compose +topology or a contact plan. diff --git a/doc/manual/chapters/formats/compose.adoc b/doc/manual/chapters/formats/compose.adoc index f5bbb61..fc159cc 100644 --- a/doc/manual/chapters/formats/compose.adoc +++ b/doc/manual/chapters/formats/compose.adoc @@ -20,12 +20,19 @@ You set the container `image` or alternatively the `build` option to specify the IMPORTANT: The container images used for the nodes must have `iproute2` installed so that `tc qdisc` can be used to configure the network emulation for the link effects. Also, for very large delays, e.g., several minutes like to mars, a version of `iproute2` from https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/commit/?id=9a6b231ea1b09e450688c5814a4c89a57cdbee77[mid 2024] or later is required. -There are a few optional things to configure for each node: +For scenarios managed by `nse2_contacts`, every node must define the +`environment` variable `NODE_ID` with a unique number. The contact player uses +this value when resolving node references from the contact plan and applying +its contacts. Applications can also use it for DTN auto-configuration. -- Sharing the compose and contact plan files `read-only` with the node container (`./compose.yml:/compose.yml:ro`, `./contacts.ccp:/contacts.ccp:ro`), so that the node can parse the topology if needed, for routing purposes. -- Set the `environment` variables `NODE_ID` to a unique number, e.g., to use for IPN auto configuration in DTN networks. This is useful for the node to identify itself in the simulation. +The Compose and contact plan files can optionally be shared with a node as +read-only mounts (`./compose.yml:/compose.yml:ro`, +`./contacts.ccp:/contacts.ccp:ro`). This is useful when an application inside +the node needs to inspect the topology or contact plan, for example to set up +its own routing. The mounts are not required by the contact player itself. -Of course, you can also add custom entry points or any other environment variables that are needed for the node to run, such as configuration files or other parameters specific to the service. +Custom entrypoints and additional environment variables can be added when the +node service needs application-specific configuration. TIP: Sometimes it is useful to wrap your `entrypoint` commands in a `bash -c` command to ensure that the commands are executed in a shell environment. This is especially useful if you want to use shell features like variable expansion or command substitution. An example of this would be: `entrypoint: 'sh -c "apk add iproute2 bash && tail -f /dev/null"'` @@ -45,4 +52,4 @@ Additionally, it can be helpful to set the `ipam` option to specify the IP addre Finally, if you expect multiple links between the same pair of nodes, you can use specific network interfaces for the link (`com.docker.network.container_iface_prefix`). This prefix is used for the actual network interface name in the node container. Contact plans can then refer to these interfaces by their name, e.g., `dev:eth0` or `dev:n1_n2_0` for the second interface, and so on, instead of using the node name as second node. -WARNING: Network names in docker are global. Thus, running two scenarios with the same network names in parallel will lead to conflicts! \ No newline at end of file +WARNING: Network names in docker are global. Thus, running two scenarios with the same network names in parallel will lead to conflicts! diff --git a/doc/manual/chapters/formats/viz.adoc b/doc/manual/chapters/formats/viz.adoc index 52718aa..e193726 100644 --- a/doc/manual/chapters/formats/viz.adoc +++ b/doc/manual/chapters/formats/viz.adoc @@ -6,7 +6,7 @@ Optionally, it can also put a background image behind the nodes to provide a bet .Example visualization file of an earth observation scenario. image::images/viz_eo.png[width=70%, align="center", alt="Example visualization file with nodes and links."] -The visualization frontend is started by running the `nse2_viz` command, which reads the `viz.json` file and starts a web server to serve the visualization. +The visualization frontend is started by running the `nse2_netviz` command, which reads the `viz.json` file and starts a web server to serve the visualization. The `viz.json` file used for <> is as follows: @@ -32,4 +32,3 @@ The `links` key is used to specify the file that contains the current state of l The file just contains a list of links, each pair on a separate line and a "-" or "." indicating whether it is a fixed or dynamic link, e.g., `n1 - n2` or `n1 . n2`. IMPORTANT: To get a proper visualization of the active links, `nse2_contacts` must be started with the `-m` option to periodically generate a mapping file with all active links. - diff --git a/doc/manual/chapters/running.adoc b/doc/manual/chapters/running.adoc index b9da618..7400777 100644 --- a/doc/manual/chapters/running.adoc +++ b/doc/manual/chapters/running.adoc @@ -54,7 +54,7 @@ include::../../../scenarios/simple/compose.yml[] The contact plan (<>) defines the communication links between the nodes, including bandwidth, delay, loss, and jitter. In our example, we have a fixed contact between n1 and n2 with a bandwidth of 100 Mbps and a delay of 100 ms, and a fluctuating contact between n2 and n3 with a bandwidth of 2 Mbps, a delay of 10 ms, and a time window from 20 to 40 seconds. Also, we set the contact plan to loop, meaning that the contacts will be repeated indefinitely. -A detailed description of the contacts file format can be found in <>. +A detailed description of the contacts file format can be found in <>. .Core contact plan of the simple scenario. @@ -78,7 +78,7 @@ include::../../../scenarios/simple/actions.txt[] The following steps need to be performed to start a simulation in NSE2: . Start the topology: `nse2_topo compose.yml` -. Start the contacts: `nse2_contacts -m contacts.ccp` +. Start the contacts: `nse2_contacts -m compose.yml contacts.ccp` . Start the actions: `nse2_actions actions.txt` . Optionally, start the node manager: `nse2_mgr compose.yml contacts.ccp` @@ -159,45 +159,22 @@ Now all three nodes are running and ready to be configured. Next step is to start the contact player, which will establish the communication links between the nodes based on the contact plan defined in `contacts.ccp`. [source] ---- -$ nse2_contacts -m contacts.ccp +$ nse2_contacts -l -m compose.yml contacts.ccp Loading scenario from compose.yml. Node ipn:1.0 connected to network n1_n2 with 172.33.0.2 Node ipn:2.0 connected to network n1_n2 with 172.33.0.3 Node ipn:2.0 connected to network n2_n3 with 172.33.1.2 Node ipn:3.0 connected to network n2_n3 with 172.33.1.3 Created 3 nodes. -[('n1', 'n2', '-'), ('n2', 'n3', '-')] -['n1', 'n2', '100000000', '0.0', '0.1', '0.0'] 6 -CoreContact(timespan=(0, 0), nodes=('n1', 'n2'), bw=100000000, loss=0.000000, delay=0.100000, jitter=0.000000) -['n2', 'n1', '100000000', '0.0', '0.1', '0.0'] 6 -CoreContact(timespan=(0, 0), nodes=('n2', 'n1'), bw=100000000, loss=0.000000, delay=0.100000, jitter=0.000000) -['20', '40', 'n2', 'n3', '2000000', '0.0', '0.01', '0.0'] 8 -CoreContact(timespan=(20, 40), nodes=('n2', 'n3'), bw=2000000, loss=0.000000, delay=0.010000, jitter=0.000000) -['20', '40', 'n3', 'n2', '2000000', '0.0', '0.01', '0.0'] 8 -CoreContact(timespan=(20, 40), nodes=('n3', 'n2'), bw=2000000, loss=0.000000, delay=0.010000, jitter=0.000000) -all contacts sorted pairs: [('n2', 'n3', '-'), ('n2', 'n3', '-')] -links: [('n1', 'n2', '-')] -Link: ('n1', 'n2', '-') -new link: ('n1', 'n2', '-') -Updating network map tmp/compose.netmap -Setting up tc for n3 on device n2_n3_0 with 100% loss -0ms -Setting up tc for n2 on device n2_n3_0 with 100% loss -0ms -Activating fixed contact CoreContact(timespan=(0, 0), nodes=('n1', 'n2'), bw=100000000, loss=0.000000, delay=0.100000, jitter=0.000000) -0.1ms -Activating fixed contact CoreContact(timespan=(0, 0), nodes=('n2', 'n1'), bw=100000000, loss=0.000000, delay=0.100000, jitter=0.000000) -0.1ms +[INIT] Initialize contact: interface n1_n2_0 on node n2 +[INIT] Initialize contact: interface n1_n2_0 on node n1 +[INIT] Initialize contact: interface n2_n3_0 on node n2 +[INIT] Initialize contact: interface n2_n3_0 on node n3 +[ 0 ] Activating Contact (n1 -> n2 via n1_n2, 0--1) +[ 0 ] Activating Contact (n2 -> n1 via n1_n2, 0--1) [ 0 ] Next event(s) at 20 -[ 20 ] Activating CoreContact(timespan=(20, 40), nodes=('n2', 'n3'), bw=2000000, loss=0.000000, delay=0.010000, jitter=0.000000) -0.01ms -[ 20 ] Activating CoreContact(timespan=(20, 40), nodes=('n3', 'n2'), bw=2000000, loss=0.000000, delay=0.010000, jitter=0.000000) -0.01ms -Link: ('n1', 'n2', '-') -new link: ('n1', 'n2', '-') -Link: ('n2', 'n3', '.') -new link: ('n2', 'n3', '.') -Updating network map tmp/compose.netmap +[ 20 ] Activating Contact (n2 -> n3 via n2_n3, 20-40) +[ 20 ] Activating Contact (n3 -> n2 via n2_n3, 20-40) [ 20 ] Next event(s) at 40 ---- @@ -287,16 +264,21 @@ IMPORTANT: Opening the logs or a terminal requires X11 forwarding (set `DISPLAY` [#viz-usage] === Visualizing the Simulation -To visualize the simulation using `nse2_viz`, you can use the `viz.json` file, which provides visualization data for the nodes and contacts. +To visualize the simulation using `nse2_netviz`, you can use the `viz.json` file, which provides visualization data for the nodes and contacts. NSE2 provides a simple web-based visualization frontend to graphically represent the current state of the simulation. It displays a log file, the nodes, their (configured) positions, and the contacts between them. Optionally, it can also put a background image behind the nodes to provide a better context for the simulation, e.g., a map of the area where the nodes are located. -The list of nodes also provides quick access to a terminal on each nodes. +The list of nodes also provides quick access to an interactive terminal for +each node. Selecting a node opens a terminal connected to that node's +container. -IMPORTANT: Opening a terminal requires X11 forwarding (set `DISPLAY` environment variable) and an installed `xterm` on the host system running the manager. +The visualization frontend is started by running the `nse2_netviz` command, which reads the `viz.json` file and starts a web server to serve the visualization. -The visualization frontend is started by running the `nse2_viz` command, which reads the `viz.json` file and starts a web server to serve the visualization. +[source,bash] +---- +nse2_netviz viz.json +---- [source,language=json] ---- @@ -309,3 +291,7 @@ IMPORTANT: To get a proper visualization of the active links, `nse2_contacts` mu The visualization frontend is very basic but can easily be extended to provide scenario and simulation specific information. +[#wireguard-example] +=== External WireGuard Service Example + +include::wireguard.adoc[] diff --git a/doc/manual/chapters/wireguard.adoc b/doc/manual/chapters/wireguard.adoc new file mode 100644 index 0000000..edea7b2 --- /dev/null +++ b/doc/manual/chapters/wireguard.adoc @@ -0,0 +1,230 @@ +The `wireguard` scenario demonstrates how an external application can +participate in an NSE2 scenario through a WireGuard tunnel. It combines a +Docker Compose topology on the NSE2 host with a second Compose project on the +external host. + +The NSE2 side contains two nodes, `n1` and `n2`, and a virtual node named +`n3_wg`. The virtual node runs the WireGuard server and forwards traffic to a +peer on the external host. The external host runs the WireGuard client and an +HTTP application in the same network namespace. + +[.text-center] +.WireGuard external service topology +[plantuml, format="png", id="FigWireGuardTopology"] +---- +left to right direction + +package "NSE2 host" { + rectangle "n1" as n1 + rectangle "n2" as n2 + rectangle "n3_wg\n(WireGuard server /\ntransparent proxy)" as n3_wg + n1 <--> n3_wg + n2 <--> n3_wg +} + +package "External host" { + rectangle "WireGuard client" as client + rectangle "External HTTP application" as app + client -- app +} + +n3_wg <--> client : WireGuard tunnel +---- + +[IMPORTANT] +==== +A WireGuard gateway node supports exactly one external WireGuard peer. +Each virtual gateway node (`n3_wg`) is designed to represent a single external +service reachable through a single WireGuard client. To connect multiple +external services, deploy additional WireGuard gateway nodes, one per service. +==== + +The complete scenario is in `scenarios/wireguard/`. The generic Compose and +contact plan formats are described in <> and +<>. This section focuses on the parts that differ from the +standard container-only scenarios. + +==== Compose Topology + +The scenario uses two Compose files because the external application is not an +NSE2 node. The important parts of `compose.yml` are: + +* `n3_wg` is a specialized gateway service rather than a normal NSE2 node. + It is a WireGuard server, but to NSE2 and the other nodes it appears as node n3. +* `n1` and `n2` are regular NSE2 nodes. +* `compose-client.yml` runs the external application with + `network_mode: service:wg-client`. The application therefore shares the + WireGuard client's network namespace and needs no separate route to use the + tunnel. + +The three networks in `compose.yml` have distinct purposes: + +[cols="1,2",options="header"] +|=== +| Network | Purpose + +| `n1_n3_wg` and `n2_n3_wg` +| The simulated links between each NSE2 node and `n3_wg`. These are the links + named by `contacts.ccp` and therefore receive the simulated bandwidth, + delay, and availability conditions. + +| `wg_transport` +| A high-priority gateway network used for communication with the host and the + external WireGuard endpoint. It is deliberately not listed in the contact + plan, so the outer WireGuard packets are not shaped as they leave the host. +|=== + +This separation is important: the tunnel remains available to carry traffic, +while the traffic path inside NSE2 can still be made intermittent or delayed. +The generated peer configuration uses `ALLOWEDIPS` to route the WireGuard +subnet and the NSE2 node networks through the tunnel. + +==== Gateway and iptables Configuration + +The server template installs `setup-iptables.sh` as both a `PostUp` and a +`PostDown` hook for the `wg0` interface. The script is based on the container's +actual interfaces and the generated peer configuration. + +When the interface starts, the script: + +* Finds every attached interface except loopback and `wg0`. +* Reads the single peer `/32` address and the WireGuard listen port with + `wg show`. +* Allows forwarding between the peer and each Docker network and adds + masquerading for return traffic. +* Accepts the outer WireGuard UDP port before the catch-all rule. +* DNATs other traffic addressed to a local `n3_wg` Docker address through the + tunnel to the generated peer address, then permits the translated traffic + through `wg0`. +* Stores the peer address and port in `/run` so that `PostDown` can remove the + same rules after the WireGuard interface has been removed. + +Rules are added only when absent and removed until absent, making repeated +`up` and `down` calls safe. The script expects exactly one peer, which matches +this example's one external service per WireGuard gateway. + +==== Configuration + +Before starting the NSE2 topology, review these values in +`scenarios/wireguard/compose.yml`: + +* `SERVERURL` is the address through which the external host reaches the NSE2 + host. For a same-host test, `host.docker.internal` can be used. +* `SERVERPORT` must match the published UDP port for `n3_wg`. +* `ALLOWEDIPS` must contain the NSE2 networks that the external application + should reach through the tunnel. Here, a list of all /24 networks can be used + or directly a bigger /20 for example. + +The example uses the https://github.com/linuxserver/docker-wireguard[LinuxServer.io +WireGuard image]. Refer to its documentation for other container or host +requirements. + +==== Starting and Verifying the Example + +Run the following command from `scenarios/wireguard/` on the NSE2 host: + +[source,bash] +---- +nse2_topo compose.yml +---- + +The `n3_wg` container generates the server keys and the peer configuration +under `server/runtime`, indicated by the line in the log: + +[source,text] +---- +n3_wg | **** No wg0.conf found (maybe an initial install), generating 1 server and external peer/client confs **** +n3_wg | PEER external QR code (conf file is saved under /config/peer_external): +---- + +and bind mounted to the host at: `server/runtime/peer_external/peer_external.conf`. + +Copy it to `client/wg0.conf` on the external host. For a same-host test: + +[source,bash] +---- +cp server/runtime/peer_external/peer_external.conf client/wg0.conf +chmod 600 client/wg0.conf +---- + +For a separate host, use a mechanism such as `scp` and apply the restrictive +permissions there. The generated file contains the peer's private key and should not be shared. + +Start the external WireGuard client and application from the directory that +contains `compose-client.yml`: + +[source,bash] +---- +docker compose -f compose-client.yml up +---- + +If the wireguard config was correctly copied the client should log: + +[source,text] +---- +external-wg-client | **** Found WG conf /config/wg_confs/wg0.conf, adding to list **** +---- + +After that, check the handshake on both sides. Run the first command on the NSE2 host and +the second command on the external host: + +[source,bash] +---- +docker compose exec n3_wg wg show +docker compose -f compose-client.yml exec wg-client wg show +---- + +It should look something like this: + +[source,text] +---- +interface: wg0 + public key: R/t7uUEJbPYZDxvAsLX9UNxGtxLR+zPnHEIEbhpAcQQ= + private key: (hidden) + listening port: 51820 + +peer: 1dRTEhlIJsX8uXaoT0d8g7vsuzw3/P4xLWgomm2Fhgs= + preshared key: (hidden) + endpoint: 172.32.0.1:51820 + allowed ips: 10.192.122.2/32 + latest handshake: Now + transfer: 520 B received, 184 B sent +---- + +The `latest handshake` value should be a few seconds old. If there is no +handshake, check `SERVERURL`, UDP reachability for `SERVERPORT`, and the copied +peer configuration. Your WireGuard tunnel was not setup correctly. + +The example application listens on port 80. Test traffic in both directions: + +[source,bash] +---- +docker compose exec n1 curl --fail http://n3_wg +docker compose exec n2 curl --fail http://n3_wg +docker compose -f compose-client.yml exec wg-client ping -c 3 n1 +docker compose -f compose-client.yml exec wg-client ping -c 3 n2 +---- + +The first two requests should return `Hello from external WireGuard service!`. + +==== Running the NSE2 Tools + +After the tunnel is established, use the standard NSE2 tools described in +<> and proceed as normal, starting the contact player with the scenario's +topology and contact plan, setting up visualisation and starting the actions. + +The action file provides a small ping and HTTP workload. The general syntax and +behavior is covered by <>. + +==== Stopping the Scenario + +Stop the action runner, contact player and topology as normal with `Ctrl+C`. Then +stop the external Compose project on the external host: + +[source,bash] +---- +docker compose -f compose-client.yml down +---- + +Remove `client/wg0.conf` and the generated contents of `server/runtime` when +the test credentials are no longer needed. diff --git a/doc/manual/manual.adoc b/doc/manual/manual.adoc index 95fe39e..35588ad 100644 --- a/doc/manual/manual.adoc +++ b/doc/manual/manual.adoc @@ -1,5 +1,5 @@ = Network Simulation & Emulation Environment (NSE2) Manual -:revdate: 2025-07-02 Work in Progress +:revdate: 2026-09-17 Work in Progress :reproducible: :toc: :toclevels: 3 @@ -17,7 +17,7 @@ :listing-caption: Listing :product-name: NSE2 :copyright: © 2025 European Space Agency (ESA) -:keywords: network simulation, emulation, network testbed, NSE2, manual +:keywords: network simulation, emulation, network testbed, NSE2, WireGuard, manual :subject: Network Simulation and Emulation Environment Manual Lars Baumgaertner @@ -39,16 +39,15 @@ include::chapters/running.adoc[] include::chapters/create.adoc[] [#manually] +=== Using CCSDS Reference Scenarios and CSV Tools +include::chapters/create/convert.adoc[] + === Manually Creating a Scenario include::chapters/create/manually.adoc[] === Using `netedit` to Create a Scenario (Work in Progress) include::chapters/create/netedit.adoc[] -=== Using `csvconvert` to Create a Scenario (Work in Progress) -include::chapters/create/convert.adoc[] - - == File Formats [#compose-format] @@ -76,9 +75,3 @@ include::chapters/formats/viz.adoc[] **TODO** See CCSDS DTN Reference Scenarios Orange Book Draft for details on the node file format. - -=== Optional: Contacts File Format (`contacts.csv`) - -**TODO** - -See CCSDS DTN Reference Scenarios Orange Book Draft for details on the node file format. \ No newline at end of file diff --git a/scenarios/eo/README.md b/scenarios/eo/README.md index 242c812..caa1d17 100644 --- a/scenarios/eo/README.md +++ b/scenarios/eo/README.md @@ -27,9 +27,9 @@ In this scenario the satellite passes are frequent and data can be downlinked at ## Contact Plan and Compose File The contact plan [contacts.ccp](contacts.ccp) is generated from -`actual_contacts.csv` via `csv_to_ccp.py`. The compose file +`actual_contacts.csv` via `csv_to_ccp`. The compose file [compose.yml](compose.yml) is generated from the same CSV via -`csv_to_compose.py`. +`csv_to_compose`. The CSV-to-Compose conversion uses `nodes.json` for node metadata and strips the `eo` prefix from node names. The generated contact plan also strips the @@ -37,10 +37,10 @@ the `eo` prefix from node names. The generated contact plan also strips the them. For short test runs, [contacts_testing.ccp](contacts_testing.ccp) is derived -from `contacts.ccp` with `random-contacts.py`: +from `contacts.ccp` with `random_contacts`: ```bash -random-contacts.py contacts.ccp contacts_testing.ccp \ +random_contacts contacts.ccp contacts_testing.ccp \ --length 120 --min-contact 30 --max-contact 30 --seed 0 ``` diff --git a/scenarios/lc/README.md b/scenarios/lc/README.md index 9b738c2..45e9b32 100644 --- a/scenarios/lc/README.md +++ b/scenarios/lc/README.md @@ -14,9 +14,9 @@ Further information about the scenario, including data rates, topology and backg ## Contact Plan and Compose File -The contact plan [contacts.ccp](contacts.ccp) is generated from `actual_contacts.csv` via `csv_to_ccp.py`. +The contact plan [contacts.ccp](contacts.ccp) is generated from `actual_contacts.csv` via `csv_to_ccp`. -The compose file [compose.yml](compose.yml) is generated from the same CSV via `csv_to_compose.py`. +The compose file [compose.yml](compose.yml) is generated from the same CSV via `csv_to_compose`. ## Docker: Running the Scenario diff --git a/scenarios/mc/README.md b/scenarios/mc/README.md index 3f45030..309657d 100644 --- a/scenarios/mc/README.md +++ b/scenarios/mc/README.md @@ -12,9 +12,9 @@ Further information about the scenario, including data rates, topology and backg ## Contact Plan and Compose File -The contact plan [contacts.ccp](contacts.ccp) is generated from `actual_contacts.csv` via `csv_to_ccp.py`. +The contact plan [contacts.ccp](contacts.ccp) is generated from `actual_contacts.csv` via `csv_to_ccp`. -The compose file [compose.yml](compose.yml) is generated from the same CSV via `csv_to_compose.py`. +The compose file [compose.yml](compose.yml) is generated from the same CSV via `csv_to_compose`. ## Docker: Running the Scenario