Skip to content

Commit

Permalink
Support TCP and UDP connectivity testing from miniccc (sandia-minimeg…
Browse files Browse the repository at this point in the history
…a#1457)

minimega, miniccc, and ron were updated to support executing tcp/udp
connectivity tests directly from miniccc agents.

ron was updated to include a new `ConnTest` command struct that
encapsulates the endpoint to test against, how long to wait, and what
UDP packet to send (if necessary).

miniccc was updated to include a handler for the new `ConnTest` command
that simply tries to dial the endpoint (in the case of TCP), and if
necessary write the UDP packet to the socket (in the case of UDP).

minimega was updated to include support for a new "cc test-conn"
command, as well as adding a "connectivity" column to the "cc commands"
table.

Documentation for minimega's "cc" command was also updated to include
details and examples of how to use the new "test-conn" command.

"cc test-conn" allows users to test network connectivity from a guest to the
given IP or domain name and port. The wait timeout should be specified as a Go
duration string (e.g. 5s, 1m). If "udp" is used, a "base64 udp packet" that will
generate a valid response must be specified. Results of the test will be written
to the command's STDOUT file, whether it passed or failed. An example test is as
follows:
	cc test-conn tcp 10.0.0.68 443 wait 10s
  • Loading branch information
activeshadow authored Oct 6, 2021
1 parent dd04c33 commit cb60299
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 6 deletions.
55 changes: 53 additions & 2 deletions src/miniccc/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@ package main
import (
"bufio"
"bytes"
log "minilog"
"fmt"
"net"
"net/url"
"os/exec"
"path/filepath"
"ron"
"strings"
"time"

log "minilog"
"ron"
)

func processCommand(cmd *ron.Command) {
Expand Down Expand Up @@ -44,6 +49,10 @@ func processCommand(cmd *ron.Command) {
resp.Stdout, resp.Stderr = runCommand(cmd.Stdin, cmd.Stdout, cmd.Stderr, cmd.Command, cmd.Background)
}

if cmd.ConnTest != nil {
resp.Stdout, resp.Stderr = testConnect(cmd.ConnTest)
}

if len(cmd.FilesRecv) != 0 {
sendFiles(cmd.ID, cmd.FilesRecv)
}
Expand Down Expand Up @@ -267,3 +276,45 @@ func killAll(needle string) {
}
}
}

func testConnect(test *ron.ConnTest) (string, string) {
log.Debug("testConnect called with %v", *test)

uri, err := url.Parse(test.Endpoint)
if err != nil {
return "", fmt.Sprintf("unable to parse test URI %s: %v", test.Endpoint, err)
}

timeout := time.After(test.Wait)

for {
select {
case <-timeout:
return fmt.Sprintf("%s | fail", uri.Host), ""
default:
if conn, err := net.DialTimeout(uri.Scheme, uri.Host, 500*time.Millisecond); err == nil {
defer conn.Close()

if uri.Scheme == "udp" {
if err := conn.SetDeadline(time.Now().Add(500 * time.Millisecond)); err != nil {
return fmt.Sprintf("%s | fail", uri.Host), ""
}

if len(test.Packet) > 0 {
if _, err := conn.Write(test.Packet); err != nil {
return fmt.Sprintf("%s | fail", uri.Host), ""
}
}

buf := make([]byte, 1)

if _, err := conn.Read(buf); err != nil {
return fmt.Sprintf("%s | fail", uri.Host), ""
}
}

return fmt.Sprintf("%s | pass", uri.Host), ""
}
}
}
}
72 changes: 68 additions & 4 deletions src/minimega/cc_cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@
package main

import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"minicli"
log "minilog"
"net"
"os"
"ron"
"sort"
"strconv"
"strings"
"syscall"
"time"

"minicli"
log "minilog"
"ron"
)

