Repository: lima-vm/lima
Pull request #2605 (2024-09-10 16:17:05)

Allow separate rules for UDP port forwarding
Additionally a `Proto` value of `"any"` would match both TCP and UDP ports.

The default for `Proto` remains `tcp`, both for backwards compatibility, and because `any` rarely makes sense, except e.g. when you want to disable all port forwarding for any protocol.

We should also update `hack/test-portforwarding.pl` to cover UDP port forwarding as well. Maybe in a separate PR to allow for manual testing of UDP forwarding rules already.


Patch:
diff --git a/examples/default.yaml b/examples/default.yaml
index d071d889..da6f06ed 100644
--- a/examples/default.yaml
+++ b/examples/default.yaml
@@ -356,7 +356,7 @@ networks:
 #   hostIP: "0.0.0.0" # overrides the default value "127.0.0.1"; allows privileged port forwarding
 # # default: hostPort: 443 (same as guestPort)
 # # default: guestIP: "127.0.0.1" (also matches bind addresses "0.0.0.0", "::", and "::1")
-# # default: proto: "tcp" (only valid value right now)
+# # default: proto: "tcp" (other options: "udp, "any")
 #
 # - guestPortRange: [4000, 4999]
 #   hostIP:  "0.0.0.0" # overrides the default value "127.0.0.1"
diff --git a/pkg/hostagent/hostagent.go b/pkg/hostagent/hostagent.go
index 32998b5d..42ea4d83 100644
--- a/pkg/hostagent/hostagent.go
+++ b/pkg/hostagent/hostagent.go
@@ -158,12 +158,16 @@ func New(instName string, stdout io.Writer, signalCh chan os.Signal, opts ...Opt
 	for _, rule := range y.PortForwards {
 		if rule.Ignore && rule.GuestPortRange[0] == 1 && rule.GuestPortRange[1] == 65535 {
 			switch rule.Proto {
-			case limayaml.TCP:
+			case limayaml.ProtoTCP:
 				ignoreTCP = true
 				logrus.Info("TCP port forwarding is disabled (except for SSH)")
-			case limayaml.UDP:
+			case limayaml.ProtoUDP:
 				ignoreUDP = true
 				logrus.Info("UDP port forwarding is disabled")
+			case limayaml.ProtoAny:
+				ignoreTCP = true
+				ignoreUDP = true
+				logrus.Info("TCP (except for SSH) and UDP port forwarding is disabled")
 			}
 		} else {
 			break
diff --git a/pkg/hostagent/port.go b/pkg/hostagent/port.go
index 10b34703..bfb86d1c 100644
--- a/pkg/hostagent/port.go
+++ b/pkg/hostagent/port.go
@@ -52,6 +52,11 @@ func (pf *portForwarder) forwardingAddresses(guest *api.IPPort) (hostAddr, guest
 		if rule.GuestSocket != "" {
 			continue
 		}
+		switch rule.Proto {
+		case limayaml.ProtoTCP, limayaml.ProtoAny:
+		default:
+			continue
+		}
 		if guest.Port < int32(rule.GuestPortRange[0]) || guest.Port > int32(rule.GuestPortRange[1]) {
 			continue
 		}
diff --git a/pkg/limayaml/defaults.go b/pkg/limayaml/defaults.go
index d3cc7ef8..2ba3534b 100644
--- a/pkg/limayaml/defaults.go
+++ b/pkg/limayaml/defaults.go
@@ -807,7 +807,7 @@ func executeHostTemplate(format, instDir string, param map[string]string) (bytes
 
 func FillPortForwardDefaults(rule *PortForward, instDir string, param map[string]string) {
 	if rule.Proto == "" {
-		rule.Proto = TCP
+		rule.Proto = ProtoTCP
 	}
 	if rule.GuestIP == nil {
 		if rule.GuestIPMustBeZero {
diff --git a/pkg/limayaml/limayaml.go b/pkg/limayaml/limayaml.go
index f932e6aa..24e9b3c7 100644
--- a/pkg/limayaml/limayaml.go
+++ b/pkg/limayaml/limayaml.go
@@ -213,8 +213,9 @@ type Probe struct {
 type Proto = string
 
 const (
-	TCP Proto = "tcp"
-	UDP Proto = "udp"
+	ProtoTCP Proto = "tcp"
+	ProtoUDP Proto = "udp"
+	ProtoAny Proto = "any"
 )
 
 type PortForward struct {
diff --git a/pkg/limayaml/validate.go b/pkg/limayaml/validate.go
index b70fe5a7..b746535e 100644
--- a/pkg/limayaml/validate.go
+++ b/pkg/limayaml/validate.go
@@ -282,8 +282,10 @@ func Validate(y *LimaYAML, warn bool) error {
 			return fmt.Errorf("field `%s.hostSocket` must be less than UNIX_PATH_MAX=%d characters, but is %d",
 				field, osutil.UnixPathMax, len(rule.HostSocket))
 		}
-		if rule.Proto != TCP {
-			return fmt.Errorf("field `%s.proto` must be %q", field, TCP)
+		switch rule.Proto {
+		case ProtoTCP, ProtoUDP, ProtoAny:
+		default:
+			return fmt.Errorf("field `%s.proto` must be %q, %q, or %q", field, ProtoTCP, ProtoUDP, ProtoAny)
 		}
 		if rule.Reverse && rule.GuestSocket == "" {
 			return fmt.Errorf("field `%s.reverse` must be %t", field, false)
diff --git a/pkg/portfwd/forward.go b/pkg/portfwd/forward.go
index c6bad28e..e6475dfa 100644
--- a/pkg/portfwd/forward.go
+++ b/pkg/portfwd/forward.go
@@ -60,6 +60,9 @@ func (fw *Forwarder) forwardingAddresses(guest *api.IPPort) (hostAddr, guestAddr
 		if rule.GuestSocket != "" {
 			continue
 		}
+		if rule.Proto != limayaml.ProtoAny && rule.Proto != guest.Protocol {
+			continue
+		}
 		if guest.Port < int32(rule.GuestPortRange[0]) || guest.Port > int32(rule.GuestPortRange[1]) {
 			continue
 		}


Test patch:
diff --git a/pkg/limayaml/defaults_test.go b/pkg/limayaml/defaults_test.go
index 16fff194..dd40e667 100644
--- a/pkg/limayaml/defaults_test.go
+++ b/pkg/limayaml/defaults_test.go
@@ -114,7 +114,7 @@ func TestFillDefault(t *testing.T) {
 		GuestPortRange: [2]int{1, 65535},
 		HostIP:         IPv4loopback1,
 		HostPortRange:  [2]int{1, 65535},
-		Proto:          TCP,
+		Proto:          ProtoTCP,
 		Reverse:        false,
 	}
 
@@ -399,7 +399,7 @@ func TestFillDefault(t *testing.T) {
 			HostIP:         IPv4loopback1,
 			HostPort:       80,
 			HostPortRange:  [2]int{80, 80},
-			Proto:          TCP,
+			Proto:          ProtoTCP,
 		}},
 		CopyToHost: []CopyToHost{{}},
 		Env: map[string]string{
@@ -616,7 +616,7 @@ func TestFillDefault(t *testing.T) {
 			HostIP:         IPv4loopback1,
 			HostPort:       8080,
 			HostPortRange:  [2]int{8080, 8080},
-			Proto:          TCP,
+			Proto:          ProtoTCP,
 		}},
 		CopyToHost: []CopyToHost{{}},
 		Env: map[string]string{