Skip to content

Module TiNet

sat edited this page Aug 15, 2026 · 11 revisions

Module: TiNET

This page describes the TiNET module for dot2net.

What is TiNET?

TiNET (Tiny Network) is a lightweight container-based network emulation tool. It uses Docker containers connected via Linux network namespaces and veth pairs to create virtual network topologies.

What dot2net Generates

The TiNET module generates spec.yaml, the specification file for TiNET:

nodes:
  - name: r1
    image: quay.io/frrouting/frr:8.5.4
    interfaces: [{name: eth0, type: direct, args: r2#eth0}]
    mounts: [$PWD/r1/etc/frr/frr.conf:/etc/frr/frr.conf, $PWD/r1/etc/frr/daemons:/etc/frr/daemons]

node_configs:
  - name: r1
    cmds:
      - cmd: vtysh -b

Generated Sections

Section Source Description
nodes DOT nodes Node definitions with image, interfaces, mounts
switches deploy: platform nodes Shared media TiNET realizes as bridges. The section is left out entirely when a topology has none
node_configs startup block Commands to run on each node

Automatic Features

  • interfaces: Automatically generated from DOT edge definitions
  • mounts: Generated from FileDefinitions that have path set. They start with $PWD/: TiNET passes the string to docker run -v, which refuses a relative source, and the output of tinet up is meant to be piped to a shell that expands it — against the directory the lab is brought up from, which is where the spec file sits
  • cmds: Generated from user-defined startup config block

One spec file per machine

When a topology declares worker groups, the spec file becomes group-scoped: each machine gets its own, holding its nodes and the links it can wire itself. A link that leaves a machine appears in neither, since no spec file can make it — see Placing nodes on machines. Mount paths become relative to the machine's directory.

Entry script

module_config:
  tinet:
    generate_scripts: true

writes tinet.sh, which carries the parts that are easy to get wrong: TiNET brings a lab up in two steps (up creates the nodes, conf applies node_configs), its output is a shell script meant to be piped, and it prints one line that is not a command when an interface attaches to a switch — which the pipe has to drop or the shell stops there.

See Command Reference.

Required Parameters

Each node TiNET deploys must have this parameter defined (a node with deploy: none has none to run, and a deploy: platform node is a bridge):

Parameter Description Example
image Container image quay.io/frrouting/frr:8.5.4

Define this in your NodeClass:

nodeclass:
  - name: router
    interface_policy: [p2p]   # addresses for this node's interfaces
    params: [lo]              # and ip_loopback for the node itself
    values:
      image: quay.io/frrouting/frr:8.5.4

Optional: startup Config Block

The startup config block defines commands to run inside the container. This is used to generate the cmds section in node_configs.

nodeclass:
  - name: router
    config:
      - name: startup
        template:
          - "vtysh -b"
          - "ip addr add {{ .ip_loopback }}/32 dev lo"

Behavior: every node gets a node_configs entry. A node with no startup commands gets one with an empty cmds:, which TiNET accepts. (containerlab differs here: its exec: section is left out when there is nothing to run.)

Optional: File Mounts

Files with path defined in FileDefinition are automatically mounted into containers:

file:
  - name: frr.conf
    path: /etc/frr/frr.conf    # This triggers mount generation
  - name: daemons
    path: /etc/frr/daemons

Generated mounts:

mounts: [$PWD/r1/etc/frr/frr.conf:/etc/frr/frr.conf, $PWD/r1/etc/frr/daemons:/etc/frr/daemons]

Complete Example

input.yaml

name: ospf_lab
module:
  - tinet
  - frr

file:
  - name: frr.conf
    path: /etc/frr/frr.conf
  - name: daemons
    path: /etc/frr/daemons

layer:
  - name: ip
    default_connect: true
    policy:
      - name: p2p
        range: 10.0.0.0/16
        prefix: 30
      - name: lo
        type: loopback
        range: 10.255.0.0/24

nodeclass:
  - name: router
    interface_policy: [p2p]   # addresses for this node's interfaces
    params: [lo]              # and ip_loopback for the node itself
    values:
      image: quay.io/frrouting/frr:8.5.4
    config:
      - file: frr.conf
        template:
          - "hostname {{ .name }}"
          - "!"
          - "router ospf"
          - " router-id {{ .ip_loopback }}"
      - file: daemons
        sourcefile: ./daemons
      - name: startup
        template:
          - "vtysh -b"

input.dot

graph  {
  r1 [class="router"]
  r2 [class="router"]
  r1 -- r2
}

Generated spec.yaml

nodes:
  - name: r1
    image: quay.io/frrouting/frr:8.5.4
    interfaces: [{name: eth0, type: direct, args: r2#eth0}]
    mounts: [$PWD/r1/etc/frr/frr.conf:/etc/frr/frr.conf, $PWD/r1/etc/frr/daemons:/etc/frr/daemons]
  - name: r2
    image: quay.io/frrouting/frr:8.5.4
    interfaces: [{name: eth0, type: direct, args: r1#eth0}]
    mounts: [$PWD/r2/etc/frr/frr.conf:/etc/frr/frr.conf, $PWD/r2/etc/frr/daemons:/etc/frr/daemons]

node_configs:
  - name: r1
    cmds:
      - cmd: vtysh -b
  - name: r2
    cmds:
      - cmd: vtysh -b

Running the Lab

dot2net build -c input.yaml input.dot

tinet up -c spec.yaml   | grep -v '<->' | sudo sh
tinet conf -c spec.yaml | grep -v '<->' | sudo sh
tinet down -c spec.yaml | grep -v '<->' | sudo sh

The grep is not optional: TiNET prints one line that is not a command when an interface attaches to a switch, and the shell stops there without it.

With an entry script

module_config.tinet.generate_scripts: true (off by default) writes tinet.sh, which carries the two-step deploy and that pipe for you:

sudo ./tinet.sh deploy      # up and conf
sudo ./tinet.sh exec r1 ip addr
sudo ./tinet.sh destroy     # teardown, collect, then down

A topology that collects files or has teardown commands needs the script for them to happen. See Command Reference.

Comparison with Containerlab

Feature TiNET Containerlab
Complexity Simpler, lightweight More features
Node types Docker containers Multiple kinds (SR Linux, cEOS, etc.)
Execution Shell script output Direct deployment
Interface naming dot2net's own, eth0 by default the same. Kathara is the one that fixes it

See Also

Clone this wiki locally