forked from gliderlabs/ssh
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for local port forwarding (gliderlabs#38)
* Support local port forwarding * refactor testSession to return ssh client as well * Tests for local port forwarding
- Loading branch information
Showing
7 changed files
with
166 additions
and
20 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
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,58 @@ | ||
package ssh | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"net" | ||
|
||
gossh "golang.org/x/crypto/ssh" | ||
) | ||
|
||
// direct-tcpip data struct as specified in RFC4254, Section 7.2 | ||
type forwardData struct { | ||
DestinationHost string | ||
DestinationPort uint32 | ||
|
||
OriginatorHost string | ||
OriginatorPort uint32 | ||
} | ||
|
||
func directTcpipHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx *sshContext) { | ||
d := forwardData{} | ||
if err := gossh.Unmarshal(newChan.ExtraData(), &d); err != nil { | ||
newChan.Reject(gossh.ConnectionFailed, "error parsing forward data: "+err.Error()) | ||
return | ||
} | ||
|
||
if srv.LocalPortForwardingCallback == nil || !srv.LocalPortForwardingCallback(ctx, d.DestinationHost, d.DestinationPort) { | ||
newChan.Reject(gossh.Prohibited, "port forwarding is disabled") | ||
return | ||
} | ||
|
||
dest := fmt.Sprintf("%s:%d", d.DestinationHost, d.DestinationPort) | ||
|
||
var dialer net.Dialer | ||
dconn, err := dialer.DialContext(ctx, "tcp", dest) | ||
if err != nil { | ||
newChan.Reject(gossh.ConnectionFailed, err.Error()) | ||
return | ||
} | ||
|
||
ch, reqs, err := newChan.Accept() | ||
if err != nil { | ||
dconn.Close() | ||
return | ||
} | ||
go gossh.DiscardRequests(reqs) | ||
|
||
go func() { | ||
defer ch.Close() | ||
defer dconn.Close() | ||
io.Copy(ch, dconn) | ||
}() | ||
go func() { | ||
defer ch.Close() | ||
defer dconn.Close() | ||
io.Copy(dconn, ch) | ||
}() | ||
} |
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,83 @@ | ||
package ssh | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io/ioutil" | ||
"net" | ||
"strings" | ||
"testing" | ||
|
||
gossh "golang.org/x/crypto/ssh" | ||
) | ||
|
||
var sampleServerResponse = []byte("Hello world") | ||
|
||
func sampleSocketServer() net.Listener { | ||
l := newLocalListener() | ||
|
||
go func() { | ||
conn, err := l.Accept() | ||
if err != nil { | ||
return | ||
} | ||
conn.Write(sampleServerResponse) | ||
conn.Close() | ||
}() | ||
|
||
return l | ||
} | ||
|
||
func newTestSessionWithForwarding(t *testing.T, forwardingEnabled bool) (net.Listener, *gossh.Client, func()) { | ||
l := sampleSocketServer() | ||
|
||
_, client, cleanup := newTestSession(t, &Server{ | ||
Handler: func(s Session) {}, | ||
LocalPortForwardingCallback: func(ctx Context, destinationHost string, destinationPort uint32) bool { | ||
addr := fmt.Sprintf("%s:%d", destinationHost, destinationPort) | ||
if addr != l.Addr().String() { | ||
panic("unexpected destinationHost: " + addr) | ||
} | ||
return forwardingEnabled | ||
}, | ||
}, nil) | ||
|
||
return l, client, func() { | ||
cleanup() | ||
l.Close() | ||
} | ||
} | ||
|
||
func TestLocalPortForwardingWorks(t *testing.T) { | ||
t.Parallel() | ||
|
||
l, client, cleanup := newTestSessionWithForwarding(t, true) | ||
defer cleanup() | ||
|
||
conn, err := client.Dial("tcp", l.Addr().String()) | ||
if err != nil { | ||
t.Fatalf("Error connecting to %v: %v", l.Addr().String(), err) | ||
} | ||
result, err := ioutil.ReadAll(conn) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !bytes.Equal(result, sampleServerResponse) { | ||
t.Fatalf("result = %#v; want %#v", result, sampleServerResponse) | ||
} | ||
} | ||
|
||
func TestLocalPortForwardingRespectsCallback(t *testing.T) { | ||
t.Parallel() | ||
|
||
l, client, cleanup := newTestSessionWithForwarding(t, false) | ||
defer cleanup() | ||
|
||
_, err := client.Dial("tcp", l.Addr().String()) | ||
if err == nil { | ||
t.Fatalf("Expected error connecting to %v but it succeeded", l.Addr().String()) | ||
} | ||
if !strings.Contains(err.Error(), "port forwarding is disabled") { | ||
t.Fatalf("Expected permission error but got %#v", err) | ||
} | ||
} |