Merge pull request #484 from MrEcco/main

Enable config file based configuration
This commit is contained in:
openshift-merge-bot[bot]
2025-09-12 12:57:51 +00:00
committed by GitHub
6 changed files with 1101 additions and 287 deletions

View File

@@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
go-version: ["1.23.x", "1.24.x"]
go-version: ["1.24.x", "1.25.x"]
steps:
- uses: actions/checkout@v5
with:
@@ -32,7 +32,7 @@ jobs:
mv bin/gvproxy.exe bin/gvproxy-windowsgui.exe
- uses: actions/upload-artifact@v4
if: matrix.go-version == '1.23.x'
if: matrix.go-version == '1.24.x'
with:
name: gvisor-tap-vsock-binaries
path: bin/*

428
cmd/gvproxy/config.go Normal file
View File

@@ -0,0 +1,428 @@
package main
import (
"flag"
"fmt"
"net"
"net/netip"
"net/url"
"os"
"runtime"
"slices"
"strings"
"github.com/containers/gvisor-tap-vsock/pkg/types"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v3"
)
const (
// gatewayIP = "192.168.127.1"
sshHostPort = "192.168.127.2:22"
hostIP = "192.168.127.254"
host = "host"
gateway = "gateway"
)
type GvproxyArgs struct {
config string
endpoints arrayFlags
debug bool
mtu int
sshPort int
vpnkitSocket string
qemuSocket string
bessSocket string
stdioSocket string
vfkitSocket string
forwardSocket arrayFlags
forwardDest arrayFlags
forwardUser arrayFlags
forwardIdentify arrayFlags
pidFile string
pcapFile string
logFile string
servicesEndpoint string
ec2MetadataAccess bool
}
type GvproxyConfig struct {
Listen []string `yaml:"listen,omitempty"`
LogLevel string `yaml:"log-level,omitempty"`
Stack types.Configuration `yaml:"stack,omitempty"`
Interfaces struct {
VPNKit string `yaml:"vpnkit,omitempty"`
Qemu string `yaml:"qemu,omitempty"`
Bess string `yaml:"bess,omitempty"`
Stdio string `yaml:"stdio,omitempty"`
Vfkit string `yaml:"vfkit,omitempty"`
} `yaml:"interfaces,omitempty"`
Forwards []GvproxyConfigForward `yaml:"forwards,omitempty"`
PIDFile string `yaml:"pid-file,omitempty"`
LogFile string `yaml:"log-file,omitempty"`
Services string `yaml:"services,omitempty"`
Ec2MetadataAccess bool `yaml:"ec2-metadata-access,omitempty"`
}
type GvproxyConfigForward struct {
Socket string `yaml:"socket,omitempty"`
Dest string `yaml:"dest,omitempty"`
User string `yaml:"user,omitempty"`
Identity string `yaml:"identity,omitempty"`
}
func GvproxyVersion() string {
return types.NewVersion("gvproxy").String()
}
func GvproxyInit() (*GvproxyConfig, error) {
var args GvproxyArgs
var config GvproxyConfig
version := types.NewVersion("gvproxy")
version.AddFlag()
// Pass it to the testable function
_, err := GvproxyArgParse(flag.CommandLine, &args, os.Args[1:])
if err != nil {
return nil, fmt.Errorf("failed to parse command line arguments: %w", err)
}
if version.ShowVersion() {
fmt.Println(version.String())
os.Exit(0)
}
// Init config if provided
if args.config != "" {
content, err := os.ReadFile(args.config)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}
if err := yaml.Unmarshal(content, &config); err != nil {
return nil, fmt.Errorf("failed to parse configuration: %w", err)
}
}
// Pass it to the testable function
return GvproxyConfigure(&config, &args, version.String())
}
func GvproxyArgParse(flagSet *flag.FlagSet, args *GvproxyArgs, argv []string) (*GvproxyArgs, error) {
flagSet.StringVar(&args.config, "config", "", "Use configuration file with command line override")
flagSet.Var(&args.endpoints, "listen", "control endpoint")
flagSet.BoolVar(&args.debug, "debug", false, "Print debug info")
flagSet.StringVar(&args.pcapFile, "pcap", "", "Capture network traffic to a pcap file")
flagSet.IntVar(&args.mtu, "mtu", 0, "Set the MTU (default: 1500)")
flagSet.IntVar(&args.sshPort, "ssh-port", 2222, "Port to access the guest virtual machine. Must be between 1024 and 65535")
flagSet.StringVar(&args.vpnkitSocket, "listen-vpnkit", "", "VPNKit socket to be used by Hyperkit")
flagSet.StringVar(&args.qemuSocket, "listen-qemu", "", "Socket to be used by Qemu")
flagSet.StringVar(&args.bessSocket, "listen-bess", "", "unixpacket socket to be used by Bess-compatible applications")
flagSet.StringVar(&args.stdioSocket, "listen-stdio", "", "accept stdio pipe")
flagSet.StringVar(&args.vfkitSocket, "listen-vfkit", "", "unixgram socket to be used by vfkit-compatible applications")
flagSet.Var(&args.forwardSocket, "forward-sock", "Forwards a unix socket to the guest virtual machine over SSH")
flagSet.Var(&args.forwardDest, "forward-dest", "Forwards a unix socket to the guest virtual machine over SSH")
flagSet.Var(&args.forwardUser, "forward-user", "SSH user to use for unix socket forward")
flagSet.Var(&args.forwardIdentify, "forward-identity", "Path to SSH identity key for forwarding")
flagSet.StringVar(&args.pidFile, "pid-file", "", "Generate a file with the PID in it")
flagSet.StringVar(&args.logFile, "log-file", "", "Output log messages (logrus) to a given file path")
flagSet.StringVar(&args.servicesEndpoint, "services", "", "Exposes the same HTTP API as the --listen flag, without the /connect endpoint")
flagSet.BoolVar(&args.ec2MetadataAccess, "ec2-metadata-access", false, "Permits access to EC2 Metadata Service (TCP only)")
if err := flagSet.Parse(argv); err != nil {
return nil, err
}
return args, nil
}
func GvproxyConfigure(config *GvproxyConfig, args *GvproxyArgs, version string) (*GvproxyConfig, error) {
if args.debug {
config.LogLevel = "debug"
}
// Set log level
if logLevel, err := log.ParseLevel(strings.ToLower(config.LogLevel)); err != nil {
log.Warningf("bad log level \"%s\", falling back to \"info\"", config.LogLevel)
log.SetLevel(log.InfoLevel)
} else {
log.SetLevel(logLevel)
}
// Set log file
if config.LogFile != "" {
lf, err := os.Create(config.LogFile)
if err != nil {
return config, fmt.Errorf("unable to open log file %s", config.LogFile)
}
log.DeferExitHandler(func() {
if err := lf.Close(); err != nil {
fmt.Printf("unable to close log-file: %q\n", err)
}
})
log.SetOutput(lf)
// If debug is set, lets seed the log file with some basic information
// about the environment and how it was called
log.Debugf("gvproxy version: %q", version)
log.Debugf("os: %q arch: %q", runtime.GOOS, runtime.GOARCH)
log.Debugf("command line: %q", os.Args)
}
// Set defaults
if config.LogLevel == "" {
config.LogLevel = "info"
}
if config.Stack.MTU == 0 {
config.Stack.MTU = 1500
}
if config.Stack.Subnet == "" {
config.Stack.Subnet = "192.168.127.0/24"
}
// Parse subnet address for further use
naddr, err := netip.ParsePrefix(config.Stack.Subnet)
if err != nil {
return config, fmt.Errorf("failed to parse subnet: %w", err)
}
fuaddr, err := getFirstUsableIPFromSubnet(naddr)
if err != nil {
return config, fmt.Errorf("failed to identify first usable address in subnet: %w", err)
}
luaddr, err := getLastUsableIPFromSubnet(naddr)
if err != nil {
return config, fmt.Errorf("failed to identify last usable address in subnet: %w", err)
}
if config.Stack.GatewayIP == "" {
config.Stack.GatewayIP = fuaddr.String()
}
if config.Stack.GatewayMacAddress == "" {
config.Stack.GatewayMacAddress = "5a:94:ef:e4:0c:dd"
}
if len(config.Stack.NAT) == 0 {
config.Stack.NAT = map[string]string{
luaddr.String(): "127.0.0.1",
}
}
if len(config.Stack.GatewayVirtualIPs) == 0 {
config.Stack.GatewayVirtualIPs = []string{
luaddr.String(),
}
}
// Default DNS zone enabled only for the default mode
// Default DNS search domains enabled only for the default mode
// Default forwards enabled only for the default mode
// Default static leases enabled only for the default mode
// Default vpnkit mac addresses enabled only for the default mode
// Patch config with CLI args
if args.logFile != "" {
config.LogFile = args.logFile
}
if args.qemuSocket != "" {
config.Interfaces.Qemu = args.qemuSocket
}
if args.bessSocket != "" {
config.Interfaces.Bess = args.bessSocket
}
if args.vfkitSocket != "" {
config.Interfaces.Vfkit = args.vfkitSocket
}
if args.vpnkitSocket != "" {
config.Interfaces.VPNKit = args.vpnkitSocket
}
if args.pidFile != "" {
config.PIDFile = args.pidFile
}
if len(args.endpoints) > 0 {
config.Listen = args.endpoints
}
if args.servicesEndpoint != "" {
config.Services = args.servicesEndpoint
}
if args.ec2MetadataAccess {
config.Ec2MetadataAccess = true
}
if args.mtu != 0 {
config.Stack.MTU = args.mtu
}
// Make sure the qemu socket provided is valid syntax
if config.Interfaces.Qemu != "" {
uri, err := url.Parse(config.Interfaces.Qemu)
if err != nil || uri == nil {
return config, errors.Wrapf(err, "invalid value for qemu listen address")
}
if _, err := os.Stat(uri.Path); err == nil && uri.Scheme == "unix" {
return config, errors.Errorf("%q already exists", uri.Path)
}
}
if config.Interfaces.Bess != "" {
uri, err := url.Parse(config.Interfaces.Bess)
if err != nil || uri == nil {
return config, errors.Wrapf(err, "invalid value for bess listen address")
}
if uri.Scheme != "unixpacket" {
return config, errors.New("bess listen address must be unixpacket:// address")
}
if _, err := os.Stat(uri.Path); err == nil {
return config, fmt.Errorf("%q already exists", uri.Path)
}
}
if config.Interfaces.Vfkit != "" {
uri, err := url.Parse(config.Interfaces.Vfkit)
if err != nil || uri == nil {
return config, errors.Wrapf(err, "invalid value for vfkit listen address")
}
if uri.Scheme != "unixgram" {
return config, errors.New("vfkit listen address must be unixgram:// address")
}
if _, err := os.Stat(uri.Path); err == nil {
return config, errors.Errorf("%q already exists", uri.Path)
}
}
if config.Interfaces.VPNKit != "" && config.Interfaces.Qemu != "" {
return config, errors.New("cannot use qemu and vpnkit protocol at the same time")
}
if config.Interfaces.VPNKit != "" && config.Interfaces.Bess != "" {
return config, errors.New("cannot use bess and vpnkit protocol at the same time")
}
if config.Interfaces.Qemu != "" && config.Interfaces.Bess != "" {
return config, errors.New("cannot use qemu and bess protocol at the same time")
}
if args.config != "" {
if slices.Contains(os.Args, "-ssh-port") || slices.Contains(os.Args, "--ssh-port") {
log.Warningf("CLI argument \"-ssh-port\" is unavailable with config file. You need to add \"127.0.0.1:%d: 192.168.127.2:22\" entry into .stack.forwards in \"\" instead", args.sshPort)
}
}
config.Stack.Protocol = types.HyperKitProtocol
if config.Interfaces.Qemu != "" {
config.Stack.Protocol = types.QemuProtocol
}
if config.Interfaces.Bess != "" {
config.Stack.Protocol = types.BessProtocol
}
if config.Interfaces.Vfkit != "" {
config.Stack.Protocol = types.VfkitProtocol
}
if InDebugMode() {
config.Stack.Debug = true
}
// Handle the default behavior without config
if args.config == "" {
if args.sshPort != -1 && args.sshPort < 1024 || args.sshPort > 65535 {
return config, errors.New("ssh-port value must be between 1024 and 65535")
}
config.Stack.CaptureFile = args.pcapFile
config.Stack.DNS = []types.Zone{
{
Name: "containers.internal.",
Records: []types.Record{
{
Name: gateway,
IP: net.ParseIP(config.Stack.GatewayIP),
},
{
Name: host,
IP: net.ParseIP(hostIP),
},
},
},
{
Name: "docker.internal.",
Records: []types.Record{
{
Name: gateway,
IP: net.ParseIP(config.Stack.GatewayIP),
},
{
Name: host,
IP: net.ParseIP(hostIP),
},
},
},
}
config.Stack.DNSSearchDomains = searchDomains()
config.Stack.Forwards = getForwardsMap(args.sshPort, sshHostPort)
config.Stack.DHCPStaticLeases = map[string]string{
"192.168.127.2": "5a:94:ef:e4:0c:ee",
}
config.Stack.VpnKitUUIDMacAddresses = map[string]string{
"c3d68012-0208-11ea-9fd7-f2189899ab08": "5a:94:ef:e4:0c:ee",
}
}
// Add SSH forwards from CLI args
if c := len(args.forwardSocket); c != len(args.forwardDest) || c != len(args.forwardUser) || c != len(args.forwardIdentify) {
return config, errors.New("--forward-sock, --forward-dest, --forward-user, and --forward-identity must all be specified together, " +
"the same number of times, or not at all")
}
for i := range args.forwardSocket {
config.Forwards = append(config.Forwards, GvproxyConfigForward{
Socket: args.forwardSocket[i],
Dest: args.forwardDest[i],
User: args.forwardUser[i],
Identity: args.forwardIdentify[i],
})
}
// Validate SSH forward rules
for _, v := range config.Forwards {
_, err := os.Stat(v.Identity)
if err != nil {
return config, errors.Wrapf(err, "Identity file \"%s\" can't be loaded", v.Identity)
}
}
return config, nil
}
func getFirstUsableIPFromSubnet(network netip.Prefix) (netip.Addr, error) {
// The network must have at least 5 IP addresses: network, broadcast, gateway, guest, and preferably host
// v4/30 has only 2 devices, thus prefer at least v4/29 CIDR. This code works also for IPv6, just in case
if (network.Bits() + 3) > network.Addr().BitLen() {
return netip.Addr{}, errors.New("too small network")
}
b := network.Masked().Addr().AsSlice()
b[len(b)-1] += 1
addr, ok := netip.AddrFromSlice(b)
if !ok {
return netip.Addr{}, errors.New("bad ip address")
}
return addr, nil
}
func getLastUsableIPFromSubnet(network netip.Prefix) (netip.Addr, error) {
// The network must have at least 5 IP addresses: network, broadcast, gateway, guest, and preferably host
// v4/30 has only 2 devices, thus prefer at least v4/29 CIDR. This code works also for IPv6, just in case
if (network.Bits() + 3) > network.Addr().BitLen() {
return netip.Addr{}, errors.New("too small network")
}
var b = network.Masked().Addr().AsSlice()
for i, v := range net.CIDRMask(network.Bits(), network.Addr().BitLen()) {
b[i] += ^v
}
b[len(b)-1] -= 1
addr, ok := netip.AddrFromSlice(b)
if !ok {
return netip.Addr{}, errors.New("bad ip address")
}
return addr, nil
}

41
cmd/gvproxy/config.yaml Normal file
View File

@@ -0,0 +1,41 @@
###
### This example config implements the legacy behavior, same as
### you didn't provide any
###
log-level: info
stack:
mtu: 1500
subnet: 192.168.127.0/24
gatewayIP: 192.168.127.1
gatewayMacAddress: 5a:94:ef:e4:0c:dd
dns:
- name: containers.internal.
records:
- name: gateway
ip: 192.168.127.1
- name: host
ip: 192.168.127.254
- name: docker.internal.
records:
- name: gateway
ip: 192.168.127.1
- name: host
ip: 192.168.127.254
forwards:
127.0.0.1:2222: 192.168.127.2:22
nat:
192.168.127.254: 127.0.0.1
gatewayVirtualIPs:
- 192.168.127.254
dhcpStaticLeases:
192.168.127.2: 5a:94:ef:e4:0c:ee
vpnKitUUIDMacAddresses:
c3d68012-0208-11ea-9fd7-f2189899ab08: 5a:94:ef:e4:0c:ee
###
### Using while testing
###
### go get -v .
### go test .
### go run . -config config.yaml
###

542
cmd/gvproxy/config_test.go Normal file
View File

@@ -0,0 +1,542 @@
package main
import (
"flag"
"net/netip"
"os"
"slices"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
yaml "gopkg.in/yaml.v3"
)
func TestIPAddressConversions(t *testing.T) {
t.Parallel()
cases := [][]string{
{"192.168.127.1/24", "192.168.127.1", "192.168.127.254"},
{"10.10.0.0/16", "10.10.0.1", "10.10.255.254"},
{"172.16.16.16/12", "172.16.0.1", "172.31.255.254"},
{"fc00::fff/64", "fc00::1", "fc00::ffff:ffff:ffff:fffe"},
}
for _, v := range cases {
naddr, _ := netip.ParsePrefix(v[0])
fuaddr, err := getFirstUsableIPFromSubnet(naddr)
require.NoError(t, err, "getFirstUsableIPFromSubnet returns error for \"%s\" -> \"%s\"", v[0], fuaddr)
luaddr, err := getLastUsableIPFromSubnet(naddr)
require.NoError(t, err, "getLastUsableIPFromSubnet returns error for \"%s\" -> \"%s\"", v[0], luaddr)
assert.Equal(t, v[1], fuaddr.String(), "getFirstUsableIPFromSubnet returns wrong result: expects \"%s\", got \"%s\"", v[1], fuaddr)
assert.Equal(t, v[2], luaddr.String(), "getLastUsableIPFromSubnet returns wrong result: expects \"%s\", got \"%s\"", v[2], luaddr)
}
}
func TestConfigInit(t *testing.T) {
t.Parallel()
for _, v := range getCaseDataConfig() {
var cnf GvproxyConfig
var args GvproxyArgs
flagSet := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
_, errArgParse := GvproxyArgParse(flagSet, &args, v.Args)
require.NoError(t, errArgParse, "%s: failed toFparse command line arguments", v.CaseName)
// Read config
errUnmarshal := yaml.Unmarshal([]byte(v.InputConfig), &cnf)
require.NoError(t, errUnmarshal, "%s: failed to parse config file", v.CaseName)
_, errConfig := GvproxyConfigure(&cnf, &args, "testing")
if v.ErrorPrefix != "" {
require.ErrorContains(t, errConfig, v.ErrorPrefix, "%s: wrong config error", v.CaseName)
} else {
require.NoError(t, errConfig, "%s: unexpected config error", v.CaseName)
}
// Ignore os-specific things while testing
if len(cnf.Listen) == 1 && slices.Contains(getCaseDataPossibleDefaultListen(), cnf.Listen[0]) {
cnf.Listen[0] = "default"
}
cnf.Stack.DNSSearchDomains = nil
result, errMarshal := yaml.Marshal(cnf)
require.NoErrorf(t, errMarshal, "%s: unmarshallable config", v.CaseName)
assert.YAMLEq(t, v.ResultConfig, string(result), "%s: resulted and expected config mismatch", v.CaseName)
}
}
func getCaseDataPossibleDefaultListen() []string {
return []string{
"unix:///var/run/gvproxy/default.sock",
"unix:\\\\\\.\\pipe\\gvproxy\\default_sock",
}
}
// Data for test cases
type caseDataConfig struct {
CaseName string
Args []string
InputConfig string
ResultConfig string
ErrorPrefix string
}
func getCaseDataConfig() []caseDataConfig {
return []caseDataConfig{
{
CaseName: "Legacy with no args",
Args: []string{},
InputConfig: "",
ResultConfig: `log-level: info
stack:
mtu: 1500
subnet: 192.168.127.0/24
gatewayIP: 192.168.127.1
gatewayMacAddress: 5a:94:ef:e4:0c:dd
dns:
- name: containers.internal.
records:
- name: gateway
ip: 192.168.127.1
- name: host
ip: 192.168.127.254
- name: docker.internal.
records:
- name: gateway
ip: 192.168.127.1
- name: host
ip: 192.168.127.254
forwards:
127.0.0.1:2222: 192.168.127.2:22
nat:
192.168.127.254: 127.0.0.1
gatewayVirtualIPs:
- 192.168.127.254
dhcpStaticLeases:
192.168.127.2: 5a:94:ef:e4:0c:ee
vpnKitUUIDMacAddresses:
c3d68012-0208-11ea-9fd7-f2189899ab08: 5a:94:ef:e4:0c:ee
`,
},
{
CaseName: "Legacy from README: qemu tcp",
Args: []string{"-listen", "unix:///tmp/network.sock", "-listen-qemu", "tcp://0.0.0.0:1234"},
InputConfig: "",
ResultConfig: `listen:
- unix:///tmp/network.sock
log-level: info
stack:
mtu: 1500
subnet: 192.168.127.0/24
gatewayIP: 192.168.127.1
gatewayMacAddress: 5a:94:ef:e4:0c:dd
dns:
- name: containers.internal.
records:
- name: gateway
ip: 192.168.127.1
- name: host
ip: 192.168.127.254
- name: docker.internal.
records:
- name: gateway
ip: 192.168.127.1
- name: host
ip: 192.168.127.254
forwards:
127.0.0.1:2222: 192.168.127.2:22
nat:
192.168.127.254: 127.0.0.1
gatewayVirtualIPs:
- 192.168.127.254
dhcpStaticLeases:
192.168.127.2: 5a:94:ef:e4:0c:ee
vpnKitUUIDMacAddresses:
c3d68012-0208-11ea-9fd7-f2189899ab08: 5a:94:ef:e4:0c:ee
interfaces:
qemu: tcp://0.0.0.0:1234
`,
},
{
CaseName: "Legacy from README: qemu unix",
Args: []string{"-debug", "-listen", "unix:///tmp/network.sock", "-listen-qemu", "unix:///tmp/qemu.sock"},
InputConfig: "",
ResultConfig: `listen:
- unix:///tmp/network.sock
log-level: debug
stack:
debug: true
mtu: 1500
subnet: 192.168.127.0/24
gatewayIP: 192.168.127.1
gatewayMacAddress: 5a:94:ef:e4:0c:dd
dns:
- name: containers.internal.
records:
- name: gateway
ip: 192.168.127.1
- name: host
ip: 192.168.127.254
- name: docker.internal.
records:
- name: gateway
ip: 192.168.127.1
- name: host
ip: 192.168.127.254
forwards:
127.0.0.1:2222: 192.168.127.2:22
nat:
192.168.127.254: 127.0.0.1
gatewayVirtualIPs:
- 192.168.127.254
dhcpStaticLeases:
192.168.127.2: 5a:94:ef:e4:0c:ee
vpnKitUUIDMacAddresses:
c3d68012-0208-11ea-9fd7-f2189899ab08: 5a:94:ef:e4:0c:ee
interfaces:
qemu: unix:///tmp/qemu.sock
`,
},
{
CaseName: "Legacy from README: UML",
Args: []string{"-debug", "-listen", "unix:///tmp/network.sock", "-listen-bess", "unixpacket:///tmp/bess.sock"},
InputConfig: "",
ResultConfig: `listen:
- unix:///tmp/network.sock
log-level: debug
stack:
debug: true
mtu: 1500
subnet: 192.168.127.0/24
gatewayIP: 192.168.127.1
gatewayMacAddress: 5a:94:ef:e4:0c:dd
dns:
- name: containers.internal.
records:
- name: gateway
ip: 192.168.127.1
- name: host
ip: 192.168.127.254
- name: docker.internal.
records:
- name: gateway
ip: 192.168.127.1
- name: host
ip: 192.168.127.254
forwards:
127.0.0.1:2222: 192.168.127.2:22
nat:
192.168.127.254: 127.0.0.1
gatewayVirtualIPs:
- 192.168.127.254
dhcpStaticLeases:
192.168.127.2: 5a:94:ef:e4:0c:ee
vpnKitUUIDMacAddresses:
c3d68012-0208-11ea-9fd7-f2189899ab08: 5a:94:ef:e4:0c:ee
interfaces:
bess: unixpacket:///tmp/bess.sock
`,
},
{
CaseName: "Legacy from README: VFKit",
Args: []string{"-debug", "-listen", "unix:///tmp/network.sock", "--listen-vfkit", "unixgram:///tmp/vfkit.sock"},
InputConfig: "",
ResultConfig: `listen:
- unix:///tmp/network.sock
log-level: debug
stack:
debug: true
mtu: 1500
subnet: 192.168.127.0/24
gatewayIP: 192.168.127.1
gatewayMacAddress: 5a:94:ef:e4:0c:dd
dns:
- name: containers.internal.
records:
- name: gateway
ip: 192.168.127.1
- name: host
ip: 192.168.127.254
- name: docker.internal.
records:
- name: gateway
ip: 192.168.127.1
- name: host
ip: 192.168.127.254
forwards:
127.0.0.1:2222: 192.168.127.2:22
nat:
192.168.127.254: 127.0.0.1
gatewayVirtualIPs:
- 192.168.127.254
dhcpStaticLeases:
192.168.127.2: 5a:94:ef:e4:0c:ee
vpnKitUUIDMacAddresses:
c3d68012-0208-11ea-9fd7-f2189899ab08: 5a:94:ef:e4:0c:ee
interfaces:
vfkit: unixgram:///tmp/vfkit.sock
`,
},
{
CaseName: "Legacy from README: vsock",
Args: []string{"-debug", "-listen", "unix:///tmp/network.sock", "-listen-qemu", "unix:///tmp/qemu.sock"},
InputConfig: "",
ResultConfig: `listen:
- unix:///tmp/network.sock
log-level: debug
stack:
debug: true
mtu: 1500
subnet: 192.168.127.0/24
gatewayIP: 192.168.127.1
gatewayMacAddress: 5a:94:ef:e4:0c:dd
dns:
- name: containers.internal.
records:
- name: gateway
ip: 192.168.127.1
- name: host
ip: 192.168.127.254
- name: docker.internal.
records:
- name: gateway
ip: 192.168.127.1
- name: host
ip: 192.168.127.254
forwards:
127.0.0.1:2222: 192.168.127.2:22
nat:
192.168.127.254: 127.0.0.1
gatewayVirtualIPs:
- 192.168.127.254
dhcpStaticLeases:
192.168.127.2: 5a:94:ef:e4:0c:ee
vpnKitUUIDMacAddresses:
c3d68012-0208-11ea-9fd7-f2189899ab08: 5a:94:ef:e4:0c:ee
interfaces:
qemu: unix:///tmp/qemu.sock
`,
},
{
CaseName: "config: empty config",
Args: []string{"-config", "config.yaml"},
InputConfig: ``,
ResultConfig: `log-level: info
stack:
mtu: 1500
subnet: 192.168.127.0/24
gatewayIP: 192.168.127.1
gatewayMacAddress: 5a:94:ef:e4:0c:dd
nat:
192.168.127.254: 127.0.0.1
gatewayVirtualIPs:
- 192.168.127.254
`,
},
{
CaseName: "config: listen, loglevel, qemu",
Args: []string{"-config", "config.yaml"},
InputConfig: `listen:
- unix:///var/run/gvproxy/domain-2.sock
log-level: warning
interfaces:
qemu: unix:///tmp/qemu.sock`,
ResultConfig: `listen:
- unix:///var/run/gvproxy/domain-2.sock
log-level: warning
stack:
mtu: 1500
subnet: 192.168.127.0/24
gatewayIP: 192.168.127.1
gatewayMacAddress: 5a:94:ef:e4:0c:dd
nat:
192.168.127.254: 127.0.0.1
gatewayVirtualIPs:
- 192.168.127.254
interfaces:
qemu: unix:///tmp/qemu.sock
`,
},
{
CaseName: "config: stack changes: auto addresses from subnet",
Args: []string{"-config", "config.yaml"},
InputConfig: `listen:
- unix:///var/run/gvproxy/domain-2.sock
log-level: warning
stack:
mtu: 1480
subnet: 10.0.0.0/16
gatewayMacAddress: 10:11:11:11:11:00
`,
ResultConfig: `listen:
- unix:///var/run/gvproxy/domain-2.sock
log-level: warning
stack:
mtu: 1480
subnet: 10.0.0.0/16
gatewayIP: 10.0.0.1
gatewayMacAddress: "10:11:11:11:11:00"
nat:
10.0.255.254: 127.0.0.1
gatewayVirtualIPs:
- 10.0.255.254
`,
},
{
CaseName: "config: stack changes: dhcpStaticLeases",
Args: []string{"-config", "config.yaml"},
InputConfig: `stack:
subnet: 10.0.0.0/16
dhcpStaticLeases:
10.0.0.2: "10:11:11:11:11:02"
10.0.0.3: "10:11:11:11:11:03"
10.0.0.100: "10:11:11:11:11:dd"
`,
ResultConfig: `log-level: info
stack:
mtu: 1500
subnet: 10.0.0.0/16
gatewayIP: 10.0.0.1
gatewayMacAddress: 5a:94:ef:e4:0c:dd
nat:
10.0.255.254: 127.0.0.1
gatewayVirtualIPs:
- 10.0.255.254
dhcpStaticLeases:
10.0.0.2: "10:11:11:11:11:02"
10.0.0.3: "10:11:11:11:11:03"
10.0.0.100: 10:11:11:11:11:dd
`,
},
{
CaseName: "config: stack changes: tcp forwards",
Args: []string{"-config", "config.yaml"},
InputConfig: `stack:
subnet: 10.0.0.0/16
forwards:
127.0.0.1:59022: 192.168.127.2:22
127.0.0.1:59080: 192.168.127.2:80
127.0.0.1:59443: 192.168.127.2:443
`,
ResultConfig: `log-level: info
stack:
mtu: 1500
subnet: 10.0.0.0/16
gatewayIP: 10.0.0.1
gatewayMacAddress: 5a:94:ef:e4:0c:dd
forwards:
127.0.0.1:59022: 192.168.127.2:22
127.0.0.1:59080: 192.168.127.2:80
127.0.0.1:59443: 192.168.127.2:443
nat:
10.0.255.254: 127.0.0.1
gatewayVirtualIPs:
- 10.0.255.254
`,
},
{
CaseName: "config: ssh forwards fails on identity file missing",
Args: []string{"-config", "config.yaml"},
InputConfig: `stack:
subnet: 10.0.0.0/16
forwards:
- socket: ???
dest: ???
user: ???
identity: ???
`,
ResultConfig: `log-level: info
stack:
mtu: 1500
subnet: 10.0.0.0/16
gatewayIP: 10.0.0.1
gatewayMacAddress: 5a:94:ef:e4:0c:dd
nat:
10.0.255.254: 127.0.0.1
gatewayVirtualIPs:
- 10.0.255.254
forwards:
- socket: ???
dest: ???
user: ???
identity: ???
`,
ErrorPrefix: "Identity file",
},
{
CaseName: "debug check #1",
Args: []string{"-config", "config.yaml"},
InputConfig: `log-level: debug`,
ResultConfig: `log-level: debug
stack:
debug: true
mtu: 1500
subnet: 192.168.127.0/24
gatewayIP: 192.168.127.1
gatewayMacAddress: 5a:94:ef:e4:0c:dd
nat:
192.168.127.254: 127.0.0.1
gatewayVirtualIPs:
- 192.168.127.254
`,
},
{
CaseName: "debug check #2",
Args: []string{"-debug", "-config", "config.yaml"},
InputConfig: `log-level: error`,
ResultConfig: `log-level: debug
stack:
debug: true
mtu: 1500
subnet: 192.168.127.0/24
gatewayIP: 192.168.127.1
gatewayMacAddress: 5a:94:ef:e4:0c:dd
nat:
192.168.127.254: 127.0.0.1
gatewayVirtualIPs:
- 192.168.127.254
`,
},
{
CaseName: "debug check #3",
Args: []string{"-debug", "-pcap", "capture.pcap"},
InputConfig: "",
ResultConfig: `log-level: debug
stack:
debug: true
capture-file: capture.pcap
mtu: 1500
subnet: 192.168.127.0/24
gatewayIP: 192.168.127.1
gatewayMacAddress: 5a:94:ef:e4:0c:dd
dns:
- name: containers.internal.
records:
- name: gateway
ip: 192.168.127.1
- name: host
ip: 192.168.127.254
- name: docker.internal.
records:
- name: gateway
ip: 192.168.127.1
- name: host
ip: 192.168.127.254
forwards:
127.0.0.1:2222: 192.168.127.2:22
nat:
192.168.127.254: 127.0.0.1
gatewayVirtualIPs:
- 192.168.127.254
dhcpStaticLeases:
192.168.127.2: 5a:94:ef:e4:0c:ee
vpnKitUUIDMacAddresses:
c3d68012-0208-11ea-9fd7-f2189899ab08: 5a:94:ef:e4:0c:ee
`,
},
}
}

View File

@@ -3,7 +3,6 @@ package main
import (
"bufio"
"context"
"flag"
"fmt"
"net"
"net/http"
@@ -20,256 +19,62 @@ import (
"github.com/containers/gvisor-tap-vsock/pkg/net/stdio"
"github.com/containers/gvisor-tap-vsock/pkg/sshclient"
"github.com/containers/gvisor-tap-vsock/pkg/transport"
"github.com/containers/gvisor-tap-vsock/pkg/types"
"github.com/containers/gvisor-tap-vsock/pkg/virtualnetwork"
"github.com/containers/winquit/pkg/winquit"
"github.com/dustin/go-humanize"
humanize "github.com/dustin/go-humanize"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
)
var (
debug bool
mtu int
endpoints arrayFlags
vpnkitSocket string
qemuSocket string
bessSocket string
stdioSocket string
vfkitSocket string
forwardSocket arrayFlags
forwardDest arrayFlags
forwardUser arrayFlags
forwardIdentify arrayFlags
sshPort int
pidFile string
pcapFile string
exitCode int
logFile string
servicesEndpoint string
ec2MetadataAccess bool
)
const (
gatewayIP = "192.168.127.1"
sshHostPort = "192.168.127.2:22"
hostIP = "192.168.127.254"
host = "host"
gateway = "gateway"
exitCode int
)
func main() {
version := types.NewVersion("gvproxy")
version.AddFlag()
flag.Var(&endpoints, "listen", "control endpoint")
flag.BoolVar(&debug, "debug", false, "Print debug info")
flag.StringVar(&pcapFile, "pcap", "", "Capture network traffic to a pcap file")
flag.IntVar(&mtu, "mtu", 1500, "Set the MTU")
flag.IntVar(&sshPort, "ssh-port", 2222, "Port to access the guest virtual machine. Must be between 1024 and 65535")
flag.StringVar(&vpnkitSocket, "listen-vpnkit", "", "VPNKit socket to be used by Hyperkit")
flag.StringVar(&qemuSocket, "listen-qemu", "", "Socket to be used by Qemu")
flag.StringVar(&bessSocket, "listen-bess", "", "unixpacket socket to be used by Bess-compatible applications")
flag.StringVar(&stdioSocket, "listen-stdio", "", "accept stdio pipe")
flag.StringVar(&vfkitSocket, "listen-vfkit", "", "unixgram socket to be used by vfkit-compatible applications")
flag.Var(&forwardSocket, "forward-sock", "Forwards a unix socket to the guest virtual machine over SSH")
flag.Var(&forwardDest, "forward-dest", "Forwards a unix socket to the guest virtual machine over SSH")
flag.Var(&forwardUser, "forward-user", "SSH user to use for unix socket forward")
flag.Var(&forwardIdentify, "forward-identity", "Path to SSH identity key for forwarding")
flag.StringVar(&pidFile, "pid-file", "", "Generate a file with the PID in it")
flag.StringVar(&logFile, "log-file", "", "Output log messages (logrus) to a given file path")
flag.StringVar(&servicesEndpoint, "services", "", "Exposes the same HTTP API as the --listen flag, without the /connect endpoint")
flag.BoolVar(&ec2MetadataAccess, "ec2-metadata-access", false, "Permits access to EC2 Metadata Service (TCP only)")
flag.Parse()
if version.ShowVersion() {
fmt.Println(version.String())
os.Exit(0)
// Use config or fallback to original behavior
config, err := GvproxyInit()
if err != nil {
log.Fatal(err.Error())
}
// If the user provides a log-file, we re-direct log messages
// from logrus to the file
if logFile != "" {
lf, err := os.Create(logFile)
if err != nil {
fmt.Printf("unable to open log file %s, exiting...\n", logFile)
os.Exit(1)
}
defer func() {
if err := lf.Close(); err != nil {
fmt.Printf("unable to close log-file: %q\n", err)
}
}()
log.SetOutput(lf)
// Report version
log.Info(GvproxyVersion())
// If debug is set, lets seed the log file with some basic information
// about the environment and how it was called
log.Debugf("gvproxy version: %q", version.String())
log.Debugf("os: %q arch: %q", runtime.GOOS, runtime.GOARCH)
log.Debugf("command line: %q", os.Args)
}
log.Info(version.String())
ctx, cancel := context.WithCancel(context.Background())
// Make this the last defer statement in the stack
defer os.Exit(exitCode)
defer log.Exit(exitCode)
// Create a PID file if requested
if config.PIDFile != "" {
f, err := os.Create(config.PIDFile)
if err != nil {
log.Errorf("failed to create pidfile: %s", err.Error())
return
}
// Remove the pid-file when exiting
defer func() {
if err := os.Remove(config.PIDFile); err != nil {
log.Errorf("failed to remove pidfile: %s", err.Error())
}
}()
pid := os.Getpid()
if _, err := f.WriteString(strconv.Itoa(pid)); err != nil {
log.Errorf("failed to write pidfile: %s", err.Error())
return
}
}
groupErrs, ctx := errgroup.WithContext(ctx)
// Setup signal channel for catching user signals
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
if debug {
log.SetLevel(log.DebugLevel)
}
// Intercept WM_QUIT/WM_CLOSE events if on Windows as SIGTERM (noop on other OSs)
winquit.SimulateSigTermOnQuit(sigChan)
// Make sure the qemu socket provided is valid syntax
if len(qemuSocket) > 0 {
uri, err := url.Parse(qemuSocket)
if err != nil || uri == nil {
exitWithError(errors.Wrapf(err, "invalid value for listen-qemu"))
}
if _, err := os.Stat(uri.Path); err == nil && uri.Scheme == "unix" {
exitWithError(errors.Errorf("%q already exists", uri.Path))
}
}
if len(bessSocket) > 0 {
uri, err := url.Parse(bessSocket)
if err != nil || uri == nil {
exitWithError(errors.Wrapf(err, "invalid value for listen-bess"))
}
if uri.Scheme != "unixpacket" {
exitWithError(errors.New("listen-bess must be unixpacket:// address"))
}
if _, err := os.Stat(uri.Path); err == nil {
exitWithError(errors.Errorf("%q already exists", uri.Path))
}
}
if len(vfkitSocket) > 0 {
uri, err := url.Parse(vfkitSocket)
if err != nil || uri == nil {
exitWithError(errors.Wrapf(err, "invalid value for listen-vfkit"))
}
if uri.Scheme != "unixgram" {
exitWithError(errors.New("listen-vfkit must be unixgram:// address"))
}
if _, err := os.Stat(uri.Path); err == nil {
exitWithError(errors.Errorf("%q already exists", uri.Path))
}
}
if vpnkitSocket != "" && qemuSocket != "" {
exitWithError(errors.New("cannot use qemu and vpnkit protocol at the same time"))
}
if vpnkitSocket != "" && bessSocket != "" {
exitWithError(errors.New("cannot use bess and vpnkit protocol at the same time"))
}
if qemuSocket != "" && bessSocket != "" {
exitWithError(errors.New("cannot use qemu and bess protocol at the same time"))
}
// If the given port is not between the privileged ports
// and the oft considered maximum port, return an error.
if sshPort != -1 && sshPort < 1024 || sshPort > 65535 {
exitWithError(errors.New("ssh-port value must be between 1024 and 65535"))
}
protocol := types.HyperKitProtocol
if qemuSocket != "" {
protocol = types.QemuProtocol
}
if bessSocket != "" {
protocol = types.BessProtocol
}
if vfkitSocket != "" {
protocol = types.VfkitProtocol
}
if c := len(forwardSocket); c != len(forwardDest) || c != len(forwardUser) || c != len(forwardIdentify) {
exitWithError(errors.New("--forward-sock, --forward-dest, --forward-user, and --forward-identity must all be specified together, " +
"the same number of times, or not at all"))
}
for i := 0; i < len(forwardSocket); i++ {
_, err := os.Stat(forwardIdentify[i])
if err != nil {
exitWithError(errors.Wrapf(err, "Identity file %s can't be loaded", forwardIdentify[i]))
}
}
// Create a PID file if requested
if len(pidFile) > 0 {
f, err := os.Create(pidFile)
if err != nil {
exitWithError(err)
}
// Remove the pid-file when exiting
defer func() {
if err := os.Remove(pidFile); err != nil {
log.Error(err)
}
}()
pid := os.Getpid()
if _, err := f.WriteString(strconv.Itoa(pid)); err != nil {
exitWithError(err)
}
}
config := types.Configuration{
Debug: debug,
CaptureFile: pcapFile,
MTU: mtu,
Subnet: "192.168.127.0/24",
GatewayIP: gatewayIP,
GatewayMacAddress: "5a:94:ef:e4:0c:dd",
DHCPStaticLeases: map[string]string{
"192.168.127.2": "5a:94:ef:e4:0c:ee",
},
DNS: []types.Zone{
{
Name: "containers.internal.",
Records: []types.Record{
{
Name: gateway,
IP: net.ParseIP(gatewayIP),
},
{
Name: host,
IP: net.ParseIP(hostIP),
},
},
},
{
Name: "docker.internal.",
Records: []types.Record{
{
Name: gateway,
IP: net.ParseIP(gatewayIP),
},
{
Name: host,
IP: net.ParseIP(hostIP),
},
},
},
},
DNSSearchDomains: searchDomains(),
Forwards: getForwardsMap(sshPort, sshHostPort),
NAT: map[string]string{
hostIP: "127.0.0.1",
},
GatewayVirtualIPs: []string{hostIP},
VpnKitUUIDMacAddresses: map[string]string{
"c3d68012-0208-11ea-9fd7-f2189899ab08": "5a:94:ef:e4:0c:ee",
},
Protocol: protocol,
Ec2MetadataAccess: ec2MetadataAccess,
}
groupErrs.Go(func() error {
return run(ctx, groupErrs, &config, endpoints, servicesEndpoint)
return run(ctx, groupErrs, config)
})
// Wait for something to happen
@@ -310,14 +115,18 @@ func (i *arrayFlags) Set(value string) error {
return nil
}
func run(ctx context.Context, g *errgroup.Group, configuration *types.Configuration, endpoints []string, servicesEndpoint string) error {
vn, err := virtualnetwork.New(configuration)
func InDebugMode() bool {
return log.GetLevel().String() == "debug"
}
func run(ctx context.Context, g *errgroup.Group, config *GvproxyConfig) error {
vn, err := virtualnetwork.New(&config.Stack)
if err != nil {
return err
}
log.Info("waiting for clients...")
for _, endpoint := range endpoints {
for _, endpoint := range config.Listen {
log.Infof("listening %s", endpoint)
ln, err := transport.Listen(endpoint)
if err != nil {
@@ -326,16 +135,16 @@ func run(ctx context.Context, g *errgroup.Group, configuration *types.Configurat
httpServe(ctx, g, ln, withProfiler(vn))
}
if servicesEndpoint != "" {
log.Infof("enabling services API. Listening %s", servicesEndpoint)
ln, err := transport.Listen(servicesEndpoint)
if config.Services != "" {
log.Infof("enabling services API. Listening %s", config.Services)
ln, err := transport.Listen(config.Services)
if err != nil {
return errors.Wrap(err, "cannot listen")
}
httpServe(ctx, g, ln, vn.ServicesMux())
}
ln, err := vn.Listen("tcp", fmt.Sprintf("%s:80", gatewayIP))
ln, err := vn.Listen("tcp", fmt.Sprintf("%s:80", config.Stack.GatewayIP))
if err != nil {
return err
}
@@ -345,13 +154,13 @@ func run(ctx context.Context, g *errgroup.Group, configuration *types.Configurat
mux.Handle("/services/forwarder/unexpose", vn.Mux())
httpServe(ctx, g, ln, mux)
if debug {
if InDebugMode() {
g.Go(func() error {
debugLog:
for {
select {
case <-time.After(5 * time.Second):
log.Debugf("%v sent to the VM, %v received from the VM\n", humanize.Bytes(vn.BytesSent()), humanize.Bytes(vn.BytesReceived()))
log.Debugf("%s sent to the VM, %s received from the VM\n", humanize.Bytes(vn.BytesSent()), humanize.Bytes(vn.BytesReceived()))
case <-ctx.Done():
break debugLog
}
@@ -360,8 +169,8 @@ func run(ctx context.Context, g *errgroup.Group, configuration *types.Configurat
})
}
if vpnkitSocket != "" {
vpnkitListener, err := transport.Listen(vpnkitSocket)
if config.Interfaces.VPNKit != "" {
vpnkitListener, err := transport.Listen(config.Interfaces.VPNKit)
if err != nil {
return errors.Wrap(err, "vpnkit listen error")
}
@@ -387,8 +196,8 @@ func run(ctx context.Context, g *errgroup.Group, configuration *types.Configurat
})
}
if qemuSocket != "" {
qemuListener, err := transport.Listen(qemuSocket)
if config.Interfaces.Qemu != "" {
qemuListener, err := transport.Listen(config.Interfaces.Qemu)
if err != nil {
return errors.Wrap(err, "qemu listen error")
}
@@ -396,9 +205,9 @@ func run(ctx context.Context, g *errgroup.Group, configuration *types.Configurat
g.Go(func() error {
<-ctx.Done()
if err := qemuListener.Close(); err != nil {
log.Errorf("error closing %s: %q", qemuSocket, err)
log.Errorf("error closing %s: %q", config.Interfaces.Qemu, err)
}
return os.Remove(qemuSocket)
return os.Remove(config.Interfaces.Qemu)
})
g.Go(func() error {
@@ -410,8 +219,8 @@ func run(ctx context.Context, g *errgroup.Group, configuration *types.Configurat
})
}
if bessSocket != "" {
bessListener, err := transport.Listen(bessSocket)
if config.Interfaces.Bess != "" {
bessListener, err := transport.Listen(config.Interfaces.Bess)
if err != nil {
return errors.Wrap(err, "bess listen error")
}
@@ -419,23 +228,22 @@ func run(ctx context.Context, g *errgroup.Group, configuration *types.Configurat
g.Go(func() error {
<-ctx.Done()
if err := bessListener.Close(); err != nil {
log.Errorf("error closing %s: %q", bessSocket, err)
log.Errorf("error closing %s: %q", config.Interfaces.Bess, err)
}
return os.Remove(bessSocket)
return os.Remove(config.Interfaces.Bess)
})
g.Go(func() error {
conn, err := bessListener.Accept()
if err != nil {
return errors.Wrap(err, "bess accept error")
}
return vn.AcceptBess(ctx, conn)
})
}
if vfkitSocket != "" {
conn, err := transport.ListenUnixgram(vfkitSocket)
if config.Interfaces.Vfkit != "" {
conn, err := transport.ListenUnixgram(config.Interfaces.Vfkit)
if err != nil {
return errors.Wrap(err, "vfkit listen error")
}
@@ -443,9 +251,9 @@ func run(ctx context.Context, g *errgroup.Group, configuration *types.Configurat
g.Go(func() error {
<-ctx.Done()
if err := conn.Close(); err != nil {
log.Errorf("error closing %s: %q", vfkitSocket, err)
log.Errorf("error closing %s: %q", config.Interfaces.Vfkit, err)
}
vfkitSocketURI, _ := url.Parse(vfkitSocket)
vfkitSocketURI, _ := url.Parse(config.Interfaces.Vfkit)
return os.Remove(vfkitSocketURI.Path)
})
@@ -458,40 +266,40 @@ func run(ctx context.Context, g *errgroup.Group, configuration *types.Configurat
})
}
if stdioSocket != "" {
if config.Interfaces.Stdio != "" {
g.Go(func() error {
conn := stdio.GetStdioConn()
return vn.AcceptStdio(ctx, conn)
})
}
for i := 0; i < len(forwardSocket); i++ {
for i := range config.Forwards {
var (
src *url.URL
err error
)
if strings.Contains(forwardSocket[i], "://") {
src, err = url.Parse(forwardSocket[i])
if strings.Contains(config.Forwards[i].Socket, "://") {
src, err = url.Parse(config.Forwards[i].Socket)
if err != nil {
return err
}
} else {
src = &url.URL{
Scheme: "unix",
Path: forwardSocket[i],
Path: config.Forwards[i].Socket,
}
}
dest := &url.URL{
Scheme: "ssh",
User: url.User(forwardUser[i]),
User: url.User(config.Forwards[i].User),
Host: sshHostPort,
Path: forwardDest[i],
Path: config.Forwards[i].Dest,
}
j := i
g.Go(func() error {
defer os.Remove(forwardSocket[j])
forward, err := sshclient.CreateSSHForward(ctx, src, dest, forwardIdentify[j], vn)
defer os.Remove(config.Forwards[j].Socket)
forward, err := sshclient.CreateSSHForward(ctx, src, dest, config.Forwards[j].Identity, vn)
if err != nil {
return err
}
@@ -544,7 +352,7 @@ func httpServe(ctx context.Context, g *errgroup.Group, ln net.Listener, mux http
func withProfiler(vn *virtualnetwork.VirtualNetwork) http.Handler {
mux := vn.Mux()
if debug {
if InDebugMode() {
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
@@ -553,11 +361,6 @@ func withProfiler(vn *virtualnetwork.VirtualNetwork) http.Handler {
return mux
}
func exitWithError(err error) {
log.Error(err)
os.Exit(1)
}
func searchDomains() []string {
if runtime.GOOS == "darwin" || runtime.GOOS == "linux" {
f, err := os.Open("/etc/resolv.conf")

View File

@@ -7,52 +7,52 @@ import (
type Configuration struct {
// Print packets on stderr
Debug bool
Debug bool `yaml:"debug,omitempty"`
// Record all packets coming in and out in a file that can be read by Wireshark (pcap)
CaptureFile string
CaptureFile string `yaml:"capture-file,omitempty"`
// Length of packet
// Larger packets means less packets to exchange for the same amount of data (and less protocol overhead)
MTU int
MTU int `yaml:"mtu,omitempty"`
// Network reserved for the virtual network
Subnet string
Subnet string `yaml:"subnet,omitempty"`
// IP address of the virtual gateway
GatewayIP string
GatewayIP string `yaml:"gatewayIP,omitempty"`
// MAC address of the virtual gateway
GatewayMacAddress string
GatewayMacAddress string `yaml:"gatewayMacAddress,omitempty"`
// Built-in DNS records that will be served by the DNS server embedded in the gateway
DNS []Zone
DNS []Zone `yaml:"dns,omitempty"`
// List of search domains that will be added in all DHCP replies
DNSSearchDomains []string
DNSSearchDomains []string `yaml:"dnsSearchDomains,omitempty"`
// Port forwarding between the machine running the gateway and the virtual network.
Forwards map[string]string
Forwards map[string]string `yaml:"forwards,omitempty"`
// Address translation of incoming traffic.
// Useful for reaching the host itself (localhost) from the virtual network.
NAT map[string]string
NAT map[string]string `yaml:"nat,omitempty"`
// IPs assigned to the gateway that can answer to ARP requests
GatewayVirtualIPs []string
GatewayVirtualIPs []string `yaml:"gatewayVirtualIPs,omitempty"`
// DHCP static leases. Allow to assign pre-defined IP to virtual machine based on the MAC address
DHCPStaticLeases map[string]string
DHCPStaticLeases map[string]string `yaml:"dhcpStaticLeases,omitempty"`
// Only for Hyperkit
// Allow to assign a pre-defined MAC address to an Hyperkit VM
VpnKitUUIDMacAddresses map[string]string
VpnKitUUIDMacAddresses map[string]string `yaml:"vpnKitUUIDMacAddresses,omitempty"`
// Protocol to be used. Only for /connect mux
Protocol Protocol
Protocol Protocol `yaml:"-"`
// EC2 Metadata Service Access
Ec2MetadataAccess bool
Ec2MetadataAccess bool `yaml:"ec2MetadataAccess,omitempty"`
}
type Protocol string
@@ -71,13 +71,13 @@ const (
)
type Zone struct {
Name string
Records []Record
DefaultIP net.IP
Name string `yaml:"name,omitempty"`
Records []Record `yaml:"records,omitempty"`
DefaultIP net.IP `yaml:"defaultIP,omitempty"`
}
type Record struct {
Name string
IP net.IP
Regexp *regexp.Regexp
Name string `yaml:"name,omitempty"`
IP net.IP `yaml:"ip,omitempty"`
Regexp *regexp.Regexp `yaml:"regexp,omitempty"`
}