mirror of
https://github.com/containers/gvisor-tap-vsock.git
synced 2025-09-26 21:01:42 +08:00
Merge pull request #484 from MrEcco/main
Enable config file based configuration
This commit is contained in:
4
.github/workflows/go.yml
vendored
4
.github/workflows/go.yml
vendored
@@ -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
428
cmd/gvproxy/config.go
Normal 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
41
cmd/gvproxy/config.yaml
Normal 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
542
cmd/gvproxy/config_test.go
Normal 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
|
||||
`,
|
||||
},
|
||||
}
|
||||
}
|
@@ -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")
|
||||
|
@@ -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"`
|
||||
}
|
||||
|
Reference in New Issue
Block a user