Skip to content

Commit

Permalink
wip: Adjust portForwarder
Browse files Browse the repository at this point in the history
  • Loading branch information
heyvito committed May 18, 2023
1 parent cdc22e9 commit e43b784
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 27 deletions.
8 changes: 4 additions & 4 deletions hack/test-port-forwarding.pl
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,8 @@ sub JoinHostPort {
# forward: 127.0.0.2 3020 → 127.0.0.1 2020
# forward: 127.0.0.1 3021 → 127.0.0.1 2021
# forward: 0.0.0.0 3022 → 127.0.0.1 2022
# forward: :: 3023 → 127.0.0.1 2023
# forward: ::1 3024 → 127.0.0.1 2024
# forward: :: 3023 → ::1 2023
# forward: ::1 3024 → ::1 2024
- guestPortRange: [3030, 3039]
hostPortRange: [2030, 2039]
Expand Down Expand Up @@ -309,7 +309,7 @@ sub JoinHostPort {
ignore: true
# forward: 0.0.0.0 4040 → 127.0.0.1 4040
# forward: :: 4041 → 127.0.0.1 4041
# forward: :: 4041 → ::1 4041
# ignore: 127.0.0.1 4043 → 127.0.0.1 4043
# ignore: 192.168.5.15 4044 → 127.0.0.1 4044
Expand All @@ -318,4 +318,4 @@ sub JoinHostPort {
# The actual test code is in test-example.sh in the "port-forwarding" block.
- guestIPMustBeZero: true
guestPort: 8888
hostIP: 0.0.0.0
hostIP: 0.0.0.0
2 changes: 1 addition & 1 deletion pkg/hostagent/hostagent.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func New(instName string, stdout io.Writer, sigintCh chan os.Signal, opts ...Opt
AdditionalArgs: sshutil.SSHArgsFromOpts(sshOpts),
}

rules := make([]limayaml.PortForward, 0, 3+len(y.PortForwards))
rules := make([]limayaml.PortForward, 0, 4+len(y.PortForwards))
// Block ports 22 and sshLocalPort on all IPs
for _, port := range []int{sshGuestPort, sshLocalPort} {
rule := limayaml.PortForward{GuestIP: net.IPv4zero, GuestPort: port, Ignore: true}
Expand Down
85 changes: 63 additions & 22 deletions pkg/hostagent/port.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,38 +40,62 @@ func hostAddress(rule limayaml.PortForward, guest api.IPPort) string {
return host.String()
}

func tcpRuleIsWithinBounds(guest api.IPPort, rule limayaml.PortForward) bool {
if rule.GuestSocket != "" {
// Not TCP
return false
}
func (pf *portForwarder) forwardingAddresses(guest api.IPPort) (hostAddr string, guestAddr string) {
// Some rules will require a small patch to the HostIP in order to bind to the
// correct IP family.
mustAdjustHostIP := false

// Check if `guest.Port` is within `rule.GuestPortRange`
return guest.Port >= rule.GuestPortRange[0] && guest.Port <= rule.GuestPortRange[1]
}
// This holds an optional rule that was rejected, but is now stored here to preserve backward
// compatibility, and will be used at the bottom of this function if set. See the case
// rule.GuestIPMustBeZero && guest.IP.IsUnspecified() for further info.
var unspecifiedRuleFallback *limayaml.PortForward

func (pf *portForwarder) forwardingAddresses(guest api.IPPort) (hostAddr string, guestAddr string) {
// Check if it matches new IPv6 rule, otherwise fallback to the pre-IPv6 checks to maintain
// compatibility
for _, rule := range pf.rules {
if !tcpRuleIsWithinBounds(guest, rule) {
if rule.GuestSocket != "" {
// Not TCP
continue
}

// guest.IP and HostIP must be either :: or 0.0.0.0, and be forwarded to the same family on HostIP
if guest.IP.IsUnspecified() && rule.HostIP.IsUnspecified() && ((guest.IP.To4() == nil) == (rule.HostIP.To4() == nil)) {
return hostAddress(rule, guest), guest.String()
}
}

// Before giving up, try to match against the old rule set
for _, rule := range pf.rules {
if !tcpRuleIsWithinBounds(guest, rule) {
// Check if `guest.Port` is within `rule.GuestPortRange`
if guest.Port < rule.GuestPortRange[0] || guest.Port > rule.GuestPortRange[1] {
continue
}

switch {
case guest.IP.IsUnspecified():
// Early-continue in case rule's IP is not zero while it is required.
case rule.GuestIPMustBeZero && !guest.IP.IsUnspecified():
continue

// Rule lacks a preferred GuestIP, so guest may be binding to wherever it wants. The rule matches the port range,
// so we can continue processing it. However, make sure to correct the rule to use a correct address family if
// not specified by the rule.
case rule.GuestIPWasUndefined && !rule.GuestIPMustBeZero:
mustAdjustHostIP = rule.HostIPWasUndefined

// if GuestIP and family matches, move along.
case rule.GuestIPMustBeZero && guest.IP.IsUnspecified():
// This is a breaking change. Here we will keep a backup of the rule, so we can still reuse it
// in case everything fails. The idea here is to move a copy of the current rule to outside this
// loop, so we can reuse it in case nothing else matches.
if !rule.GuestIPWasUndefined && !guest.IP.Equal(rule.GuestIP) {
if unspecifiedRuleFallback == nil {
// Move the rule to obtain a copy
func(p limayaml.PortForward) { unspecifiedRuleFallback = &p }(rule)
}
continue
}

mustAdjustHostIP = true

// Rule lack's HostIP, and guest is binding to '0.0.0.0' or '::'. Bind to the same address family.
case rule.HostIPWasUndefined && guest.IP.IsUnspecified():
mustAdjustHostIP = true

// We don't have a preferred HostIP in the rule, and guest wants to bind to a loopback
// address. In that case, use the same address family.
case rule.HostIPWasUndefined && (guest.IP.Equal(net.IPv6loopback) || guest.IP.Equal(api.IPv4loopback1)):
mustAdjustHostIP = true

case guest.IP.Equal(rule.GuestIP):
case guest.IP.Equal(net.IPv6loopback) && rule.GuestIP.Equal(api.IPv4loopback1):
case rule.GuestIP.IsUnspecified() && !rule.GuestIPMustBeZero:
Expand All @@ -80,15 +104,32 @@ func (pf *portForwarder) forwardingAddresses(guest api.IPPort) (hostAddr string,
default:
continue
}

if rule.Ignore {
if guest.IP.IsUnspecified() && !rule.GuestIP.IsUnspecified() {
continue
}

break
}

if mustAdjustHostIP {
if guest.IP.To4() != nil {
rule.HostIP = api.IPv4loopback1
} else {
rule.HostIP = net.IPv6loopback
}
}

return hostAddress(rule, guest), guest.String()
}

// At this point, no other rule matched. So check if this is being impacted by our
// breaking change, and return the fallback rule. Otherwise, just ignore it.
if unspecifiedRuleFallback != nil {
return hostAddress(*unspecifiedRuleFallback, guest), guest.String()
}

return "", guest.String()
}

Expand Down
4 changes: 4 additions & 0 deletions pkg/limayaml/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -646,10 +646,14 @@ func FillPortForwardDefaults(rule *PortForward, instDir string) {
} else {
rule.GuestIP = api.IPv4loopback1
}
rule.GuestIPWasUndefined = true
}

if rule.HostIP == nil {
rule.HostIP = api.IPv4loopback1
rule.HostIPWasUndefined = true
}

if rule.GuestPortRange[0] == 0 && rule.GuestPortRange[1] == 0 {
if rule.GuestPort == 0 {
rule.GuestPortRange[0] = 1
Expand Down
6 changes: 6 additions & 0 deletions pkg/limayaml/limayaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,12 @@ type PortForward struct {
Proto Proto `yaml:"proto,omitempty" json:"proto,omitempty"`
Reverse bool `yaml:"reverse,omitempty" json:"reverse,omitempty"`
Ignore bool `yaml:"ignore,omitempty" json:"ignore,omitempty"`

// Set in case the HostIP field was automatically filled by FillPortForwardDefaults
HostIPWasUndefined bool `yaml:"-" json:"-"`

// Set in case the GuestIP field was automatically filled by FillPortForwardDefaults
GuestIPWasUndefined bool `yaml:"-" json:"-"`
}

type CopyToHost struct {
Expand Down

0 comments on commit e43b784

Please sign in to comment.