Skip to content

Commit 34f00b0

Browse files
committed
Add static bool field for static port forwarding
Signed-off-by: Praful Khanduri <[email protected]>
1 parent df5213b commit 34f00b0

File tree

6 files changed

+171
-33
lines changed

6 files changed

+171
-33
lines changed

cmd/limactl/editflags/editflags.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,13 @@ func RegisterCreate(cmd *cobra.Command, commentPrefix string) {
9797

9898
flags.Bool("plain", false, commentPrefix+"Plain mode. Disables mounts, port forwarding, containerd, etc.")
9999

100-
flags.StringSlice("port-forward", nil, commentPrefix+"Port forwards (host:guest), works even in plain mode, e.g., '8080:80,2222:22'")
100+
flags.StringSlice("port-forward", nil, commentPrefix+"Port forwards (host:guest), e.g., '8080:80,2222:22'")
101101
_ = cmd.RegisterFlagCompletionFunc("port-forward", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
102-
return []string{"8080:80", "2222:22", "3000:3000"}, cobra.ShellCompDirectiveNoFileComp
102+
return []string{"8080:80", "3000:3000"}, cobra.ShellCompDirectiveNoFileComp
103+
})
104+
flags.StringSlice("static-port-forward", nil, commentPrefix+"Static port forwards (host:guest), works even in plain mode, e.g., '8080:80,2222:22'")
105+
_ = cmd.RegisterFlagCompletionFunc("static-port-forward", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
106+
return []string{"8080:80", "3000:3000"}, cobra.ShellCompDirectiveNoFileComp
103107
})
104108
}
105109

