-
- A little detour:
- Namespaces
- (Feel free to skip this section if you are
only interested in learning about Gont)
-
-
-
- What are Linux namespaces?
-
-
-
- They..
-
- - partition kernel resources
from a process-perspective.
- - power most of Linux containerization tools.
- - appear in many Linux subsystems.
- - are used by many sandboxing solutions in browsers.
-
-
-
-
- Available Linux namespaces
-
- - mnt: Mounts
- - pid: Process IDs
- - net: Networking
- - ipc: Interprocess Communication
- - uts: Unix Timesharing (hostname)
- - user: User identification & privileges
- - cgroup: Process Control Groups
- - time: System time
-
-
-
-
- Buts what exactly is a namespace?
-
- - Can be identified by a file descriptor
- - A namespace can have multiple processes assigned to it
- - It lives as long as there is still at least one remaining process
- - Child processes inherit parent namespaces
-
-
-
-
- How do I create a namespace?
-
-
-
-
- unshare(2)
-
- func main() {
- err := syscall.Unshare(syscall.CLONE_NEWNS);
- }
-
-
-
-
- How do I create a namespace?
-
- static int child(void *arg) {
- struct utsname uts;
-
- sethostname(arg, "ernie")
-
- uname(&uts)
- printf("nodename in child: %s\n", uts.nodename);
-
- return 0;
- }
-
- int main() {
- struct utsname uts;
-
- /* Allocate stack for child */
- char *stack = malloc(STACK_SIZE);
- if (stack == NULL)
- return -1;
-
- /* Start new kernel task in new UTS namespace */
- pid_t child_pid = clone(child, stack + STACK_SIZE, CLONE_NEWUTS | SIGCHLD, NULL);
-
- /* Output hostname */
- uname(&uts)
- printf("nodename in parent: %s\n", uts.nodename);
- }
-
-
-
-
- How can I share a namespace with other processes?
-
-
-
-
- Joining namespace of another process by /proc/{pid}/ns/*
-
- fd := syscall.Open("/proc/1234/ns/uts", syscall.O_RDONLY);
-
- err := unix.Setns(fd, syscall.CLONE_NEWUTS);
-
- Note: Can only set a single namespace per netns(2)
invocation.
-
-
-
- Joining namespace of another process by pidfd_open(2)
-
- pid_t pid = 1234;
- int fd = pidfd_open(pid, 0);
-
- setns(fd, CLONE_NEWUTS | CLONE_NEWNET);
-
- Note: Can only set a multiple namespaces per netns(2)
invocation.
-
-
-
- Persisting namespaces
-
- err := syscall.Mount("/proc/self/ns/uts",
- "/home/acs/my_uts_namespace", "", syscall.MS_BIND, nil);
-
- In other process
-
- fd := syscall.Open("/home/acs/my_uts_namespace", syscall.O_RDONLY);
-
- err := unix.Setns(fd, syscall.CLONE_NEWUTS);
-
-
-
-
-
-
- Two directly connected hosts
-
- import g "github.com/stv0g/gont/v2/pkg"
- import o "github.com/stv0g/gont/v2/pkg/options"
-
- ...
-
- n, _ := g.NewNetwork("mynet")
-
- h1, _ := n.AddHost("h1")
- h2, _ := n.AddHost("h2")
-
- n.AddLink(
- g.NewInterface("eth0", h1, o.AddressIP("10.0.0.1/24")),
- g.NewInterface("eth0", h2, o.AddressIP("10.0.0.2/24")),
- )
-
- h1.Ping(h2)
-
- (We use g
and o
throughout these examples as import aliases)
-
-
-
- Lets add a L2 switch
-
- sw, _ := n.AddSwitch("sw")
-
- h1, _ := n.AddHost("h1",
- g.NewInterface("eth0", sw,
- o.AddressIP("10.0.0.1/24")))
-
- h2, _ := n.AddHost("h2",
- g.NewInterface("eth0", sw,
- o.AddressIP("10.0.0.2/24")))
-
- h1.Ping(h2)
-
-
-
-
- How about a L3 router?
-
- sw1, _ := n.AddSwitch("sw1")
- sw2, _ := n.AddSwitch("sw2")
-
- h1, _ := n.AddHost("h1", g.NewInterface("eth0", sw1,
- o.AddressIP("10.0.0.2/24")))
- h2, _ := n.AddHost("h2", g.NewInterface("eth0", sw2,
- o.AddressIP("10.0.1.2/24")))
-
- n.AddRouter("r1",
- g.NewInterface("eth0", sw, o.AddressIP("10.0.0.1/24")),
- g.NewInterface("eth1", sw, o.AddressIP("10.0.1.1/24"))
- )
-
- h1.Ping(h2)
-
-
-
-
- Lets do some evil NATing 😈
-
- sw1, _ := n.AddSwitch("sw1")
- sw2, _ := n.AddSwitch("sw2")
-
- h1, _ := n.AddHost("h1", g.NewInterface("eth0", sw1,
- o.AddressIP("10.0.0.2/24")))
- h2, _ := n.AddHost("h2", g.NewInterface("eth0", sw2,
- o.AddressIP("10.0.1.2/24")))
-
- n.AddNAT("n1",
- g.NewInterface("eth0", sw, o.SouthBound,
- o.AddressIP("10.0.0.1/24")),
- g.NewInterface("eth1", sw, o.NorthBound,
- o.AddressIP("10.0.1.1/24")))
-
- h1.Ping(h2)
-
-
-
-
- How about a whole chain of routers?
-
- var firstSwitch *g.Switch = n.AddSwitch("sw0")
- var lastSwitch *g.Switch = nil
-
- for i := 1; i < 100; i++ {
- swName := fmt.Printf("sw%d", i)
- rtrName := fmt.Printf("r%d", i)
-
- newSwitch, _ := n.AddSwitch(swName)
-
- n.AddRouter(rtrName,
- g.NewInterface("eth0", lastSwitch,
- o.AddressIP("10.0.0.1/24")),
- g.NewInterface("eth1", newSwitch,
- o.AddressIP("10.0.1.1/24"))
- )
-
- lastSwitch = newSwitch
- }
-
- h1, _ := n.AddHost("h1", g.NewInterface("eth0", firstSwitch,
- o.AddressIP("10.0.0.2/24")))
- h2, _ := n.AddHost("h2", g.NewInterface("eth0", lastSwitch,
- o.AddressIP("10.0.1.2/24")))
-
- h1.Ping(h2)
-
-
-
-
- Running your application
- Inside the network namespaces / hosts
-
-
-
- exec.Cmd API
-
- // Get a exec.Cmd-like struct
- cmd := h1.Command("ping", "h2")
- out, err := cmd.CombinedOutput()
-
- // Directly run a simple process synchronously
- cmd, err := h1.Run("ping", "h2")
-
- // Directly start asynchronously
- cmd, err := h1.Start("ping", "h2")
-
- time.Sleep(5 * time.Second)
-
- err = cmd.Process.Signal(os.Interrupt)
- cmd.Wait()
-
- The g.Node
type implements an API similar to the one provided by Go's exec
package.
-
-
-
- Pass options
-
-
- import co "github.com/stv0g/gont/v2/pkg/options/cmd"
-
- outb := &bytes.Buffer{}
-
- cmd := h1.Command("ping", "1.1.1.1",
- co.DisableASLR(true),
- co.Dir("/custom/working/dir"),
- co.EnvVar("DEBUG", "1"),
- co.Stdin(...), // pass any io.Reader
- co.Stdout(outb), // pass any io.Writer (can be repeated)
- co.Stderr(...), // pass any io.Writer (can be repeated)
- )
-
- print(outb.String())
-
-
-
-
- Pass non-string arguments
-
-
- ip := net.ParseIP("1.1.1.1")
-
- cmd := h1.Command("ping", "-c", 10, "-i", 0.1, ip)
-
-
-
-
- Go functions
-
-
- h1.RunFunc(func() {
- r := http.Get("http://h2:8080")
- io.Copy(os.Stdout, r.Body)
- })
-
-
-
- Call a function inside a network namespace
- of host h1
but still
in the same process
- so you can use channels and access global variables.
-
-
-
- ⚠️ Warning: Spawning Goroutines from within the callback
is only indirectly supported:
-
-
-
- h1.RunFunc(func() {
- go h1.RunFunc(func() { ... })
- })
-
-
-
-
- Go packages
-
- cmd, err := h1.RunGo("test/prog.go", "arg1")
-
-
-
-
- Distributed Tracing
- User defined events, Log messages
-
-
-
- A trace event
-
- Gont supports collecting trace events from all processes running in a distributed system which can carry the following information. Gont orders trace events by time and saves them to different destinations for analysis.
-
-
- type Event struct {
- Timestamp time.Time // Timestamp when the event occurred
- Type string // Either: 'log', ́'trace', 'break' & ́ watchpoint'
- Level uint8 // Log level
- Message string // A human readable description
- Source string // Logger name
- PID int
- Function string
- File string
- Line int
- Args []any
- Data any // User defined data
- }
-
-
-
-
- Write to trace events to
-
- - JSON files
- - Go channels
- - Go callbacks
- - Packet captures
-
-
-
-
- Create a tracer
-
-
- import "github.com/stv0g/gont/v2/trace"
- import to "github.com/stv0g/gont/v2/options/trace"
-
- c := g.NewCapture(...)
- f, _ := os.OpenFile(...)
- ch := make(chan trace.Event)
-
- t := g.NewTracer(
- to.ToFile(f)
- to.ToFilename("trace.log"),
- to.ToChannel(ch),
- to.ToCapture(c),
- to.Callback(func(e trace.Event) { ... }))
-
- t.Start()
-
-
-
-
- Attach the tracer
-
- Trace all processes started by nodes of this network
-
- n, _ := g.NewNetwork("", t)
-
-
- Trace all processes started by a node
-
- h1 := n.NewHost("h1", t)
-
-
- Trace a single process
-
- h1.RunGo("test/main.go", t)
-
-
-
-
-
- Trace with trace package
-
- import "github.com/stv0g/gont/v2/pkg/trace"
-
- someData := map[string]string{"Hello": "World"}
- count := 42
-
- trace.Start(0)
-
- trace.PrintfWithData(someData, "Count is: %d", count)
- trace.Print("Another message")
-
- trace.Stop()
-
-
- Works from:
-
- - Gont process itself
- - Any process spawned via Gont's
- Host.{Command,Run,RunGo,Start,StartGo}(...)
functions
-
-
-
-
- Trace via slog structured logging package
-
-
- import "golang.org/x/exp/slog"
- import "github.com/stv0g/gont/v2/pkg/trace"
-
- // Create a slog handler which emits trace events
- handler := trace.NewTraceHandler(slog.HandlerOptions{})
-
- // Add the tracing option which emits a trace event for each log message
- logger := slog.New(handler)
-
-
-
- Each log message emits a trace event which includes the log message, filename, line number as well function name and more.
- Any fields passed to to zap structured logger are included in the Data
field of the Event
structure.
-
-
-
-
- Trace via zap logging package
-
-
- import "go.uber.org/zap"
- import "github.com/stv0g/gont/v2/pkg/trace"
-
- // Add the tracing option which emits a trace event for each log message
- logger := zap.NewDevelopment(trace.Log())
-
- // Add the caller info which gets also included in the trace event
- logger = logger.WithOptions(zap.AddCaller())
-
- // Give the logger some name which is added as the Source field to the trace event
- logger = logger.Named("my-test-logger")
-
-
-
- Each log message emits a trace event which includes the log message, filename, line number as well function name and more.
- Any fields passed to to zap structured logger are included in the Data
field of the Event
structure.
-
-
-
-
- Distributed Debugging
- powered by Delve
-
-
-
- Introduction
-
- Gont can manage Delve debugger instances attached to spawned sub-processes.
- These Delve instances can be used to debug or trace multiple applications in a distributed system simultaneously.
- Users can either use DAP-compatible IDEs like VScode to attach to those processes, or record tracepoint data generated by Delve break- & watchpoints to a PCAPng file.
-
-
- Its integration with the packet capture and event tracing feature of Gont allows the user to even streaming tracepoint data interleaved with packet and other tracing data in real-time to Wireshark.
-
-
-
-
- Create a debugger
-
-
- import do "github.com/stv0g/gont/v2/pkt/options/debug"
-
- t := g.NewTracer(...)
-
- d := g.NewDebugger(
- // ... Tracepoints are defined her
- do.BreakOnEntry(true),
- do.ListenAddr("tcp:[::]:1234"), // DAP listening socket
- do.ToTracer(t))
-
-
-
-
- Attach a debugger
-
- Debug all processes started by nodes of this network:
-
- n, _ := g.NewNetwork("", d)
-
-
- Debug all processes started by a node:
-
- h1 := n.NewHost("h1", d)
-
-
- Debug a single process:
-
- h1.RunGo("test/main.go", d)
-
-
- (Like for the event tracing)
-
-
-
- Define tracepoints
- Break-, watchpoint location
-
-
- import "github.com/go-delve/delve/service/api"
- import do "github.com/stv0g/gont/v2/pkt/options/debug"
-
- d := g.NewDebugger(
- g.NewTracepoint(
- do.Disabled(false),
- do.Name("tp1"),
- do.Message("A trace message with evaluated {placeholders}"),
- do.Location(...), // A Delve locspec
- do.Address(0x12312321),
- do.File("main.go"),
- do.Line(12),
- do.FunctionName("main.main"),
- do.FunctionNameRegex("main\.(main|setupLogger)"),
- do.Condition("i % 100 == 0"),
- do.HitCondition("> 100"),
- do.HitConditionPerGoroutine(true),
- do.Watch("p", api.WatchRead|api.WatchWrite),
- ),
- ...
- )
-
-
-
-
- Define tracepoints
- Gathering of breakpoint information
-
-
- import do "github.com/stv0g/gont/v2/pkt/options/debug"
-
- d := g.NewDebugger(
- g.NewTracepoint(
- ...
- do.Variable("j"),
- do.Goroutine(true),
- do.Stacktrace(10),
- do.LoadLocals(...),
- do.LoadArguments(
- do.FollowPointers(true),
- do.MaxVariableRecurse(3),
- do.MaxStringLen(128),
- do.MaxArrayValues(128),
- do.MaxStructFields(32),
- )
- )
- )
-
-
-
-
- VSCode Integration
-
- Gont generates VS Code launch compound configurations based on the active debugging sessions.
-
-
- d.WriteVSCodeConfigs("", false)
-
-
-
- Topology factories (WIP)
-
- createHost := func(pos int) (*g.Host. error) {
- return n.AddHost(fmt.Sprintf("h%d", pos))
- }
-
- linkHosts := func(a, b *g.Node) error {
- _, err := n.AddRouter(fmt.Sprintf("r%d", pos),
- g.NewInterface("eth0", a, o.AddressIPv4(10, 0, 0, a.Position, 24),
- g.NewInterface("eth1", b, o.AddressIPv4(10, 0, 0, b.Position, 24)
- )
- return err
- }
-
- topo.Linear(n, 100, createHost, linkHosts)
-
- n.Nodes["h0"].Traceroute(n.Nodes["h99"])
-
-