Skip to content

Commit 5241aea

Browse files
committed
Implement rules for all guestIP and hostIP setting
Use net.IP internally instead of strings, and implement even default block and forwarding rules via data instead of code. Signed-off-by: Jan Dubois <[email protected]>
1 parent 3334a87 commit 5241aea

File tree

8 files changed

+138
-104
lines changed

8 files changed

+138
-104
lines changed

pkg/guestagent/api/api.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ type ErrorJSON struct {
1111
Message string `json:"message"`
1212
}
1313

14+
var (
15+
IPv4loopback1 = net.IPv4(127,0,0,1)
16+
)
17+
1418
type IPPort struct {
1519
IP net.IP `json:"ip"`
1620
Port int `json:"port"`

pkg/guestagent/guestagent_linux.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"encoding/binary"
66
"errors"
7-
"net"
87
"reflect"
98
"time"
109

@@ -116,18 +115,13 @@ func (a *agent) LocalPorts(ctx context.Context) ([]api.IPPort, error) {
116115
return res, err
117116
}
118117

119-
ipv4Loopback1 := net.ParseIP("127.0.0.1")
120118
for _, f := range tcpParsed {
121119
switch f.Kind {
122120
case procnettcp.TCP, procnettcp.TCP6:
123121
default:
124122
continue
125123
}
126-
isListen := f.State == procnettcp.TCPListen
127-
// We do NOT use net.IP.IsLoopback() here, because we want to exclude addresses like 127.0.0.53 here.
128-
isLocal := f.IP.Equal(net.IPv4zero) || f.IP.Equal(net.IPv6zero) ||
129-
f.IP.Equal(ipv4Loopback1) || f.IP.Equal(net.IPv6loopback)
130-
if isListen && isLocal {
124+
if f.State == procnettcp.TCPListen {
131125
res = append(res,
132126
api.IPPort{
133127
IP: f.IP,

pkg/hostagent/hostagent.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"fmt"
88
"io"
9+
"net"
910
"os"
1011
"os/exec"
1112
"path/filepath"
@@ -86,17 +87,25 @@ func New(instName string, stdout, stderr io.Writer, sigintCh chan os.Signal) (*H
8687
AdditionalArgs: sshArgs,
8788
}
8889

89-
ports := make([]limayaml.Port, 0, 2+len(y.Ports))
90-
ports = append(ports, limayaml.Port{GuestPort: sshGuestPort, Ignore: true})
91-
ports = append(ports, limayaml.Port{GuestPort: y.SSH.LocalPort, Ignore: true})
92-
ports = append(ports, y.Ports...)
90+
rules := make([]limayaml.PortForward, 0, 3+len(y.PortForwards))
91+
// Block ports 22 and sshLocalPort on all IPs
92+
for _, port := range []int{sshGuestPort, y.SSH.LocalPort} {
93+
rule := limayaml.PortForward{GuestIP: net.IPv4zero, GuestPort: port, Ignore: true}
94+
limayaml.FillPortForwardDefaults(&rule)
95+
rules = append(rules, rule)
96+
}
97+
rules = append(rules, y.PortForwards...)
98+
// Default forwards for all non-privileged ports from "127.0.0.1" and "::1"
99+
rule := limayaml.PortForward{GuestIP: guestagentapi.IPv4loopback1}
100+
limayaml.FillPortForwardDefaults(&rule)
101+
rules = append(rules, rule)
93102

94103
a := &HostAgent{
95104
l: l,
96105
y: y,
97106
instDir: inst.Dir,
98107
sshConfig: sshConfig,
99-
portForwarder: newPortForwarder(l, sshConfig, y.SSH.LocalPort, ports),
108+
portForwarder: newPortForwarder(l, sshConfig, y.SSH.LocalPort, rules),
100109
qExe: qExe,
101110
qArgs: qArgs,
102111
sigintCh: sigintCh,

pkg/hostagent/port.go

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ package hostagent
22

33
import (
44
"context"
5-
"fmt"
6-
"strconv"
5+
"net"
76

87
"github.com/AkihiroSuda/lima/pkg/guestagent/api"
98
"github.com/AkihiroSuda/lima/pkg/limayaml"
@@ -16,48 +15,60 @@ type portForwarder struct {
1615
sshConfig *ssh.SSHConfig
1716
sshHostPort int
1817
tcp map[int]struct{} // key: int (NOTE: this might be inconsistent with the actual status of SSH master)
19-
ports []limayaml.Port
18+
rules []limayaml.PortForward
2019
}
2120

2221
const sshGuestPort = 22
2322

24-
func newPortForwarder(l *logrus.Logger, sshConfig *ssh.SSHConfig, sshHostPort int, ports []limayaml.Port) *portForwarder {
23+
func newPortForwarder(l *logrus.Logger, sshConfig *ssh.SSHConfig, sshHostPort int, rules []limayaml.PortForward) *portForwarder {
2524
return &portForwarder{
2625
l: l,
2726
sshConfig: sshConfig,
2827
sshHostPort: sshHostPort,
2928
tcp: make(map[int]struct{}),
30-
ports: ports,
29+
rules: rules,
3130
}
3231
}
3332

3433
func (pf *portForwarder) forwardingAddresses(guest api.IPPort) (string, string) {
35-
for _, port := range pf.ports {
36-
if port.GuestPortRange[0] <= guest.Port && guest.Port <= port.GuestPortRange[1] {
37-
guestAddr := fmt.Sprintf("%s:%d", port.GuestIP, guest.Port)
38-
if port.Ignore {
39-
return guestAddr, ""
34+
for _, rule := range pf.rules {
35+
if guest.Port < rule.GuestPortRange[0] || guest.Port > rule.GuestPortRange[1] {
36+
continue
37+
}
38+
switch {
39+
case guest.IP.IsUnspecified():
40+
case guest.IP.Equal(rule.GuestIP):
41+
case guest.IP.Equal(net.IPv6loopback) && rule.GuestIP.Equal(api.IPv4loopback1):
42+
case rule.GuestIP.IsUnspecified():
43+
default:
44+
continue
45+
}
46+
if rule.Ignore {
47+
if guest.IP.IsUnspecified() && !rule.GuestIP.IsUnspecified() {
48+
continue
4049
}
41-
offset := port.HostPortRange[0] - port.GuestPortRange[0]
42-
hostAddr := fmt.Sprintf("%s:%d", port.HostIP, guest.Port + offset)
43-
return guestAddr, hostAddr
50+
break
51+
}
52+
host := api.IPPort{
53+
IP: rule.HostIP,
54+
Port: guest.Port + rule.HostPortRange[0] - rule.GuestPortRange[0],
4455
}
56+
return host.String(), guest.String()
4557
}
46-
addr := "127.0.0.1:" + strconv.Itoa(guest.Port)
47-
return addr, addr
58+
return "", guest.String()
4859
}
4960

5061
func (pf *portForwarder) OnEvent(ctx context.Context, ev api.Event) {
5162
for _, f := range ev.LocalPortsRemoved {
5263
// pf.tcp might be inconsistent with the actual state of the SSH master,
5364
// so we always attempt to cancel forwarding, even when f.Port is not tracked in pf.tcp.
54-
guestAddr, hostAddr := pf.forwardingAddresses(f)
55-
if hostAddr == "" {
65+
local, remote := pf.forwardingAddresses(f)
66+
if local == "" {
5667
continue
5768
}
58-
pf.l.Infof("Stopping forwarding TCP from %s to %s", guestAddr, hostAddr)
69+
pf.l.Infof("Stopping forwarding TCP from %s to %s", remote, local)
5970
verbCancel := true
60-
if err := forwardSSH(ctx, pf.sshConfig, pf.sshHostPort, hostAddr, guestAddr, verbCancel); err != nil {
71+
if err := forwardSSH(ctx, pf.sshConfig, pf.sshHostPort, local, remote, verbCancel); err != nil {
6172
if _, ok := pf.tcp[f.Port]; ok {
6273
pf.l.WithError(err).Warnf("failed to stop forwarding TCP port %d", f.Port)
6374
} else {
@@ -67,14 +78,14 @@ func (pf *portForwarder) OnEvent(ctx context.Context, ev api.Event) {
6778
delete(pf.tcp, f.Port)
6879
}
6980
for _, f := range ev.LocalPortsAdded {
70-
guestAddr, hostAddr := pf.forwardingAddresses(f)
71-
if hostAddr == "" {
72-
pf.l.Infof("Not forwarding TCP from %s", guestAddr)
81+
local, remote := pf.forwardingAddresses(f)
82+
if local == "" {
83+
pf.l.Infof("Not forwarding TCP %s", remote)
7384
continue
7485
}
75-
pf.l.Infof("Forwarding TCP from %s to %s", guestAddr, hostAddr)
76-
if err := forwardSSH(ctx, pf.sshConfig, pf.sshHostPort, hostAddr, guestAddr, false); err != nil {
77-
pf.l.WithError(err).Warnf("failed to setting up forward TCP port %d (negligible if already forwarded)", f.Port)
86+
pf.l.Infof("Forwarding TCP from %s to %s", remote, local)
87+
if err := forwardSSH(ctx, pf.sshConfig, pf.sshHostPort, local, remote, false); err != nil {
88+
pf.l.WithError(err).Warnf("failed to set up forwarding TCP port %d (negligible if already forwarded)", f.Port)
7889
} else {
7990
pf.tcp[f.Port] = struct{}{}
8091
}

pkg/limayaml/default.yaml

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,25 +73,31 @@ containerd:
7373
# Default: true
7474
user: true
7575

76-
# port forwarding rules.
77-
# By default guest ports are forwarded to the same port on the 127.0.0.1 interface on the host.
78-
# ports:
79-
76+
# Port forwarding rules. Forwarding between ports 22 and ssh.localPort cannot be overridden.
77+
# Rules are checked sequentially until the first one matches.
78+
# portForwards:
8079
# - guestPort: 443
81-
# hostIP: "0.0.0.0" # overrides the default value "127.0.0.1"
82-
# # default: hostPort: 443
83-
# # default: guestIP: "127.0.0.1" (only valid value right now)
80+
# hostIP: "0.0.0.0" # overrides the default value "127.0.0.1"; allows privileged port forwarding
81+
# # default: hostPort: 443 (same as guestPort)
82+
# # default: guestIP: "127.0.0.1" (also matches bind addresses "0.0.0.0", "::", and "::1")
8483
# # default: proto: "tcp" (only valid value right now)
85-
8684
# - guestPortRange: [4000, 4999]
8785
# hostIP: "0.0.0.0" # overrides the default value "127.0.0.1"
8886
# # default: hostPortRange: [4000, 4999] (must specify same number of ports as guestPortRange)
89-
9087
# - guestPort: 80
9188
# hostPort: 8080 # overrides the default value 80
92-
89+
# - guestIP: "127.0.0.2" # overrides the default value "127.0.0.1"
90+
# hostIP: "127.0.0.2" # overrides the default value "127.0.0.1"
91+
# # default: guestPortRange: [1024, 65535]
92+
# # default: hostPortRange: [1024, 65535]
9393
# - guestPort: 8888
9494
# ignore: true (don't forward this port)
95+
# # Lima internally appends this fallback rule at the end:
96+
# - guestIP: "127.0.0.1"
97+
# guestPortRange: [1024, 65535]
98+
# hostIP: "127.0.0.1"
99+
# hostPortRange: [1024, 65535]
100+
# # Any port still not matched by a rule will not be forwarded (ignored)
95101

96102
# Provisioning scripts need to be idempotent because they might be called
97103
# multiple times, e.g. when the host VM is being restarted.

pkg/limayaml/defaults.go

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package limayaml
33
import (
44
"fmt"
55
"runtime"
6+
7+
"github.com/AkihiroSuda/lima/pkg/guestagent/api"
68
)
79

810
func FillDefault(y *LimaYAML) {
@@ -49,27 +51,37 @@ func FillDefault(y *LimaYAML) {
4951
probe.Description = fmt.Sprintf("user probe %d/%d", i+1, len(y.Probes))
5052
}
5153
}
52-
for i := range y.Ports {
53-
port := &y.Ports[i]
54-
if port.GuestIP == "" {
55-
port.GuestIP = "127.0.0.1"
56-
}
57-
if port.GuestPortRange[0] == 0 && port.GuestPortRange[1] == 0 {
58-
port.GuestPortRange[0] = port.GuestPort
59-
port.GuestPortRange[1] = port.GuestPort
60-
}
61-
if port.HostIP == "" {
62-
port.HostIP = "127.0.0.1"
63-
}
64-
if port.HostPortRange[0] == 0 && port.HostPortRange[1] == 0 {
65-
if port.HostPort == 0 {
66-
port.HostPort = port.GuestPortRange[0]
67-
}
68-
port.HostPortRange[0] = port.HostPort
69-
port.HostPortRange[1] = port.HostPort
54+
for i := range y.PortForwards {
55+
FillPortForwardDefaults(&y.PortForwards[i])
56+
// After defaults processing the singular HostPort and GuestPort values should not be used again.
57+
}
58+
}
59+
60+
func FillPortForwardDefaults(rule *PortForward) {
61+
if rule.Proto == "" {
62+
rule.Proto = TCP
63+
}
64+
if rule.GuestIP == nil {
65+
rule.GuestIP = api.IPv4loopback1
66+
}
67+
if rule.HostIP == nil {
68+
rule.HostIP = api.IPv4loopback1
69+
}
70+
if rule.GuestPortRange[0] == 0 && rule.GuestPortRange[1] == 0 {
71+
if rule.GuestPort == 0 {
72+
rule.GuestPortRange[0] = 1024
73+
rule.GuestPortRange[1] = 65535
74+
} else {
75+
rule.GuestPortRange[0] = rule.GuestPort
76+
rule.GuestPortRange[1] = rule.GuestPort
7077
}
71-
if port.Proto == "" {
72-
port.Proto = TCP
78+
}
79+
if rule.HostPortRange[0] == 0 && rule.HostPortRange[1] == 0 {
80+
if rule.HostPort == 0 {
81+
rule.HostPortRange = rule.GuestPortRange
82+
} else {
83+
rule.HostPortRange[0] = rule.HostPort
84+
rule.HostPortRange[1] = rule.HostPort
7385
}
7486
}
7587
}

pkg/limayaml/limayaml.go

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
package limayaml
22

3-
import "github.com/opencontainers/go-digest"
3+
import (
4+
"net"
5+
6+
"github.com/opencontainers/go-digest"
7+
)
48

59
type LimaYAML struct {
6-
Arch Arch `yaml:"arch,omitempty"`
7-
Images []File `yaml:"images"` // REQUIRED
8-
CPUs int `yaml:"cpus,omitempty"`
9-
Memory string `yaml:"memory,omitempty"` // go-units.RAMInBytes
10-
Disk string `yaml:"disk,omitempty"` // go-units.RAMInBytes
11-
Mounts []Mount `yaml:"mounts,omitempty"`
12-
SSH SSH `yaml:"ssh,omitempty"` // REQUIRED (FIXME)
13-
Firmware Firmware `yaml:"firmware,omitempty"`
14-
Video Video `yaml:"video,omitempty"`
15-
Provision []Provision `yaml:"provision,omitempty"`
16-
Containerd Containerd `yaml:"containerd,omitempty"`
17-
Probes []Probe `yaml:"probes,omitempty"`
18-
Ports []Port `yaml:"ports,omitempty"`
10+
Arch Arch `yaml:"arch,omitempty"`
11+
Images []File `yaml:"images"` // REQUIRED
12+
CPUs int `yaml:"cpus,omitempty"`
13+
Memory string `yaml:"memory,omitempty"` // go-units.RAMInBytes
14+
Disk string `yaml:"disk,omitempty"` // go-units.RAMInBytes
15+
Mounts []Mount `yaml:"mounts,omitempty"`
16+
SSH SSH `yaml:"ssh,omitempty"` // REQUIRED (FIXME)
17+
Firmware Firmware `yaml:"firmware,omitempty"`
18+
Video Video `yaml:"video,omitempty"`
19+
Provision []Provision `yaml:"provision,omitempty"`
20+
Containerd Containerd `yaml:"containerd,omitempty"`
21+
Probes []Probe `yaml:"probes,omitempty"`
22+
PortForwards []PortForward `yaml:"portForwards,omitempty"`
1923
}
2024

2125
type Arch = string
@@ -91,11 +95,11 @@ const (
9195
TCP Proto = "tcp"
9296
)
9397

94-
type Port struct {
95-
GuestIP string `yaml:"guestIP,omitempty"`
98+
type PortForward struct {
99+
GuestIP net.IP `yaml:"guestIP,omitempty"`
96100
GuestPort int `yaml:"guestPort,omitempty"`
97101
GuestPortRange [2]int `yaml:"guestPortRange,omitempty"`
98-
HostIP string `yaml:"hostIP,omitempty"`
102+
HostIP net.IP `yaml:"hostIP,omitempty"`
99103
HostPort int `yaml:"hostPort,omitempty"`
100104
HostPortRange [2]int `yaml:"hostPortRange,omitempty"`
101105
Proto Proto `yaml:"proto,omitempty"`

0 commit comments

Comments
 (0)