Skip to content

Commit cc08d05

Browse files
committed
pkg/hostagent: Support port forwarding to direct guest IP
Change to use `github.com/inetaf/tcpproxy`: - pkg/driver/vz/vsock_forwarder.go - pkg/portfwd/client.go - pkg/portfwdserver/server.go pkg/portfwd: - Use `dialContext` instead of `client` to use `net.Conn` other than `GrpcClientRW`. - Change to create proxies from `dialContext` parameters instead of `conn`. - Add `DialContextToGRPCTunnel()` to return `DialContext` function that connects to GRPC tunnel. pkg/hostagent: - Add `HostAgent.DialContextToGuestIP()` to return `DialContext` function that connects to the guest IP directly. If the guest IP is not known, it returns nil. - Prefer `HostAgent.DialContextToGuestIP()` over `portfwd.DialContextToGRPCTunnel()`. Signed-off-by: Norio Nomura <[email protected]>
1 parent 588db13 commit cc08d05

File tree

8 files changed

+68
-54
lines changed

8 files changed

+68
-54
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ require (
139139
require (
140140
github.com/go-ini/ini v1.67.0 // indirect
141141
github.com/google/jsonschema-go v0.3.0 // indirect
142+
github.com/inetaf/tcpproxy v0.0.0-20250222171855-c4b9df066048
142143
github.com/pmezard/go-difflib v1.0.0 // indirect
143144
go.yaml.in/yaml/v2 v2.4.2 // indirect
144145
go.yaml.in/yaml/v3 v3.0.4 // indirect

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs=
2424
github.com/anchore/go-lzo v0.1.0/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk=
2525
github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU=
2626
github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
27+
github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU=
2728
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
2829
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
2930
github.com/balajiv113/fd v0.0.0-20230330094840-143eec500f3e h1:IdMhFPEfTZQU971tIHx3UhY4l+yCeynprnINrDTSrOc=
@@ -134,6 +135,8 @@ github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u
134135
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
135136
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
136137
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
138+
github.com/inetaf/tcpproxy v0.0.0-20250222171855-c4b9df066048 h1:jaqViOFFlZtkAwqvwZN+id37fosQqR5l3Oki9Dk4hz8=
139+
github.com/inetaf/tcpproxy v0.0.0-20250222171855-c4b9df066048/go.mod h1:Di7LXRyUcnvAcLicFhtM9/MlZl/TNgRSDHORM2c6CMI=
137140
github.com/insomniacslk/dhcp v0.0.0-20240710054256-ddd8a41251c9 h1:LZJWucZz7ztCqY6Jsu7N9g124iJ2kt/O62j3+UchZFg=
138141
github.com/insomniacslk/dhcp v0.0.0-20240710054256-ddd8a41251c9/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
139142
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=

pkg/driver/vz/vsock_forwarder.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"errors"
1111
"net"
1212

13-
"github.com/containers/gvisor-tap-vsock/pkg/tcpproxy"
13+
"github.com/inetaf/tcpproxy"
1414
"github.com/sirupsen/logrus"
1515
)
1616

pkg/hostagent/hostagent.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -875,7 +875,11 @@ func (a *HostAgent) processGuestAgentEvents(ctx context.Context, client *guestag
875875
if useSSHFwd {
876876
a.portForwarder.OnEvent(ctx, ev)
877877
} else {
878-
a.grpcPortForwarder.OnEvent(ctx, client, ev)
878+
dialContext := a.DialContextToGuestIP()
879+
if dialContext == nil {
880+
dialContext = portfwd.DialContextToGRPCTunnel(client)
881+
}
882+
a.grpcPortForwarder.OnEvent(ctx, dialContext, ev)
879883
}
880884
}
881885

@@ -888,6 +892,26 @@ func (a *HostAgent) processGuestAgentEvents(ctx context.Context, client *guestag
888892
return io.EOF
889893
}
890894

895+
// DialContextToGuestIP returns a DialContext function that connects to the guest IP directly.
896+
// If the guest IP is not known, it returns nil.
897+
func (a *HostAgent) DialContextToGuestIP() func(ctx context.Context, network, address string) (net.Conn, error) {
898+
a.guestIPMu.RLock()
899+
defer a.guestIPMu.RUnlock()
900+
if a.guestIP == nil {
901+
return nil
902+
}
903+
return func(ctx context.Context, network, address string) (net.Conn, error) {
904+
var d net.Dialer
905+
_, port, err := net.SplitHostPort(address)
906+
if err != nil {
907+
return nil, err
908+
}
909+
// Host part of address is ignored, because it already has been checked by forwarding rules
910+
// and we want to connect to the guest IP directly.
911+
return d.DialContext(ctx, network, net.JoinHostPort(a.guestIP.String(), port))
912+
}
913+
}
914+
891915
const (
892916
verbForward = "forward"
893917
verbCancel = "cancel"

pkg/portfwd/client.go

Lines changed: 28 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,68 +11,57 @@ import (
1111
"time"
1212

1313
"github.com/containers/gvisor-tap-vsock/pkg/services/forwarder"
14-
"github.com/containers/gvisor-tap-vsock/pkg/tcpproxy"
14+
"github.com/inetaf/tcpproxy"
1515
"github.com/sirupsen/logrus"
1616

1717
"github.com/lima-vm/lima/v2/pkg/guestagent/api"
1818
guestagentclient "github.com/lima-vm/lima/v2/pkg/guestagent/api/client"
1919
)
2020

21-
func HandleTCPConnection(ctx context.Context, client *guestagentclient.GuestAgentClient, conn net.Conn, guestAddr string) {
22-
id := fmt.Sprintf("tcp-%s-%s", conn.LocalAddr().String(), conn.RemoteAddr().String())
21+
func HandleTCPConnection(_ context.Context, dialContext func(ctx context.Context, network string, addr string) (net.Conn, error), conn net.Conn, guestAddr string) {
22+
proxy := tcpproxy.DialProxy{Addr: guestAddr, DialContext: dialContext}
23+
proxy.HandleConn(conn)
24+
}
2325

24-
stream, err := client.Tunnel(ctx)
26+
func HandleUDPConnection(ctx context.Context, dialContext func(ctx context.Context, network string, addr string) (net.Conn, error), conn net.PacketConn, guestAddr string) {
27+
proxy, err := forwarder.NewUDPProxy(conn, func() (net.Conn, error) {
28+
return dialContext(ctx, "udp", guestAddr)
29+
})
2530
if err != nil {
26-
logrus.Errorf("could not open tcp tunnel for id: %s error:%v", id, err)
27-
return
28-
}
29-
30-
// Handshake message to start tunnel
31-
if err := stream.Send(&api.TunnelMessage{Id: id, Protocol: "tcp", GuestAddr: guestAddr}); err != nil {
32-
logrus.Errorf("could not start tcp tunnel for id: %s error:%v", id, err)
31+
logrus.WithError(err).Error("error in udp tunnel proxy")
3332
return
3433
}
3534

36-
rw := &GrpcClientRW{stream: stream, id: id, addr: guestAddr, protocol: "tcp"}
37-
proxy := tcpproxy.DialProxy{DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
38-
return conn, nil
39-
}}
40-
proxy.HandleConn(rw)
35+
defer func() {
36+
err := proxy.Close()
37+
if err != nil {
38+
logrus.WithError(err).Error("error in closing udp tunnel proxy")
39+
}
40+
}()
41+
proxy.Run()
4142
}
4243

43-
func HandleUDPConnection(ctx context.Context, client *guestagentclient.GuestAgentClient, conn net.PacketConn, guestAddr string) {
44-
var udpConnectionCounter atomic.Uint32
45-
initialID := fmt.Sprintf("udp-%s", conn.LocalAddr().String())
46-
44+
func DialContextToGRPCTunnel(client *guestagentclient.GuestAgentClient) func(ctx context.Context, network, addr string) (net.Conn, error) {
4745
// gvisor-tap-vsock's UDPProxy demultiplexes client connections internally based on their source address.
4846
// It calls this dialer function only when it receives a datagram from a new, unrecognized client.
4947
// For each new client, we must return a new net.Conn, which in our case is a new gRPC stream.
5048
// The atomic counter ensures that each stream has a unique ID to distinguish them on the server side.
51-
proxy, err := forwarder.NewUDPProxy(conn, func() (net.Conn, error) {
52-
id := fmt.Sprintf("%s-%d", initialID, udpConnectionCounter.Add(1))
53-
stream, err := client.Tunnel(ctx)
49+
var connectionCounter atomic.Uint32
50+
return func(_ context.Context, network, addr string) (net.Conn, error) {
51+
// Passed context.Context is used for timeout on initiate connection, not for the lifetime of the connection.
52+
// We use context.Background() here to avoid unexpected cancellation.
53+
stream, err := client.Tunnel(context.Background())
5454
if err != nil {
55-
return nil, fmt.Errorf("could not open udp tunnel for id: %s error:%w", id, err)
55+
return nil, fmt.Errorf("could not open tunnel for addr: %s error:%w", addr, err)
5656
}
5757
// Handshake message to start tunnel
58-
if err := stream.Send(&api.TunnelMessage{Id: id, Protocol: "udp", GuestAddr: guestAddr}); err != nil {
59-
return nil, fmt.Errorf("could not start udp tunnel for id: %s error:%w", id, err)
58+
id := fmt.Sprintf("%s-%s-%d", network, addr, connectionCounter.Add(1))
59+
if err := stream.Send(&api.TunnelMessage{Id: id, Protocol: network, GuestAddr: addr}); err != nil {
60+
return nil, fmt.Errorf("could not start tunnel for id: %s addr: %s error:%w", id, addr, err)
6061
}
61-
rw := &GrpcClientRW{stream: stream, id: id, addr: guestAddr, protocol: "udp"}
62+
rw := &GrpcClientRW{stream: stream, id: id, addr: addr, protocol: network}
6263
return rw, nil
63-
})
64-
if err != nil {
65-
logrus.Errorf("error in udp tunnel proxy for id: %s error:%v", initialID, err)
66-
return
6764
}
68-
69-
defer func() {
70-
err := proxy.Close()
71-
if err != nil {
72-
logrus.Errorf("error in closing udp tunnel proxy for id: %s error:%v", initialID, err)
73-
}
74-
}()
75-
proxy.Run()
7665
}
7766

7867
type GrpcClientRW struct {

pkg/portfwd/forward.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/sirupsen/logrus"
1212

1313
"github.com/lima-vm/lima/v2/pkg/guestagent/api"
14-
guestagentclient "github.com/lima-vm/lima/v2/pkg/guestagent/api/client"
1514
"github.com/lima-vm/lima/v2/pkg/limatype"
1615
"github.com/lima-vm/lima/v2/pkg/limayaml"
1716
)
@@ -38,7 +37,7 @@ func (fw *Forwarder) Close() error {
3837
return fw.closableListeners.Close()
3938
}
4039

41-
func (fw *Forwarder) OnEvent(ctx context.Context, client *guestagentclient.GuestAgentClient, ev *api.Event) {
40+
func (fw *Forwarder) OnEvent(ctx context.Context, dialContext func(ctx context.Context, network string, addr string) (net.Conn, error), ev *api.Event) {
4241
for _, f := range ev.AddedLocalPorts {
4342
// Before forwarding, check if any static rule matches this port otherwise it will be forwarded twice and cause a port conflict
4443
if fw.isPortStaticallyForwarded(f) {
@@ -55,7 +54,7 @@ func (fw *Forwarder) OnEvent(ctx context.Context, client *guestagentclient.Guest
5554
continue
5655
}
5756
logrus.Infof("Forwarding %s from %s to %s", strings.ToUpper(f.Protocol), remote, local)
58-
fw.closableListeners.Forward(ctx, client, f.Protocol, local, remote)
57+
fw.closableListeners.Forward(ctx, dialContext, f.Protocol, local, remote)
5958
}
6059
for _, f := range ev.RemovedLocalPorts {
6160
local, remote := fw.forwardingAddresses(f)

pkg/portfwd/listener.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ import (
1414
"sync"
1515

1616
"github.com/sirupsen/logrus"
17-
18-
guestagentclient "github.com/lima-vm/lima/v2/pkg/guestagent/api/client"
1917
)
2018

2119
type ClosableListeners struct {
@@ -59,14 +57,14 @@ func (p *ClosableListeners) Close() error {
5957
return errors.Join(errs...)
6058
}
6159

62-
func (p *ClosableListeners) Forward(ctx context.Context, client *guestagentclient.GuestAgentClient,
60+
func (p *ClosableListeners) Forward(ctx context.Context, dialContext func(ctx context.Context, network string, addr string) (net.Conn, error),
6361
protocol string, hostAddress string, guestAddress string,
6462
) {
6563
switch protocol {
6664
case "tcp", "tcp6":
67-
go p.forwardTCP(ctx, client, hostAddress, guestAddress)
65+
go p.forwardTCP(ctx, dialContext, hostAddress, guestAddress)
6866
case "udp", "udp6":
69-
go p.forwardUDP(ctx, client, hostAddress, guestAddress)
67+
go p.forwardUDP(ctx, dialContext, hostAddress, guestAddress)
7068
}
7169
}
7270

@@ -93,7 +91,7 @@ func (p *ClosableListeners) Remove(_ context.Context, protocol, hostAddress, gue
9391
}
9492
}
9593

96-
func (p *ClosableListeners) forwardTCP(ctx context.Context, client *guestagentclient.GuestAgentClient, hostAddress, guestAddress string) {
94+
func (p *ClosableListeners) forwardTCP(ctx context.Context, dialContext func(ctx context.Context, network string, addr string) (net.Conn, error), hostAddress, guestAddress string) {
9795
key := key("tcp", hostAddress, guestAddress)
9896

9997
p.listenersRW.Lock()
@@ -124,11 +122,11 @@ func (p *ClosableListeners) forwardTCP(ctx context.Context, client *guestagentcl
124122
}
125123
return
126124
}
127-
go HandleTCPConnection(ctx, client, conn, guestAddress)
125+
go HandleTCPConnection(ctx, dialContext, conn, guestAddress)
128126
}
129127
}
130128

131-
func (p *ClosableListeners) forwardUDP(ctx context.Context, client *guestagentclient.GuestAgentClient, hostAddress, guestAddress string) {
129+
func (p *ClosableListeners) forwardUDP(ctx context.Context, dialContext func(ctx context.Context, network string, addr string) (net.Conn, error), hostAddress, guestAddress string) {
132130
key := key("udp", hostAddress, guestAddress)
133131
defer p.Remove(ctx, "udp", hostAddress, guestAddress)
134132

@@ -148,7 +146,7 @@ func (p *ClosableListeners) forwardUDP(ctx context.Context, client *guestagentcl
148146
p.udpListeners[key] = udpConn
149147
p.udpListenersRW.Unlock()
150148

151-
HandleUDPConnection(ctx, client, udpConn, guestAddress)
149+
HandleUDPConnection(ctx, dialContext, udpConn, guestAddress)
152150
}
153151

154152
func key(protocol, hostAddress, guestAddress string) string {

pkg/portfwdserver/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"strings"
1313
"time"
1414

15-
"github.com/containers/gvisor-tap-vsock/pkg/tcpproxy"
15+
"github.com/inetaf/tcpproxy"
1616

1717
"github.com/lima-vm/lima/v2/pkg/bicopy"
1818
"github.com/lima-vm/lima/v2/pkg/guestagent/api"

0 commit comments

Comments
 (0)