Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .github/workflows/develop.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2020-2025, The OTNS Authors.
# Copyright (c) 2020-2026, The OTNS Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -79,11 +79,9 @@ jobs:
fi
./script/install
- name: Build custom OT node
# FIXME: OT_CMAKE_NINJA_TARGET used to exclude tests, because test_platform.cpp is missing otPlatAssertFail()
run: |
cd ot-rfsim
OT_CMAKE_NINJA_TARGET="ot-cli-ftd ot-cli-mtd" ./script/build -DOT_HISTORY_TRACKER=ON
# ./script/build -DOT_HISTORY_TRACKER=ON # this should be the command above (after the OT fix)
./script/build -DOT_HISTORY_TRACKER=ON
- name: Setup dev environment
run: |
./script/setup-dev
42 changes: 42 additions & 0 deletions GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,45 @@ Done
This shows the "ext" type for node 2, and also the string "(external-node)" instead of the actual node's executable name, which is unknown to OTNS.

In case the externally running node process is interrupted (e.g. by pressing Ctrl+C) or killed, the corresponding node automatically gets removed from the simulation and a warning is logged by OTNS.

## Adding an OpenThread Border Router (OTBR) interfacing to a real network (Optional, for Advanced Use Only)

In OTNS real-time mode, a real OpenThread Border Router (OTBR) can be added to the simulation. The OTBR can route IPv6 between the simulated network and a real network.

Running an OTBR requires the binaries `otbr-agent` (daemon) and `ot-ctl` (CLI interface) to be built, from the project `openthread/ot-br-posix`. The current process for this includes two steps:

1. Use Git to check out the project `openthread/ot-br-posix` into a local directory, located at `../ot-br-posix`. So this directory resides in the same parent directory as this `ot-ns` project directory.
2. Use the build script to build the OTBR correctly (with parameters suitable for simulation):

```bash
cd ot-rfsim
./script/build_otbr
[... build output here ...]
Build of 'otbr-agent' done.
Copy otbr-agent to /usr/local/sbin for use by OTNS? [y/N]
[... next question here ...]
```

Answer 'y' to the two questions to copy the build result. This requires entering the root password (for sudo). It results in the two binaries being installed locally, to be used by OTNS.

To run these, OTNS needs root access (sudo) for these binaries. When starting an OTBR node, OTNS will try to execute both using the `sudo -n` non-interactive invocation. To make this work, both binaries need to be added to the `/etc/sudoers` list by using `sudo visudo` and then adding the below two lines at the end of the file:

```bash
myusername ALL=(ALL) NOPASSWD: SETENV: /usr/local/sbin/otbr-agent
myusername ALL=(ALL) NOPASSWD: /usr/local/bin/ot-ctl
```

Check that the binaries are installed in the given locations: if not, adjust the paths accordingly. If both binaries are available in the user's PATH, OTNS will find them automatically.

Below are examples how OTNS should be run to enable OTBRs in the simulation.

```bash
otns -realtime -otbr-if eth0
otns -realtime
```

The flag `-realtime` specifies that OTNS runs in real-time mode, required to support OTBR and other RCP/Posix based nodes.

The parameter `-otbr-if` specifies the network interface (AIL) that the OTBR should use to connect to the real network. It can be your local Ethernet or Wi-Fi interface, or a virtual network interface set up with Linux network namespaces. If the parameter is omitted, the OTBR will connect to the loopback interface (`lo`) by default. In this case, no external IP communication is possible even though an OTBR can be started.

Once OTNS is running, the CLI command to add an OTBR is `add otbr` to use the default backbone interface as specified above. To use a different interface, use `add otbr if "wifi1"` for example to pick the 'wifi1' interface. This is useful when adding multiple OTBRs in a simulation.
36 changes: 35 additions & 1 deletion cli/CmdRunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,12 @@ func (rt *CmdRunner) execute(cmd *Command, output io.Writer) {
}