@@ -268,25 +272,25 @@ func YQExpressions(flags *flag.FlagSet, newInstance bool) ([]string, error) {
268272
{"vm-type", d(".vmType = %q"), true, false},
269273
{"plain", d(".plain = %s"), true, false},
270274
{
271-
"port-forward",
275+
"static-port-forward",
272276
func(_ *flag.Flag) (string, error) {
273-
ss, err := flags.GetStringSlice("port-forward")
277+
ss, err := flags.GetStringSlice("static-port-forward")
274278
if err != nil {
275279
return "", err
276280
}
277281
if len(ss) == 0 {
278282
return "", nil
279283
}
280284

281-
expr := `.portForwards = [`
285+
expr := `.portForwards += [`
282286
for i, s := range ss {
283287
parts := strings.Split(s, ":")
284288
if len(parts) != 2 {
285-
return "", fmt.Errorf("invalid port forward format %q, expected HOST:GUEST", s)
289+
return "", fmt.Errorf("invalid static port forward format %q, expected HOST:GUEST", s)
286290
}
287291
hostPort := strings.TrimSpace(parts[0])
288292
guestPort := strings.TrimSpace(parts[1])
289-
expr += fmt.Sprintf(`{"hostPort": %s, "guestPort": %s}`, hostPort, guestPort)
293+
expr += fmt.Sprintf(`{"hostPort": %s, "guestPort": %s, "static": true}`, hostPort, guestPort)
290294
if i < len(ss)-1 {
291295
expr += ","
292296
}

pkg/hostagent/hostagent.go

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -420,11 +420,7 @@ 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-
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-
}
423+
logrus.Info("Running in plain mode. Mounts, dynamic port forwarding, containerd, etc. will be ignored. Guest agent will not be running. Static port forwarding is allowed.")
428424
}
429425
a.onClose = append(a.onClose, func() error {
430426
logrus.Debugf("shutting down the SSH master")
@@ -482,9 +478,14 @@ sudo chown -R "${USER}" /run/host-services`
482478
return errors.Join(unlockErrs...)
483479
})
484480
}
485-
if !*a.instConfig.Plain || len(a.instConfig.PortForwards) > 0 && *a.instConfig.Plain {
481+
482+
if !*a.instConfig.Plain {
486483
go a.watchGuestAgentEvents(ctx)
484+
} else {
485+
logrus.Info("Running in plain mode, skipping guest agent events watcher")
486+
a.addStaticPortForwards(ctx)
487487
}
488+
488489
if err := a.waitForRequirements("optional", a.optionalRequirements()); err != nil {
489490
errs = append(errs, err)
490491
}
@@ -547,25 +548,7 @@ func (a *HostAgent) watchGuestAgentEvents(ctx context.Context) {
547548
}
548549
}
549550

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-
}
551+
a.addStaticPortForwards(ctx)
569552

570553
localUnix := filepath.Join(a.instDir, filenames.GuestAgentSock)
571554
remoteUnix := "/run/lima-guestagent.sock"
@@ -630,6 +613,27 @@ func (a *HostAgent) watchGuestAgentEvents(ctx context.Context) {
630613
}
631614
}
632615

616+
func (a *HostAgent) addStaticPortForwards(ctx context.Context) {
617+
for _, rule := range a.instConfig.PortForwards {
618+
if rule.Static {
619+
if rule.GuestSocket == "" {
620+
guest := &guestagentapi.IPPort{
621+
Ip: rule.GuestIP.String(),
622+
Port: int32(rule.GuestPort),
623+
Protocol: rule.Proto,
624+
}
625+
local, remote := a.portForwarder.forwardingAddresses(guest)
626+
if local != "" {
627+
logrus.Infof("Setting up static TCP forwarding from %s to %s", remote, local)
628+
if err := forwardTCP(ctx, a.sshConfig, a.sshLocalPort, local, remote, verbForward); err != nil {
629+
logrus.WithError(err).Warnf("failed to set up static TCP forwarding %s -> %s", remote, local)
630+
}
631+
}
632+
}
633+
}
634+
}
635+
}
636+
633637
func isGuestAgentSocketAccessible(ctx context.Context, client *guestagentclient.GuestAgentClient) bool {
634638
_, err := client.Info(ctx)
635639
return err == nil

pkg/limayaml/defaults.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,7 @@ func fixUpForPlainMode(y *LimaYAML) {
915915
if !*y.Plain {
916916
return
917917
}
918+
deleteNonStaticPortForwards(&y.PortForwards)
918919
y.Mounts = nil
919920
y.Containerd.System = ptr.Of(false)
920921
y.Containerd.User = ptr.Of(false)
@@ -923,6 +924,17 @@ func fixUpForPlainMode(y *LimaYAML) {
923924
y.TimeZone = ptr.Of("")
924925
}
925926

927+
// deleteNonStaticPortForwards removes all non-static port forwarding rules in case of Plain mode.
928+
func deleteNonStaticPortForwards(portForwards *[]PortForward) {
929+
staticPortForwards := make([]PortForward, 0, len(*portForwards))
930+
for _, rule := range *portForwards {
931+
if rule.Static {
932+
staticPortForwards = append(staticPortForwards, rule)
933+
}
934+
}
935+
*portForwards = staticPortForwards
936+
}
937+
926938
func executeGuestTemplate(format, instDir string, user User, param map[string]string) (bytes.Buffer, error) {
927939
tmpl, err := template.New("").Parse(format)
928940
if err == nil {

pkg/limayaml/defaults_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,3 +762,120 @@ func TestContainerdDefault(t *testing.T) {
762762
archives := defaultContainerdArchives()
763763
assert.Assert(t, len(archives) > 0)
764764
}
765+
766+
func TestStaticPortForwarding(t *testing.T) {
767+
tests := []struct {
768+
name string
769+
config LimaYAML
770+
expected []PortForward
771+
}{
772+
{
773+
name: "plain mode with static port forwards",
774+
config: LimaYAML{
775+
Plain: ptr.Of(true),
776+
PortForwards: []PortForward{
777+
{
778+
GuestPort: 8080,
779+
HostPort: 8080,
780+
Static: true,
781+
},
782+
{
783+
GuestPort: 9000,
784+
HostPort: 9000,
785+
Static: false,
786+
},
787+
{
788+
GuestPort: 8081,
789+
HostPort: 8081,
790+
},
791+
},
792+
},
793+
expected: []PortForward{
794+
{
795+
GuestPort: 8080,
796+
HostPort: 8080,
797+
Static: true,
798+
},
799+
},
800+
},
801+
{
802+
name: "non-plain mode with static port forwards",
803+
config: LimaYAML{
804+
Plain: ptr.Of(false),
805+
PortForwards: []PortForward{
806+
{
807+
GuestPort: 8080,
808+
HostPort: 8080,
809+
Static: true,
810+
},
811+
{
812+
GuestPort: 9000,
813+
HostPort: 9000,
814+
Static: false,
815+
},
816+
},
817+
},
818+
expected: []PortForward{
819+
{
820+
GuestPort: 8080,
821+
HostPort: 8080,
822+
Static: true,
823+
},
824+
{
825+
GuestPort: 9000,
826+
HostPort: 9000,
827+
Static: false,
828+
},
829+
},
830+
},
831+
{
832+
name: "plain mode with no static port forwards",
833+
config: LimaYAML{
834+
Plain: ptr.Of(true),
835+
PortForwards: []PortForward{
836+
{
837+
GuestPort: 8080,
838+
HostPort: 8080,
839+
Static: false,
840+
},
841+
{
842+
GuestPort: 9000,
843+
HostPort: 9000,
844+
},
845+
},
846+
},
847+
expected: []PortForward{},
848+
},
849+
}
850+
851+
for _, tt := range tests {
852+
t.Run(tt.name, func(t *testing.T) {
853+
fixUpForPlainMode(&tt.config)
854+
855+
if *tt.config.Plain {
856+
for _, pf := range tt.config.PortForwards {
857+
if !pf.Static {
858+
t.Errorf("Non-static port forward found in plain mode: %+v", pf)
859+
}
860+
}
861+
}
862+
863+
assert.Equal(t, len(tt.config.PortForwards), len(tt.expected),
864+
"Expected %d port forwards, got %d", len(tt.expected), len(tt.config.PortForwards))
865+
866+
for i, expected := range tt.expected {
867+
if i >= len(tt.config.PortForwards) {
868+
t.Errorf("Missing port forward at index %d", i)
869+
continue
870+
}
871+
actual := tt.config.PortForwards[i]
872+
assert.Equal(t, expected.Static, actual.Static,
873+
"Port forward %d: expected Static=%v, got %v", i, expected.Static, actual.Static)
874+
assert.Equal(t, expected.GuestPort, actual.GuestPort,
875+
"Port forward %d: expected GuestPort=%d, got %d", i, expected.GuestPort, actual.GuestPort)
876+
assert.Equal(t, expected.HostPort, actual.HostPort,
877+
"Port forward %d: expected HostPort=%d, got %d", i, expected.HostPort, actual.HostPort)
878+
}
879+
})
880+
}
881+
}

pkg/limayaml/limayaml.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ type PortForward struct {
285285
Proto Proto `yaml:"proto,omitempty" json:"proto,omitempty"`
286286
Reverse bool `yaml:"reverse,omitempty" json:"reverse,omitempty"`
287287
Ignore bool `yaml:"ignore,omitempty" json:"ignore,omitempty"`
288+
Static bool `yaml:"static,omitempty" json:"static,omitempty"` // if true, the port forward is static and will not be removed when the instance is stopped
288289
}
289290

290291
type CopyToHost struct {

templates/default.yaml

Lines changed: 1 addition & 1 deletion
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: null
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)

0 commit comments

Comments
 (0)