Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/pkg/portfwd/forward.go
2601 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package portfwd
5
6
import (
7
"context"
8
"net"
9
"strings"
10
11
"github.com/sirupsen/logrus"
12
13
"github.com/lima-vm/lima/v2/pkg/guestagent/api"
14
"github.com/lima-vm/lima/v2/pkg/hostagent/events"
15
"github.com/lima-vm/lima/v2/pkg/limatype"
16
"github.com/lima-vm/lima/v2/pkg/limayaml"
17
)
18
19
var IPv4loopback1 = limayaml.IPv4loopback1
20
21
type Forwarder struct {
22
rules []limatype.PortForward
23
ignoreTCP bool
24
ignoreUDP bool
25
closableListeners *ClosableListeners
26
onEvent func(*events.PortForwardEvent)
27
}
28
29
func NewPortForwarder(rules []limatype.PortForward, ignoreTCP, ignoreUDP bool, onEvent func(*events.PortForwardEvent)) *Forwarder {
30
return &Forwarder{
31
rules: rules,
32
ignoreTCP: ignoreTCP,
33
ignoreUDP: ignoreUDP,
34
closableListeners: NewClosableListener(),
35
onEvent: onEvent,
36
}
37
}
38
39
func (fw *Forwarder) emitEvent(ev *events.PortForwardEvent) {
40
if fw.onEvent != nil {
41
fw.onEvent(ev)
42
}
43
}
44
45
func (fw *Forwarder) Close() error {
46
return fw.closableListeners.Close()
47
}
48
49
func (fw *Forwarder) OnEvent(ctx context.Context, dialContext func(ctx context.Context, network string, addr string) (net.Conn, error), ev *api.Event) {
50
for _, f := range ev.AddedLocalPorts {
51
// Before forwarding, check if any static rule matches this port otherwise it will be forwarded twice and cause a port conflict
52
if fw.isPortStaticallyForwarded(f) {
53
continue
54
}
55
local, remote := fw.forwardingAddresses(f)
56
if local == "" {
57
if !fw.ignoreTCP && f.Protocol == "tcp" {
58
logrus.Infof("Not forwarding TCP %s", remote)
59
fw.emitEvent(&events.PortForwardEvent{
60
Type: events.PortForwardEventNotForwarding,
61
Protocol: f.Protocol,
62
GuestAddr: remote,
63
})
64
}
65
if !fw.ignoreUDP && f.Protocol == "udp" {
66
logrus.Infof("Not forwarding UDP %s", remote)
67
fw.emitEvent(&events.PortForwardEvent{
68
Type: events.PortForwardEventNotForwarding,
69
Protocol: f.Protocol,
70
GuestAddr: remote,
71
})
72
}
73
continue
74
}
75
logrus.Infof("Forwarding %s from %s to %s", strings.ToUpper(f.Protocol), remote, local)
76
fw.emitEvent(&events.PortForwardEvent{
77
Type: events.PortForwardEventForwarding,
78
Protocol: f.Protocol,
79
GuestAddr: remote,
80
HostAddr: local,
81
})
82
fw.closableListeners.Forward(ctx, dialContext, f.Protocol, local, remote)
83
}
84
for _, f := range ev.RemovedLocalPorts {
85
local, remote := fw.forwardingAddresses(f)
86
if local == "" {
87
continue
88
}
89
fw.emitEvent(&events.PortForwardEvent{
90
Type: events.PortForwardEventStopping,
91
Protocol: f.Protocol,
92
GuestAddr: remote,
93
HostAddr: local,
94
})
95
fw.closableListeners.Remove(ctx, f.Protocol, local, remote)
96
logrus.Debugf("Port forwarding closed proto:%s host:%s guest:%s", f.Protocol, local, remote)
97
}
98
}
99
100
func (fw *Forwarder) forwardingAddresses(guest *api.IPPort) (hostAddr, guestAddr string) {
101
guestIP := net.ParseIP(guest.Ip)
102
for _, rule := range fw.rules {
103
if rule.GuestSocket != "" {
104
continue
105
}
106
if rule.Proto != limatype.ProtoAny && rule.Proto != guest.Protocol {
107
continue
108
}
109
if guest.Port < int32(rule.GuestPortRange[0]) || guest.Port > int32(rule.GuestPortRange[1]) {
110
continue
111
}
112
switch {
113
case guestIP.IsUnspecified():
114
case guestIP.Equal(rule.GuestIP):
115
case guestIP.Equal(net.IPv6loopback) && rule.GuestIP.Equal(IPv4loopback1):
116
case rule.GuestIP.IsUnspecified() && !*rule.GuestIPMustBeZero:
117
// When GuestIPMustBeZero is true, then 0.0.0.0 must be an exact match, which is already
118
// handled above by the guestIP.IsUnspecified() condition.
119
default:
120
continue
121
}
122
if rule.Ignore {
123
if guestIP.IsUnspecified() && !rule.GuestIP.IsUnspecified() {
124
continue
125
}
126
break
127
}
128
return hostAddress(rule, guest), guest.HostString()
129
}
130
return "", guest.HostString()
131
}
132
133
func (fw *Forwarder) isPortStaticallyForwarded(guest *api.IPPort) bool {
134
for _, rule := range fw.rules {
135
if !rule.Static {
136
continue
137
}
138
if guest.Port >= int32(rule.GuestPortRange[0]) && guest.Port <= int32(rule.GuestPortRange[1]) {
139
return true
140
}
141
}
142
return false
143
}
144
145
func hostAddress(rule limatype.PortForward, guest *api.IPPort) string {
146
if rule.HostSocket != "" {
147
return rule.HostSocket
148
}
149
host := &api.IPPort{Ip: rule.HostIP.String()}
150
if guest.Port == 0 {
151
// guest is a socket
152
host.Port = int32(rule.HostPort)
153
} else {
154
host.Port = guest.Port + int32(rule.HostPortRange[0]-rule.GuestPortRange[0])
155
}
156
return host.HostString()
157
}
158
159