-
Notifications
You must be signed in to change notification settings - Fork 621
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support port forwarding for privileged ports (1-1023)
On macOS, listening on 127.0.0.1:80 requires root while 0.0.0.0:80 does not require root. For such ports, the hostagent launches "pseudoloopback" forwarder that listens on 0.0.0.0:80 but rejects connections from non-loopback src IP. Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
- Loading branch information
1 parent
bab1029
commit 11c08ca
Showing
7 changed files
with
186 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
package hostagent | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net" | ||
"os" | ||
"path/filepath" | ||
"strconv" | ||
"sync" | ||
|
||
"github.com/lima-vm/sshocker/pkg/ssh" | ||
"github.com/norouter/norouter/pkg/agent/bicopy" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
func forwardTCP(ctx context.Context, sshConfig *ssh.SSHConfig, port int, local, remote string, cancel bool) error { | ||
localIPStr, localPortStr, err := net.SplitHostPort(local) | ||
if err != nil { | ||
return err | ||
} | ||
localIP := net.ParseIP(localIPStr) | ||
localPort, err := strconv.Atoi(localPortStr) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if !net.ParseIP("127.0.0.1").Equal(localIP) || localPort >= 1024 { | ||
return forwardSSH(ctx, sshConfig, port, local, remote, cancel) | ||
} | ||
|
||
// on macOS, listening on 127.0.0.1:80 requires root while 0.0.0.0:80 does not require root. | ||
// https://twitter.com/_AkihiroSuda_/status/1403403845842075648 | ||
// | ||
// We use "pseudoloopback" forwarder that listens on 0.0.0.0:80 but rejects connections from non-loopback src IP. | ||
logrus.Debugf("using pseudoloopback port forwarder for %q", local) | ||
|
||
if cancel { | ||
pseudoLoopbackForwardersMu.Lock() | ||
plf, ok := pseudoLoopbackForwarders[local] | ||
pseudoLoopbackForwardersMu.Unlock() | ||
if ok { | ||
localUnix := plf.unixAddr.Name | ||
_ = plf.Close() | ||
pseudoLoopbackForwardersMu.Lock() | ||
delete(pseudoLoopbackForwarders, local) | ||
pseudoLoopbackForwardersMu.Unlock() | ||
if err := forwardSSH(ctx, sshConfig, port, localUnix, remote, cancel); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
localUnixDir, err := os.MkdirTemp("/tmp", fmt.Sprintf("lima-psl-%s-%d-", localIP, localPort)) | ||
if err != nil { | ||
return err | ||
} | ||
localUnix := filepath.Join(localUnixDir, "sock") | ||
if err := forwardSSH(ctx, sshConfig, port, localUnix, remote, cancel); err != nil { | ||
_ = os.RemoveAll(localUnixDir) | ||
return err | ||
} | ||
plf, err := newPseudoLoopbackForwarder(localPort, localUnix) | ||
if err != nil { | ||
_ = os.RemoveAll(localUnixDir) | ||
return err | ||
} | ||
plf.onClose = func() error { | ||
return os.RemoveAll(localUnixDir) | ||
} | ||
pseudoLoopbackForwardersMu.Lock() | ||
pseudoLoopbackForwarders[local] = plf | ||
pseudoLoopbackForwardersMu.Unlock() | ||
go func() { | ||
if plfErr := plf.Serve(); plfErr != nil { | ||
logrus.WithError(plfErr).Warning("pseudoloopback forwarder crashed") | ||
} | ||
}() | ||
return nil | ||
} | ||
|
||
var ( | ||
pseudoLoopbackForwarders = make(map[string]*pseudoLoopbackForwarder) | ||
pseudoLoopbackForwardersMu sync.Mutex | ||
) | ||
|
||
type pseudoLoopbackForwarder struct { | ||
ln *net.TCPListener | ||
unixAddr *net.UnixAddr | ||
onClose func() error | ||
} | ||
|
||
func newPseudoLoopbackForwarder(localPort int, unixSock string) (*pseudoLoopbackForwarder, error) { | ||
unixAddr, err := net.ResolveUnixAddr("unix", unixSock) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
lnAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("0.0.0.0:%d", localPort)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
ln, err := net.ListenTCP("tcp4", lnAddr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
plf := &pseudoLoopbackForwarder{ | ||
ln: ln, | ||
unixAddr: unixAddr, | ||
} | ||
|
||
return plf, nil | ||
} | ||
|
||
func (plf *pseudoLoopbackForwarder) Serve() error { | ||
defer plf.ln.Close() | ||
for { | ||
ac, err := plf.ln.AcceptTCP() | ||
if err != nil { | ||
return err | ||
} | ||
remoteAddr := ac.RemoteAddr().String() // ip:port | ||
remoteAddrIP, _, err := net.SplitHostPort(remoteAddr) | ||
if err != nil { | ||
logrus.WithError(err).Debugf("pseudoloopback forwarder: rejecting non-loopback remoteAddr %q (unparsable)", remoteAddr) | ||
ac.Close() | ||
continue | ||
} | ||
if remoteAddrIP != "127.0.0.1" { | ||
logrus.WithError(err).Debugf("pseudoloopback forwarder: rejecting non-loopback remoteAddr %q", remoteAddr) | ||
ac.Close() | ||
continue | ||
} | ||
go func(ac *net.TCPConn) { | ||
if fErr := plf.forward(ac); fErr != nil { | ||
logrus.Error(fErr) | ||
} | ||
}(ac) | ||
} | ||
} | ||
|
||
func (plf *pseudoLoopbackForwarder) forward(ac *net.TCPConn) error { | ||
defer ac.Close() | ||
unixConn, err := net.DialUnix("unix", nil, plf.unixAddr) | ||
if err != nil { | ||
return err | ||
} | ||
defer unixConn.Close() | ||
bicopy.Bicopy(ac, unixConn, nil) | ||
return nil | ||
} | ||
|
||
func (plf *pseudoLoopbackForwarder) Close() error { | ||
_ = plf.ln.Close() | ||
return plf.onClose() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
//go:build !darwin | ||
// +build !darwin | ||
|
||
package hostagent | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/lima-vm/sshocker/pkg/ssh" | ||
) | ||
|
||
func forwardTCP(ctx context.Context, sshConfig *ssh.SSHConfig, port int, local, remote string, cancel bool) error { | ||
return forwardSSH(ctx, sshConfig, port, local, remote, cancel) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters