m/test: implement SOCKS proxy in cluster tests
This uses the new socksproxy package to run a proxy server in the
nanoswitch, and uses it within tests to access the test cluster's nodes.
The cluster test code (and nanoswitch) still forward traffic to the
first node, but this will be gradually removed as SOCKS support is
implemented in metroctl and the debug tool. Forwards from host ports to
different node can then be implemented as part of the dbg tool (instead
of the cluster launch code) to maintain a simple interface during debug
and development.
We also use the opportunity to make the non-cluster launch code not
Metropolis specific (by removing an assumption that all ports on all
nodes are Metropolis ports). In the long term, we will probably remove
non-cluster launches entirely (or further turn this code into just being
a 'launch qemu' wrapper).
Change-Id: I9b321bde95ba74fbfaa695eaaad8f9974aba5372
Reviewed-on: https://review.monogon.dev/c/monogon/+/648
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/metropolis/test/nanoswitch/BUILD b/metropolis/test/nanoswitch/BUILD
index 74f2ddf..a3163f5 100644
--- a/metropolis/test/nanoswitch/BUILD
+++ b/metropolis/test/nanoswitch/BUILD
@@ -3,7 +3,10 @@
go_library(
name = "nanoswitch_lib",
- srcs = ["nanoswitch.go"],
+ srcs = [
+ "nanoswitch.go",
+ "socks.go",
+ ],
importpath = "source.monogon.dev/metropolis/test/nanoswitch",
visibility = ["//visibility:private"],
deps = [
@@ -11,6 +14,7 @@
"//metropolis/node/core/network/dhcp4c",
"//metropolis/node/core/network/dhcp4c/callback",
"//metropolis/pkg/logtree",
+ "//metropolis/pkg/socksproxy",
"//metropolis/pkg/supervisor",
"//metropolis/test/launch",
"@com_github_google_nftables//:nftables",
diff --git a/metropolis/test/nanoswitch/nanoswitch.go b/metropolis/test/nanoswitch/nanoswitch.go
index de04a42..5cc2077 100644
--- a/metropolis/test/nanoswitch/nanoswitch.go
+++ b/metropolis/test/nanoswitch/nanoswitch.go
@@ -20,7 +20,10 @@
// served by a built-in DHCP server. Traffic from that network to the
// SLIRP/external network is SNATed as the host-side SLIRP ignores routed
// packets.
-// It also has built-in userspace proxying support for debugging.
+//
+// It also has built-in userspace proxying support for accessing the first
+// node's services, as well as a SOCKS proxy to access all nodes within the
+// network.
package main
import (
@@ -312,6 +315,7 @@
supervisor.Run(ctx, "proxy-dbg1", userspaceProxy(net.IPv4(10, 1, 0, 2), common.DebugServicePort))
supervisor.Run(ctx, "proxy-k8s-api1", userspaceProxy(net.IPv4(10, 1, 0, 2), common.KubernetesAPIPort))
supervisor.Run(ctx, "proxy-k8s-api-wrapped1", userspaceProxy(net.IPv4(10, 1, 0, 2), common.KubernetesAPIWrappedPort))
+ supervisor.Run(ctx, "socks", runSOCKSProxy)
supervisor.Signal(ctx, supervisor.SignalHealthy)
supervisor.Signal(ctx, supervisor.SignalDone)
return nil
diff --git a/metropolis/test/nanoswitch/socks.go b/metropolis/test/nanoswitch/socks.go
new file mode 100644
index 0000000..7b0278a
--- /dev/null
+++ b/metropolis/test/nanoswitch/socks.go
@@ -0,0 +1,70 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "net"
+
+ "source.monogon.dev/metropolis/pkg/socksproxy"
+ "source.monogon.dev/metropolis/pkg/supervisor"
+)
+
+// ONCHANGE(//metropolis/test/launch/cluster:cluster.go): port must be kept in sync
+const SOCKSPort uint16 = 1080
+
+// socksHandler implements a socksproxy.Handler which permits and logs
+// connections to the nanoswitch network.
+type socksHandler struct{}
+
+func (s *socksHandler) Connect(ctx context.Context, req *socksproxy.ConnectRequest) *socksproxy.ConnectResponse {
+ logger := supervisor.Logger(ctx)
+ target := net.JoinHostPort(req.Address.String(), fmt.Sprintf("%d", req.Port))
+
+ if len(req.Address) != 4 {
+ logger.Warningf("Connect %s: wrong address type", target)
+ return &socksproxy.ConnectResponse{
+ Error: socksproxy.ReplyAddressTypeNotSupported,
+ }
+ }
+
+ addr := req.Address
+ switchCIDR := net.IPNet{
+ IP: switchIP.Mask(switchSubnetMask),
+ Mask: switchSubnetMask,
+ }
+ if !switchCIDR.Contains(addr) || switchCIDR.IP.Equal(addr) {
+ logger.Warningf("Connect %s: not in switch network", target)
+ return &socksproxy.ConnectResponse{
+ Error: socksproxy.ReplyNetworkUnreachable,
+ }
+ }
+
+ con, err := net.Dial("tcp", target)
+ if err != nil {
+ logger.Warningf("Connect %s: dial failed: %v", target, err)
+ return &socksproxy.ConnectResponse{
+ Error: socksproxy.ReplyHostUnreachable,
+ }
+ }
+ res, err := socksproxy.ConnectResponseFromConn(con)
+ if err != nil {
+ logger.Warningf("Connect %s: could not make SOCKS response: %v", target, err)
+ return &socksproxy.ConnectResponse{
+ Error: socksproxy.ReplyGeneralFailure,
+ }
+ }
+ logger.Infof("Connect %s: established", target)
+ return res
+}
+
+// runSOCKSProxy starts a SOCKS proxy to the nanoswitchnetwork at SOCKSPort.
+func runSOCKSProxy(ctx context.Context) error {
+ lis, err := net.Listen("tcp", fmt.Sprintf(":%d", SOCKSPort))
+ if err != nil {
+ return fmt.Errorf("failed to listen on :%d : %v", SOCKSPort, err)
+ }
+
+ h := &socksHandler{}
+ supervisor.Signal(ctx, supervisor.SignalHealthy)
+ return socksproxy.Serve(ctx, h, lis)
+}