Skip to content

Commit df5213b

Browse files
committed
Plain mode: support port forwarding
Signed-off-by: Praful Khanduri <[email protected]>
1 parent e05d782 commit df5213b

File tree

5 files changed

+74
-5
lines changed

5 files changed

+74
-5
lines changed

cmd/limactl/editflags/editflags.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ func RegisterCreate(cmd *cobra.Command, commentPrefix string) {
9696
})
9797

9898
flags.Bool("plain", false, commentPrefix+"Plain mode. Disables mounts, port forwarding, containerd, etc.")
99+
100+
flags.StringSlice("port-forward", nil, commentPrefix+"Port forwards (host:guest), works even in plain mode, e.g., '8080:80,2222:22'")
101+
_ = cmd.RegisterFlagCompletionFunc("port-forward", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
102+
return []string{"8080:80", "2222:22", "3000:3000"}, cobra.ShellCompDirectiveNoFileComp
103+
})
99104
}
100105

101106
func defaultExprFunc(expr string) func(v *flag.Flag) (string, error) {
@@ -206,6 +211,7 @@ func YQExpressions(flags *flag.FlagSet, newInstance bool) ([]string, error) {
206211
false,
207212
false,
208213
},
214+
209215
{
210216
"rosetta",
211217
func(_ *flag.Flag) (string, error) {
@@ -261,6 +267,36 @@ func YQExpressions(flags *flag.FlagSet, newInstance bool) ([]string, error) {
261267
{"disk", d(".disk= \"%sGiB\""), false, false},
262268
{"vm-type", d(".vmType = %q"), true, false},
263269
{"plain", d(".plain = %s"), true, false},
270+
{
271+
"port-forward",
272+
func(_ *flag.Flag) (string, error) {
273+
ss, err := flags.GetStringSlice("port-forward")
274+
if err != nil {
275+
return "", err
276+
}
277+
if len(ss) == 0 {
278+
return "", nil
279+
}
280+
281+
expr := `.portForwards = [`
282+
for i, s := range ss {
283+
parts := strings.Split(s, ":")
284+
if len(parts) != 2 {
285+
return "", fmt.Errorf("invalid port forward format %q, expected HOST:GUEST", s)
286+
}
287+
hostPort := strings.TrimSpace(parts[0])
288+
guestPort := strings.TrimSpace(parts[1])
289+
expr += fmt.Sprintf(`{"hostPort": %s, "guestPort": %s}`, hostPort, guestPort)
290+
if i < len(ss)-1 {
291+
expr += ","
292+
}
293+
}
294+
expr += `]`
295+
return expr, nil
296+
},
297+
false,
298+
false,
299+
},
264300
}
265301
var exprs []string
266302
for _, def := range defs {

pkg/hostagent/hostagent.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,11 @@ func (a *HostAgent) Info(_ context.Context) (*hostagentapi.Info, error) {
420420

421421
func (a *HostAgent) startHostAgentRoutines(ctx context.Context) error {
422422
if *a.instConfig.Plain {
423-
logrus.Info("Running in plain mode. Mounts, port forwarding, containerd, etc. will be ignored. Guest agent will not be running.")
423+
if len(a.instConfig.PortForwards) > 0 {
424+
logrus.Info("Running in plain mode. Mounts, containerd, etc. will be ignored. Guest agent will not be running. Port forwarding is enabled via static SSH tunnels.")
425+
} else {
426+
logrus.Info("Running in plain mode. Mounts, port forwarding, containerd, etc. will be ignored. Guest agent will not be running.")
427+
}
424428
}
425429
a.onClose = append(a.onClose, func() error {
426430
logrus.Debugf("shutting down the SSH master")
@@ -478,7 +482,7 @@ sudo chown -R "${USER}" /run/host-services`
478482
return errors.Join(unlockErrs...)
479483
})
480484
}
481-
if !*a.instConfig.Plain {
485+
if !*a.instConfig.Plain || len(a.instConfig.PortForwards) > 0 && *a.instConfig.Plain {
482486
go a.watchGuestAgentEvents(ctx)
483487
}
484488
if err := a.waitForRequirements("optional", a.optionalRequirements()); err != nil {
@@ -543,6 +547,26 @@ func (a *HostAgent) watchGuestAgentEvents(ctx context.Context) {
543547
}
544548
}
545549

550+
if *a.instConfig.Plain {
551+
logrus.Debugf("Setting up static TCP port forwarding for plain mode")
552+
for _, rule := range a.instConfig.PortForwards {
553+
if rule.GuestSocket == "" {
554+
guest := &guestagentapi.IPPort{
555+
Ip: rule.GuestIP.String(),
556+
Port: int32(rule.GuestPort),
557+
Protocol: rule.Proto,
558+
}
559+
local, remote := a.portForwarder.forwardingAddresses(guest)
560+
if local != "" {
561+
logrus.Infof("Setting up static TCP forwarding from %s to %s", remote, local)
562+
if err := forwardTCP(ctx, a.sshConfig, a.sshLocalPort, local, remote, verbForward); err != nil {
563+
logrus.WithError(err).Warnf("failed to set up static TCP forwarding %s -> %s", remote, local)
564+
}
565+
}
566+
}
567+
}
568+
}
569+
546570
localUnix := filepath.Join(a.instDir, filenames.GuestAgentSock)
547571
remoteUnix := "/run/lima-guestagent.sock"
548572

pkg/limayaml/defaults.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -916,7 +916,6 @@ func fixUpForPlainMode(y *LimaYAML) {
916916
return
917917
}
918918
y.Mounts = nil
919-
y.PortForwards = nil
920919
y.Containerd.System = ptr.Of(false)
921920
y.Containerd.User = ptr.Of(false)
922921
y.Rosetta.BinFmt = ptr.Of(false)

pkg/limayaml/validate.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,16 @@ func Validate(y *LimaYAML, warn bool) error {
415415
}
416416
}
417417
}
418+
if y.Plain != nil && *y.Plain {
419+
const portRangeWarnThreshold = 10
420+
for i, rule := range y.PortForwards {
421+
guestRange := rule.GuestPortRange[1] - rule.GuestPortRange[0] + 1
422+
hostRange := rule.HostPortRange[1] - rule.HostPortRange[0] + 1
423+
if guestRange > portRangeWarnThreshold || hostRange > portRangeWarnThreshold {
424+
logrus.Warnf("[plain mode] portForwards[%d] covers a range of more than %d ports (guest: %d, host: %d). All ports will be forwarded unconditionally, which may be inefficient.", i, portRangeWarnThreshold, guestRange, hostRange)
425+
}
426+
}
427+
}
418428

419429
return errs
420430
}

templates/default.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ networks:
422422

423423
# Port forwarding rules. Forwarding between ports 22 and ssh.localPort cannot be overridden.
424424
# Rules are checked sequentially until the first one matches.
425-
# portForwards:
425+
portForwards: null
426426
# - guestPort: 443
427427
# hostIP: "0.0.0.0" # overrides the default value "127.0.0.1"; allows privileged port forwarding
428428
# # default: hostPort: 443 (same as guestPort)
@@ -613,4 +613,4 @@ nestedVirtualization: null
613613

614614
# ===================================================================== #
615615
# END OF TEMPLATE
616-
# ===================================================================== #
616+
# ===================================================================== #

0 commit comments

Comments
 (0)