Skip to content

Commit

Permalink
Merge pull request #238 from rod-hynes/master
Browse files Browse the repository at this point in the history
Add TCPPortForwardRedirects + minor changes
  • Loading branch information
rod-hynes committed Aug 19, 2016
2 parents 2f85102 + d43b274 commit e73ad9f
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 12 deletions.
19 changes: 18 additions & 1 deletion psiphon/notice.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package psiphon
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"log"
Expand Down Expand Up @@ -112,7 +113,23 @@ func outputNotice(noticeType string, noticeFlags uint32, args ...interface{}) {
if err == nil {
output = string(encodedJson)
} else {
output = fmt.Sprintf("{\"Alert\":{\"message\":\"%s\"}}", common.ContextError(err))
// Try to emit a properly formatted Alert notice that the outer client can
// report. One scenario where this is useful is if the preceeding Marshal
// fails due to bad data in the args. This has happened for a json.RawMessage
// field.
obj := make(map[string]interface{})
obj["noticeType"] = "Alert"
obj["showUser"] = false
obj["data"] = map[string]interface{}{
"message": fmt.Sprintf("Marshal notice failed: %s", common.ContextError(err)),
}
obj["timestamp"] = time.Now().UTC().Format(time.RFC3339)
encodedJson, err := json.Marshal(obj)
if err == nil {
output = string(encodedJson)
} else {
output = common.ContextError(errors.New("failed to marshal notice")).Error()
}
}
noticeLoggerMutex.Lock()
defer noticeLoggerMutex.Unlock()
Expand Down
54 changes: 46 additions & 8 deletions psiphon/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@ type Config struct {
// directly by this server, which parses the SSH channel using the
// udpgw protocol. Handling includes udpgw transparent DNS: tunneled
// UDP DNS packets are rerouted to the host's DNS server.
//
// The intercept is applied before the port forward destination is
// validated against SSH_DISALLOWED_PORT_FORWARD_HOSTS and
// AllowTCPPorts/DenyTCPPorts. So the intercept address may be any
// otherwise prohibited destination.
UDPInterceptUdpgwServerAddress string

// DNSResolverIPAddress specifies the IP address of a DNS server
Expand All @@ -181,6 +186,21 @@ type Config struct {
// "nameserver" entry.
DNSResolverIPAddress string

// TCPPortForwardRedirects is a mapping from client port forward
// destination to an alternate destination address. When the client's
// port forward HostToConnect and PortToConnect matches a redirect,
// the redirect is substituted and dialed instead of the original
// destination.
//
// The redirect is applied after the original destination is
// validated against SSH_DISALLOWED_PORT_FORWARD_HOSTS and
// AllowTCPPorts/DenyTCPPorts. So the redirect may map to any
// otherwise prohibited destination.
//
// The redirect is applied after UDPInterceptUdpgwServerAddress is
// checked. So the redirect address will not be intercepted.
TCPPortForwardRedirects map[string]string

// LoadMonitorPeriodSeconds indicates how frequently to log server
// load information (number of connected clients per tunnel protocol,
// number of running goroutines, amount of memory allocated, etc.)
Expand Down Expand Up @@ -257,19 +277,26 @@ func LoadConfig(configJSON []byte) (*Config, error) {
}
}

validateNetworkAddress := func(address string) error {
host, port, err := net.SplitHostPort(address)
if err == nil && net.ParseIP(host) == nil {
err = errors.New("Host must be an IP address")
validateNetworkAddress := func(address string, requireIPaddress bool) error {
host, portStr, err := net.SplitHostPort(address)
if err != nil {
return err
}
if requireIPaddress && net.ParseIP(host) == nil {
return errors.New("host must be an IP address")
}
port, err := strconv.Atoi(portStr)
if err != nil {
return err
}
if err == nil {
_, err = strconv.Atoi(port)
if port < 0 || port > 65535 {
return errors.New("invalid port")
}
return err
return nil
}

if config.UDPInterceptUdpgwServerAddress != "" {
if err := validateNetworkAddress(config.UDPInterceptUdpgwServerAddress); err != nil {
if err := validateNetworkAddress(config.UDPInterceptUdpgwServerAddress, true); err != nil {
return nil, fmt.Errorf("UDPInterceptUdpgwServerAddress is invalid: %s", err)
}
}
Expand All @@ -280,6 +307,17 @@ func LoadConfig(configJSON []byte) (*Config, error) {
}
}

if config.TCPPortForwardRedirects != nil {
for destination, redirect := range config.TCPPortForwardRedirects {
if err := validateNetworkAddress(destination, false); err != nil {
return nil, fmt.Errorf("TCPPortForwardRedirects destination %s is invalid: %s", destination, err)
}
if err := validateNetworkAddress(redirect, false); err != nil {
return nil, fmt.Errorf("TCPPortForwardRedirects redirect %s is invalid: %s", redirect, err)
}
}
}

return &config, nil
}

Expand Down
18 changes: 15 additions & 3 deletions psiphon/server/tunnelServer.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"fmt"
"io"
"net"
"strconv"
"sync"
"sync/atomic"
"time"
Expand Down Expand Up @@ -801,9 +802,7 @@ func (sshClient *sshClient) handleNewPortForwardChannel(newChannel ssh.NewChanne
// TODO: also support UDP explicitly, e.g. with a custom "direct-udp" channel type?
isUDPChannel := sshClient.sshServer.support.Config.UDPInterceptUdpgwServerAddress != "" &&
sshClient.sshServer.support.Config.UDPInterceptUdpgwServerAddress ==
fmt.Sprintf("%s:%d",
directTcpipExtraData.HostToConnect,
directTcpipExtraData.PortToConnect)
net.JoinHostPort(directTcpipExtraData.HostToConnect, strconv.Itoa(int(directTcpipExtraData.PortToConnect)))

if isUDPChannel {
sshClient.handleUDPChannel(newChannel)
Expand Down Expand Up @@ -891,6 +890,19 @@ func (sshClient *sshClient) handleTCPChannel(
return
}

// Note: redirects are applied *after* isPortForwardPermitted allows the original destination
if sshClient.sshServer.support.Config.TCPPortForwardRedirects != nil {
destination := net.JoinHostPort(hostToConnect, strconv.Itoa(portToConnect))
if redirect, ok := sshClient.sshServer.support.Config.TCPPortForwardRedirects[destination]; ok {
// Note: redirect format is validated when config is loaded
host, portStr, _ := net.SplitHostPort(redirect)
port, _ := strconv.Atoi(portStr)
hostToConnect = host
portToConnect = port
log.WithContextFields(LogFields{"destination": destination, "redirect": redirect}).Debug("port forward redirect")
}
}

var bytesUp, bytesDown int64
sshClient.openedPortForward(sshClient.tcpTrafficState)
defer func() {
Expand Down
3 changes: 3 additions & 0 deletions psiphon/server/webServer.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ func RunWebServer(
ReadTimeout: WEB_SERVER_IO_TIMEOUT,
WriteTimeout: WEB_SERVER_IO_TIMEOUT,
ErrorLog: golanglog.New(logWriter, "", 0),

// Disable auto HTTP/2 (https://golang.org/doc/go1.6)
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
},
}

Expand Down

0 comments on commit e73ad9f

Please sign in to comment.