type ccMount struct {
Expand Down Expand Up @@ -84,6 +87,23 @@ without arguments displays the existing mounts. Users can use "clear cc mount"
to unmount the filesystem of one or all VMs. This should be done before killing
or stopping the VM ("clear namespace <name>" will handle this automatically).
"cc test-conn" allows users to test network connectivity from a guest to the
given IP or domain name and port. The wait timeout should be specified as a Go
duration string (e.g. 5s, 1m). If "udp" is used, a "base64 udp packet" that will
generate a valid response must be specified. Results of the test will be written
to the command's STDOUT file, whether it passed or failed. An example test is as
follows:
cc test-conn tcp 10.0.0.68 443 wait 10s
If the above test passes, STDOUT for the command will contain the following:
10.0.0.68:443 | pass
If it fails, STDOUT will instead contain the following:
10.0.0.68:443 | fail
For more documentation, see the article "Command and Control API Tutorial".`,
Patterns: []string{
"cc",
Expand Down Expand Up @@ -112,6 +132,8 @@ For more documentation, see the article "Command and Control API Tutorial".`,

"cc <delete,> <command,> <id or prefix or all>",
"cc <delete,> <response,> <id or prefix or all>",

"cc <test-conn,> <tcp,udp> <ip or fqdn> <port> wait <timeout> [base64 udp packet]",
},
Call: wrapBroadcastCLI(cliCC),
},
Expand Down Expand Up @@ -169,6 +191,7 @@ var ccCliSubHandlers = map[string]wrappedCLIFunc{
"send": cliCCFileSend,
"tunnel": cliCCTunnel,
"listen": cliCCListen,
"test-conn": cliCCTestConn,
}

func cliCC(ns *Namespace, c *minicli.Command, resp *minicli.Response) error {
Expand Down Expand Up @@ -568,7 +591,7 @@ func cliCCClients(ns *Namespace, c *minicli.Command, resp *minicli.Response) err
func cliCCCommand(ns *Namespace, c *minicli.Command, resp *minicli.Response) error {
resp.Header = []string{
"id", "prefix", "command", "responses", "background",
"sent", "received", "level", "filter",
"sent", "received", "connectivity", "level", "filter",
}
resp.Tabular = [][]string{}

Expand All @@ -593,6 +616,12 @@ func cliCCCommand(ns *Namespace, c *minicli.Command, resp *minicli.Response) err
fmt.Sprintf("%v", v.FilesRecv),
}

if v.ConnTest != nil {
row = append(row, fmt.Sprintf("%s (%v wait)", v.ConnTest.Endpoint, v.ConnTest.Wait))
} else {
row = append(row, "")
}

if v.Level != nil {
row = append(row, v.Level.String())
} else {
Expand Down Expand Up @@ -642,6 +671,41 @@ func cliCCListen(ns *Namespace, c *minicli.Command, resp *minicli.Response) erro
return ns.ccServer.Listen(port)
}

func cliCCTestConn(ns *Namespace, c *minicli.Command, resp *minicli.Response) error {
if _, err := strconv.Atoi(c.StringArgs["port"]); err != nil {
return fmt.Errorf("invalid port %s: %v", c.StringArgs["port"], err)
}

wait, err := time.ParseDuration(c.StringArgs["timeout"])
if err != nil {
return fmt.Errorf("invalid wait duration %s: %v", c.StringArgs["timeout"], err)
}

scheme := "tcp"
if c.BoolArgs["udp"] {
scheme = "udp"
}

test := ron.ConnTest{
Endpoint: fmt.Sprintf("%s://%s:%s", scheme, c.StringArgs["ip"], c.StringArgs["port"]),
Wait: wait,
}

if packet := c.StringArgs["base64"]; len(packet) > 0 {
var err error

test.Packet, err = base64.StdEncoding.DecodeString(packet)
if err != nil {
return fmt.Errorf("unable to decode base64 packet string: %v", err)
}
}

cmd := &ron.Command{ConnTest: &test}

resp.Data = ns.NewCommand(cmd)
return nil
}

// cliCCMount needs to collect mounts from both the local ccMounts for the
// namespace and across the cluster.
func cliCCMount(c *minicli.Command, respChan chan<- minicli.Responses) {
Expand Down
16 changes: 16 additions & 0 deletions src/ron/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
log "minilog"
"strings"
"time"
)

type Filter struct {
Expand Down Expand Up @@ -37,6 +38,9 @@ type Command struct {
// Files to transfer back to the master
FilesRecv []string

// Connectivity test to execute
ConnTest *ConnTest

// PID of the process to signal, -1 signals all processes
PID int

Expand Down Expand Up @@ -73,6 +77,12 @@ type Response struct {
Stderr string
}

type ConnTest struct {
Endpoint string
Wait time.Duration
Packet []byte
}

func (f *Filter) String() string {
if f == nil {
return ""
Expand Down Expand Up @@ -128,10 +138,16 @@ func (c *Command) Copy() *Command {
c2.Filter = new(Filter)
*c2.Filter = *c.Filter
}

if c.Level != nil {
c2.Level = new(log.Level)
*c2.Level = *c.Level
}

if c.ConnTest != nil {
c2.ConnTest = new(ConnTest)
*c2.ConnTest = *c.ConnTest
}

return c2
}

0 comments on commit cb60299

Please sign in to comment.