func (rt *CmdRunner) executeGo(cc *CommandContext, cmd *GoCmd) {
// verify that we're not in realtime mode.
if rt.sim.GetConfig().Realtime {
cc.errorf("cannot use 'go' commands in -realtime mode")
return
}

// determine duration and desired speed of the Go simulation period.
timeDurToGo, err := time.ParseDuration(cmd.Time)
if cmd.Ever == nil && err != nil {
Expand Down Expand Up @@ -382,6 +388,10 @@ func (rt *CmdRunner) executeAutoGo(cc *CommandContext, cmd *AutoGoCmd) {
}
cc.outputf("%d\n", autoGoInt)
} else {
if rt.sim.GetConfig().Realtime && cmd.Val.Yes == nil {
cc.errorf("cannot set 'autogo' to false in -realtime mode")
return
}
sim.SetAutoGo(cmd.Val.Yes != nil)
}
})
Expand Down Expand Up @@ -445,6 +455,9 @@ func (rt *CmdRunner) executeAddNode(cc *CommandContext, cmd *AddCmd) {
if cmd.Raw != nil {
cfg.IsRaw = true
}
if cmd.BackboneIf != nil {
cfg.NetIfName = cmd.BackboneIf.Name
}

rt.postAsyncWait(cc, func(sim *simulation.Simulation) {
sim.NodeConfigFinalize(&cfg)
Expand Down Expand Up @@ -713,7 +726,7 @@ func (rt *CmdRunner) executeLsNodes(cc *CommandContext, cmd *NodesCmd) {
for _, nodeId := range sim.GetNodes() {
snode, dnode := rt.getNodeById(nodeId)
var line strings.Builder
line.WriteString(fmt.Sprintf("id=%d\ttype=%-6s extaddr=%016x rloc16=%04x x=%-2d\ty=%-3d\tz=%-3d\tstate=%s\tfailed=%v", nodeId, dnode.Type, dnode.ExtAddr, dnode.Rloc16,
line.WriteString(fmt.Sprintf("id=%d\ttype=%-6s extaddr=%016x rloc16=%04x x=%-2d\ty=%-3d\tz=%-3d\tstate=%s\tfailed=%v", nodeId, snode.GetType(), dnode.ExtAddr, dnode.Rloc16,
dnode.X, dnode.Y, dnode.Z, dnode.Role, dnode.IsFailed()))
line.WriteString(fmt.Sprintf("\texe=%s", snode.GetExecutableName()))
cc.outputf("%s\n", line.String())
Expand Down Expand Up @@ -1288,6 +1301,21 @@ func (rt *CmdRunner) executeExe(cc *CommandContext, cmd *ExeCmd) {
ec.Br = cmd.Path
}
cc.outputf("br : %s\n", ec.Br)
case OTBR:
if isSetPath {
ec.OtBr = cmd.Path
}
cc.outputf("otbr: %s\n", ec.OtBr)
case RCP:
if isSetPath {
ec.Rcp = cmd.Path
}
cc.outputf("rcp: %s\n", ec.Rcp)
case HOST:
if isSetPath {
ec.RcpHost = cmd.Path
}
cc.outputf("host: %s\n", ec.RcpHost)
}
return
} else if isSetDefault && !isSetPath && !isSetNodeType && !isSetVersion {
Expand All @@ -1306,12 +1334,18 @@ func (rt *CmdRunner) executeExe(cc *CommandContext, cmd *ExeCmd) {
cc.outputf("ftd: %s\n", ec.Ftd)
cc.outputf("mtd: %s\n", ec.Mtd)
cc.outputf("br : %s\n", ec.Br)
cc.outputf("otbr: %s\n", ec.OtBr)
cc.outputf("matter: %s\n", ec.Matter)
cc.outputf("rcp: %s\n", ec.Rcp)
cc.outputf("host: %s\n", ec.RcpHost)
cc.outputf("Executables search path: %s\n", ec.SearchPathsString())
cc.outputf("Detected FTD path : %s\n", ec.FindExecutable(ec.Ftd))
cc.outputf("Detected MTD path : %s\n", ec.FindExecutable(ec.Mtd))
cc.outputf("Detected BR path : %s\n", ec.FindExecutable(ec.Br))
cc.outputf("Detected OTBR path : %s\n", ec.FindExecutable(ec.OtBr))
cc.outputf("Detected Matter path : %s\n", ec.FindExecutable(ec.Matter))
cc.outputf("Detected RCP path : %s\n", ec.FindExecutable(ec.Rcp))
cc.outputf("Detected RCP Host path : %s\n", ec.FindExecutable(ec.RcpHost))
})
}

Expand Down
16 changes: 13 additions & 3 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Add a node to the simulation and get the node ID.
add <type> [x <x>] [y <y>] [rr <radio-range>] [id <node-id>] [restore] [exe <path>] [v11|v12|v13|v14]
```

The `<type>` can be `router`, `fed`, `med`, `sed`, `ssed`, `br` (Border Router), `wifi` (for a Wi-Fi interferer node), or `matter`. Node ID can be specified using the `id` parameter, otherwise OTNS assigns the next available one. If the `restore` option is specified, the node restores its network configuration from persistent storage.
The `<type>` can be `router`, `fed`, `med`, `sed`, `ssed`, `br` (Border Router), `rcp`, `wifi` (for a Wi-Fi interferer node), or `matter`. Node ID can be specified using the `id` parameter, otherwise OTNS assigns the next available one. If the `restore` option is specified, the node restores its network configuration from persistent storage.

The (advanced) `exe` option can be used to specify a node executable for the new node; either a name only which is then located in the default search paths, or a full abs or rel pathname pointing to the executable to use.

Expand Down Expand Up @@ -254,10 +254,14 @@ The line `Executables search path` lists the paths where the executable of that
ftd: ot-cli-ftd
mtd: ot-cli-mtd
br : ot-cli-ftd_br
rcp: ot-rcp
host: ot-cli
Executables search path: [".", "./ot-rfsim/ot-versions", "./build/bin"]
Detected FTD path : ./ot-rfsim/ot-versions/ot-cli-ftd
Detected MTD path : ./ot-rfsim/ot-versions/ot-cli-mtd
Detected BR path : ./ot-rfsim/ot-versions/ot-cli-ftd_br
Detected RCP path : ./ot-rfsim/ot-versions/ot-rcp
Detected RCP Host path : ./ot-rfsim/ot-versions/ot-cli
Done
>
```
Expand Down Expand Up @@ -299,10 +303,10 @@ Done
#### exe: Change OT executable for particular node type

```shell
exe ( ftd | mtd | br ) ["<path-or-filename-of-executable>"]
exe ( ftd | mtd | br | rcp | host) ["<path-or-filename-of-executable>"]
```

Change the OpenThread (OT) executable, or shell script, for a particular node types as provided in the first argument (ftd, mtd, or br). The path-or-filename is provided in the second argument and will replace the current default executable for that node type. If only the first argument is given, the current executable for this node type will be listed and no change is made. If only a filename is given, without full path, the executable will be located using the search paths listed under `Executables search path`.
Change the OpenThread (OT) executable, or shell script, for a particular node types as provided in the first argument (ftd, mtd, br, rcp or host). The path-or-filename is provided in the second argument and will replace the current default executable for that node type. If only the first argument is given, the current executable for this node type will be listed and no change is made. If only a filename is given, without full path, the executable will be located using the search paths listed under `Executables search path`.

Note that the default executable is used when normally adding a node using the GUI or a command such as `add router x 200 y 200` where the executable is not explictly specified. The "exe" argument of the "add" command will however override the default executable always, for example as in the command `add router x 200 y 200 exe "./my-override-ot-cli-ftd"` .

Expand All @@ -317,10 +321,14 @@ Done
ftd: ./my-ot-cli-ftd
mtd: ot-cli-mtd
br : ./br-script.sh
rcp: ot-rcp
host: ot-cli
Executables search path: [".", "./ot-rfsim/ot-versions", "./build/bin"]
Detected FTD path : ./my-ot-cli-ftd
Detected MTD path : ./ot-rfsim/ot-versions/ot-cli-mtd
Detected BR path : ./br-script.sh
Detected RCP path : ./ot-rfsim/ot-versions/ot-rcp
Detected RCP Host path : ./ot-rfsim/ot-versions/ot-cli
Done
> exe mtd
mtd: ot-cli-mtd
Expand Down Expand Up @@ -349,6 +357,8 @@ go <duration> [speed <particular-speed>]

Simulate for a specified time in seconds or indefinitely (duration=`ever`). It is required in `-autogo=false` mode to advance the simulation. In `-autogo=true` mode, it can be optionally used to advance the simulation quickly by the given time. For example, in a paused simulation to quickly advance 64 us, 1 ms, 10 seconds, or an hour. The optional `speed` argument can be given to do the simulation at that speed e.g. to see the animations and log output better. The `duration` argument can optionally end with a time unit suffix: `us`, `ms`, `s`, `m`, or `h`.

In `-realtime` mode this command is not available and will always return an error.

```bash
> go 1
Done
Expand Down
13 changes: 10 additions & 3 deletions cli/ast.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2020-2025, The OTNS Authors.
// Copyright (c) 2020-2026, The OTNS Authors.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -307,12 +307,13 @@ type AddCmd struct {
Restore *RestoreFlag `| @@` //nolint
Version *ThreadVersion `| @@` //nolint
Raw *RawFlag `| @@` //nolint
Executable *ExecutableFlag `| @@ )*` //nolint
Executable *ExecutableFlag `| @@` //nolint
BackboneIf *BackboneIfFlag `| @@ )*` //nolint
}

// noinspection GoVetStructTag
type NodeTypeOrRole struct {
Val string `@("router"|"reed"|"fed"|"med"|"sed"|"ssed"|"br"|"mtd"|"ftd"|"wifi"|"matter")` //nolint
Val string `@("router"|"reed"|"fed"|"med"|"sed"|"ssed"|"br"|"otbr"|"mtd"|"ftd"|"wifi"|"matter"|"rcp"|"host")` //nolint
}

// noinspection GoVetStructTag
Expand Down Expand Up @@ -346,6 +347,12 @@ type RawFlag struct {
Dummy struct{} `"raw"` //nolint
}

// noinspection GoVetStructTag
type BackboneIfFlag struct {
Dummy struct{} `"if"` //nolint
Name string `@String` //nolint
}

// noinspection GoVetStructTag
type MaxSpeedFlag struct {
Dummy struct{} `( "max" | "inf")` //nolint
Expand Down
4 changes: 4 additions & 0 deletions cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ func TestParseBytes(t *testing.T) {
assert.True(t, cmd.Add.RadioRange.Val == 1234)
assert.Nil(t, parseBytes([]byte("add router x 1 y 2 id 3 rr 1234"), &cmd))
assert.Nil(t, parseBytes([]byte("add router rr 1234 id 3 y 2 x 1"), &cmd))
assert.Nil(t, parseBytes([]byte(`add otbr if "eth0"`), &cmd))
assert.True(t, cmd.Add.BackboneIf != nil && cmd.Add.BackboneIf.Name == "eth0")
assert.Nil(t, parseBytes([]byte("add otbr"), &cmd))
assert.True(t, cmd.Add.BackboneIf == nil)

assert.Nil(t, parseBytes([]byte("autogo"), &cmd))
assert.NotNil(t, cmd.AutoGo)
Expand Down
5 changes: 4 additions & 1 deletion dispatcher/FailureCtrl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ type mockDispatcherCallback struct {
func (m mockDispatcherCallback) OnUartWrite(nodeid NodeId, data []byte) {
}

func (m mockDispatcherCallback) OnLogWrite(nodeid NodeId, data []byte) {
func (m mockDispatcherCallback) OnUartDisconnected(nodeid NodeId) {
}

func (m mockDispatcherCallback) OnLogWrite(nodeid NodeId, data []byte, isFromHost bool) {
}

func (m mockDispatcherCallback) OnNextEventTime(nextTimeUs uint64) {
Expand Down
Loading
Loading