Explorar o código

* Remove privileged port listeners from automated tests.
* Revert back to accepting ports on Server generate command
line, now supporting all tunnel protocols.

Rod Hynes %!s(int64=9) %!d(string=hai) anos
pai
achega
28b9de7239
Modificáronse 4 ficheiros con 127 adicións e 55 borrados
  1. 1 1
      .travis.yml
  2. 37 7
      Server/main.go
  3. 64 39
      psiphon/server/config.go
  4. 25 8
      psiphon/server/server_test.go

+ 1 - 1
.travis.yml

@@ -8,7 +8,7 @@ addons:
 install:
 - go get -t -d -v ./... && go build -v ./...
 script:
-- sudo -E go test -v ./...
+- go test -v ./...
 - cd psiphon
 - go test -v -covermode=count -coverprofile=coverage.out
 - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN

+ 37 - 7
Server/main.go

@@ -24,6 +24,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
+	"strconv"
 	"strings"
 
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
@@ -34,6 +35,8 @@ func main() {
 
 	var generateServerIPaddress, generateServerNetworkInterface string
 	var generateConfigFilename, generateServerEntryFilename string
+	var generateWebServerPort int
+	var generateProtocolPorts stringListFlag
 	var runConfigFilenames stringListFlag
 
 	flag.StringVar(
@@ -60,6 +63,17 @@ func main() {
 		server.DEFAULT_SERVER_IP_ADDRESS,
 		"generate with this server `IP address`")
 
+	flag.IntVar(
+		&generateWebServerPort,
+		"web",
+		0,
+		"generate with web server `port`; 0 for no web server")
+
+	flag.Var(
+		&generateProtocolPorts,
+		"protocol",
+		"generate with `protocol:port`; flag may be repeated to enable multiple protocols")
+
 	flag.Var(
 		&runConfigFilenames,
 		"config",
@@ -88,26 +102,42 @@ func main() {
 		if generateServerNetworkInterface != "" {
 			var err error
 			serverIPaddress, err = psiphon.GetInterfaceIPAddress(generateServerNetworkInterface)
-			fmt.Errorf("generate failed: %s", err)
+			fmt.Printf("generate failed: %s\n", err)
 			os.Exit(1)
 		}
 
+		tunnelProtocolPorts := make(map[string]int)
+		for _, protocolPort := range generateProtocolPorts {
+			parts := strings.Split(protocolPort, ":")
+			if len(parts) == 2 {
+				port, err := strconv.Atoi(parts[1])
+				if err != nil {
+					fmt.Printf("generate failed: %s\n", err)
+					os.Exit(1)
+				}
+				tunnelProtocolPorts[parts[0]] = port
+			}
+		}
+
 		configFileContents, serverEntryFileContents, err :=
-			server.GenerateConfig(serverIPaddress)
+			server.GenerateConfig(
+				serverIPaddress,
+				generateWebServerPort,
+				tunnelProtocolPorts)
 		if err != nil {
-			fmt.Errorf("generate failed: %s", err)
+			fmt.Printf("generate failed: %s\n", err)
 			os.Exit(1)
 		}
 
 		err = ioutil.WriteFile(generateConfigFilename, configFileContents, 0600)
 		if err != nil {
-			fmt.Errorf("error writing configuration file: %s", err)
+			fmt.Printf("error writing configuration file: %s\n", err)
 			os.Exit(1)
 		}
 
 		err = ioutil.WriteFile(generateServerEntryFilename, serverEntryFileContents, 0600)
 		if err != nil {
-			fmt.Errorf("error writing server entry file: %s", err)
+			fmt.Printf("error writing server entry file: %s\n", err)
 			os.Exit(1)
 		}
 
@@ -122,7 +152,7 @@ func main() {
 		for _, configFilename := range runConfigFilenames {
 			contents, err := ioutil.ReadFile(configFilename)
 			if err != nil {
-				fmt.Errorf("error loading configuration file: %s", err)
+				fmt.Printf("error loading configuration file: %s\n", err)
 				os.Exit(1)
 			}
 
@@ -131,7 +161,7 @@ func main() {
 
 		err := server.RunServices(configFileContents)
 		if err != nil {
-			fmt.Errorf("run failed: %s", err)
+			fmt.Printf("run failed: %s\n", err)
 			os.Exit(1)
 		}
 	}

+ 64 - 39
psiphon/server/config.go

@@ -443,15 +443,40 @@ func LoadConfig(configJSONs [][]byte) (*Config, error) {
 // encoded config and a client-compatible "server entry" for the server. It
 // generates all necessary secrets and key material, which are emitted in
 // the config file and server entry as necessary.
-// GenerateConfig creates a maximal config with many tunnel protocols enabled.
-// It uses sample values for many fields. The intention is for a generated
-// config to be used for testing or as a template for production setup, not
-// to generate production-ready configurations.
-func GenerateConfig(serverIPaddress string) ([]byte, []byte, error) {
+// GenerateConfig uses sample values for many fields. The intention is for
+// a generated config to be used for testing or as a template for production
+// setup, not to generate production-ready configurations.
+func GenerateConfig(
+	serverIPaddress string,
+	webServerPort int,
+	tunnelProtocolPorts map[string]int) ([]byte, []byte, error) {
+
+	// Input validation
+
+	if net.ParseIP(serverIPaddress) == nil {
+		return nil, nil, psiphon.ContextError(errors.New("invalid IP address"))
+	}
 
-	// Web server config
+	if len(tunnelProtocolPorts) == 0 {
+		return nil, nil, psiphon.ContextError(errors.New("no tunnel protocols"))
+	}
+
+	usedPort := make(map[int]bool)
+	if webServerPort != 0 {
+		usedPort[webServerPort] = true
+	}
+
+	for protocol, port := range tunnelProtocolPorts {
+		if !psiphon.Contains(psiphon.SupportedTunnelProtocols, protocol) {
+			return nil, nil, psiphon.ContextError(errors.New("invalid tunnel protocol"))
+		}
+		if usedPort[port] {
+			return nil, nil, psiphon.ContextError(errors.New("duplicate listening port"))
+		}
+		usedPort[port] = true
+	}
 
-	webServerPort := 8088
+	// Web server config
 
 	webServerSecret, err := psiphon.MakeRandomStringHex(WEB_SERVER_SECRET_BYTE_LENGTH)
 	if err != nil {
@@ -530,34 +555,24 @@ func GenerateConfig(serverIPaddress string) ([]byte, []byte, error) {
 	// Note: this config is intended for either testing or as an illustrative
 	// example or template and is not intended for production deployment.
 
-	sshPort := 22
-	obfuscatedSSHPort := 53
-	meekPort := 8188
-
 	config := &Config{
-		LogLevel:              "info",
-		SyslogFacility:        "user",
-		SyslogTag:             "psiphon-server",
-		Fail2BanFormat:        "Authentication failure for psiphon-client from %s",
-		GeoIPDatabaseFilename: "",
-		ServerIPAddress:       serverIPaddress,
-		DiscoveryValueHMACKey: discoveryValueHMACKey,
-		WebServerPort:         webServerPort,
-		WebServerSecret:       webServerSecret,
-		WebServerCertificate:  webServerCertificate,
-		WebServerPrivateKey:   webServerPrivateKey,
-		SSHPrivateKey:         string(sshPrivateKey),
-		SSHServerVersion:      sshServerVersion,
-		SSHUserName:           sshUserName,
-		SSHPassword:           sshPassword,
-		ObfuscatedSSHKey:      obfuscatedSSHKey,
-		TunnelProtocolPorts: map[string]int{
-			"SSH":                    sshPort,
-			"OSSH":                   obfuscatedSSHPort,
-			"FRONTED-MEEK-OSSH":      443,
-			"UNFRONTED-MEEK-OSSH":    meekPort,
-			"FRONTED-MEEK-HTTP-OSSH": 80,
-		},
+		LogLevel:                       "info",
+		SyslogFacility:                 "user",
+		SyslogTag:                      "psiphon-server",
+		Fail2BanFormat:                 "Authentication failure for psiphon-client from %s",
+		GeoIPDatabaseFilename:          "",
+		ServerIPAddress:                serverIPaddress,
+		DiscoveryValueHMACKey:          discoveryValueHMACKey,
+		WebServerPort:                  webServerPort,
+		WebServerSecret:                webServerSecret,
+		WebServerCertificate:           webServerCertificate,
+		WebServerPrivateKey:            webServerPrivateKey,
+		SSHPrivateKey:                  string(sshPrivateKey),
+		SSHServerVersion:               sshServerVersion,
+		SSHUserName:                    sshUserName,
+		SSHPassword:                    sshPassword,
+		ObfuscatedSSHKey:               obfuscatedSSHKey,
+		TunnelProtocolPorts:            tunnelProtocolPorts,
 		RedisServerAddress:             "",
 		UDPForwardDNSServerAddress:     "8.8.8.8:53",
 		UDPInterceptUdpgwServerAddress: "127.0.0.1:7300",
@@ -594,11 +609,21 @@ func GenerateConfig(serverIPaddress string) ([]byte, []byte, error) {
 	lines := strings.Split(webServerCertificate, "\n")
 	strippedWebServerCertificate := strings.Join(lines[1:len(lines)-2], "")
 
-	capabilities := []string{
-		psiphon.GetCapability(psiphon.TUNNEL_PROTOCOL_SSH),
-		psiphon.GetCapability(psiphon.TUNNEL_PROTOCOL_OBFUSCATED_SSH),
-		psiphon.GetCapability(psiphon.TUNNEL_PROTOCOL_FRONTED_MEEK),
-		psiphon.GetCapability(psiphon.TUNNEL_PROTOCOL_UNFRONTED_MEEK),
+	capabilities := make([]string, 0)
+
+	for protocol, _ := range tunnelProtocolPorts {
+		capabilities = append(capabilities, psiphon.GetCapability(protocol))
+	}
+
+	sshPort := tunnelProtocolPorts["SSH"]
+	obfuscatedSSHPort := tunnelProtocolPorts["OSSH"]
+
+	// Meek port limitations
+	// - fronted meek protocols are hard-wired in the client to be port 443 or 80.
+	// - only one other meek port may be specified.
+	meekPort := tunnelProtocolPorts["UNFRONTED-MEEK-OSSH"]
+	if meekPort == 0 {
+		meekPort = tunnelProtocolPorts["UNFRONTED-MEEK-HTTPS-OSSH"]
 	}
 
 	// Note: fronting params are a stub; this server entry will exercise

+ 25 - 8
psiphon/server/server_test.go

@@ -41,6 +41,9 @@ func TestMain(m *testing.M) {
 	os.Exit(m.Run())
 }
 
+// Note: not testing fronting meek protocols, which client is
+// hard-wired to except running on privileged ports 80 and 443.
+
 func TestSSH(t *testing.T) {
 	runServer(t, "SSH")
 }
@@ -49,16 +52,12 @@ func TestOSSH(t *testing.T) {
 	runServer(t, "OSSH")
 }
 
-func TestFrontedMeek(t *testing.T) {
-	runServer(t, "FRONTED-MEEK-OSSH")
-}
-
 func TestUnfrontedMeek(t *testing.T) {
 	runServer(t, "UNFRONTED-MEEK-OSSH")
 }
 
-func TestFrontedMeekHTTP(t *testing.T) {
-	runServer(t, "FRONTED-MEEK-HTTP-OSSH")
+func TestUnfrontedMeekHTTPS(t *testing.T) {
+	runServer(t, "UNFRONTED-MEEK-HTTPS-OSSH")
 }
 
 func runServer(t *testing.T, tunnelProtocol string) {
@@ -66,7 +65,9 @@ func runServer(t *testing.T, tunnelProtocol string) {
 	// create a server
 
 	serverConfigFileContents, serverEntryFileContents, err := GenerateConfig(
-		"127.0.0.1")
+		"127.0.0.1",
+		8000,
+		map[string]int{tunnelProtocol: 4000})
 	if err != nil {
 		t.Fatalf("error generating server config: %s", err)
 	}
@@ -177,7 +178,23 @@ func runServer(t *testing.T, tunnelProtocol string) {
 		defer controllerWaitGroup.Done()
 		controller.Run(controllerShutdownBroadcast)
 	}()
-	defer close(controllerShutdownBroadcast)
+	defer func() {
+		close(controllerShutdownBroadcast)
+
+		shutdownTimeout := time.NewTimer(20 * time.Second)
+
+		shutdownOk := make(chan struct{}, 1)
+		go func() {
+			controllerWaitGroup.Wait()
+			shutdownOk <- *new(struct{})
+		}()
+
+		select {
+		case <-shutdownOk:
+		case <-shutdownTimeout.C:
+			t.Fatalf("controller shutdown timeout exceeded")
+		}
+	}()
 
 	// Test: tunnels must be established within 30 seconds