Rod Hynes 10 жил өмнө
parent
commit
2fb4ef29e1

+ 107 - 22
psiphon/server/config.go

@@ -38,7 +38,8 @@ import (
 const (
 const (
 	SERVER_CONFIG_FILENAME                 = "psiphon-server.config"
 	SERVER_CONFIG_FILENAME                 = "psiphon-server.config"
 	SERVER_ENTRY_FILENAME                  = "serverEntry.dat"
 	SERVER_ENTRY_FILENAME                  = "serverEntry.dat"
-	DEFAULT_LOG_LEVEL                      = "Info"
+	DEFAULT_LOG_LEVEL                      = "info"
+	DEFAULT_SYSLOG_TAG                     = "psiphon-server"
 	DEFAULT_GEO_IP_DATABASE_FILENAME       = "GeoLite2-City.mmdb"
 	DEFAULT_GEO_IP_DATABASE_FILENAME       = "GeoLite2-City.mmdb"
 	DEFAULT_SERVER_IP_ADDRESS              = "127.0.0.1"
 	DEFAULT_SERVER_IP_ADDRESS              = "127.0.0.1"
 	WEB_SERVER_SECRET_BYTE_LENGTH          = 32
 	WEB_SERVER_SECRET_BYTE_LENGTH          = 32
@@ -62,44 +63,111 @@ const (
 
 
 // TODO: break config into sections (sub-structs)
 // TODO: break config into sections (sub-structs)
 
 
+// Config specifies the configuration and behavior of a Psiphon
+// server.
 type Config struct {
 type Config struct {
-	LogLevel                string
-	SyslogAddress           string
-	SyslogFacility          string
-	SyslogTag               string
-	DiscoveryValueHMACKey   string
-	GeoIPDatabaseFilename   string
-	ServerIPAddress         string
-	WebServerPort           int
-	WebServerSecret         string
-	WebServerCertificate    string
-	WebServerPrivateKey     string
-	SSHPrivateKey           string
-	SSHServerVersion        string
-	SSHUserName             string
-	SSHPassword             string
-	SSHServerPort           int
-	ObfuscatedSSHKey        string
+
+	// LogLevel specifies the log level. Valid values are:
+	// panic, fatal, error, warn, info, debug
+	LogLevel string
+
+	// SyslogAddress specifies the UDP address of a syslog
+	// service. When set, syslog is used for message logging.
+	SyslogAddress string
+
+	// SyslogFacility specifies the syslog facility to log to.
+	// Valid values include: "user", "local0", "local1", etc.
+	SyslogFacility string
+
+	// SyslogTag specifies an optional tag for syslog log
+	// messages. The default tag is "psiphon-server".
+	SyslogTag string
+
+	// DiscoveryValueHMACKey is the network-wide secret value
+	// used to determine a unique discovery strategy.
+	DiscoveryValueHMACKey string
+
+	// GeoIPDatabaseFilename is the path of the GeoIP2/GeoLite2
+	// MaxMind database file.
+	GeoIPDatabaseFilename string
+
+	// ServerIPAddress is the public IP address of the server.
+	ServerIPAddress string
+
+	// WebServerPort is the listening port of the web server.
+	// When <= 0, no web server component is run.
+	WebServerPort int
+
+	// WebServerSecret is the unique secret value that the client
+	// must supply to make requests to the web server.
+	WebServerSecret string
+
+	// WebServerCertificate is the certificate the client uses to
+	// authenticate the web server.
+	WebServerCertificate string
+
+	// WebServerPrivateKey is the private key the web server uses to
+	// authenticate itself to clients.
+	WebServerPrivateKey string
+
+	// SSHPrivateKey is the SSH host key. The same key is used for
+	// both the SSH and Obfuscated SSH servers.
+	SSHPrivateKey string
+
+	// SSHServerVersion is the server version presented in the
+	// identification string. The same value is used for both SSH
+	// and Obfuscated SSH servers.
+	SSHServerVersion string
+
+	// SSHUserName is the SSH user name to be presented by the
+	// the tunnel-core client. The same value is used for both SSH
+	// and Obfuscated SSH servers.
+	SSHUserName string
+
+	// SSHPassword is the SSH password to be presented by the
+	// the tunnel-core client. The same value is used for both SSH
+	// and Obfuscated SSH servers.
+	SSHPassword string
+
+	// SSHServerPort is the listening port of the SSH server.
+	// When <= 0, no SSH server component is run.
+	SSHServerPort int
+
+	// ObfuscatedSSHKey is the secret key for use in the Obfuscated
+	// SSH protocol.
+	ObfuscatedSSHKey string
+
+	// ObfuscatedSSHServerPort is the listening port of the Obfuscated SSH server.
+	// When <= 0, no Obfuscated SSH server component is run.
 	ObfuscatedSSHServerPort int
 	ObfuscatedSSHServerPort int
-	RedisServerAddress      string
+
+	// RedisServerAddress is the TCP address of a redis server. When
+	// set, redis is used to store per-session GeoIP information.
+	RedisServerAddress string
 }
 }
 
 
+// RunWebServer indicates whether to run a web server component.
 func (config *Config) RunWebServer() bool {
 func (config *Config) RunWebServer() bool {
 	return config.WebServerPort > 0
 	return config.WebServerPort > 0
 }
 }
 
 
+// RunSSHServer indicates whether to run an SSH server component.
 func (config *Config) RunSSHServer() bool {
 func (config *Config) RunSSHServer() bool {
 	return config.SSHServerPort > 0
 	return config.SSHServerPort > 0
 }
 }
 
 
+// RunObfuscatedSSHServer indicates whether to run an Obfuscated SSH server component.
 func (config *Config) RunObfuscatedSSHServer() bool {
 func (config *Config) RunObfuscatedSSHServer() bool {
 	return config.ObfuscatedSSHServerPort > 0
 	return config.ObfuscatedSSHServerPort > 0
 }
 }
 
 
+// RunObfuscatedSSHServer indicates whether to store per-session GeoIP information in
+// redis. This is for integration with the legacy psi_web component.
 func (config *Config) UseRedis() bool {
 func (config *Config) UseRedis() bool {
 	return config.RedisServerAddress != ""
 	return config.RedisServerAddress != ""
 }
 }
 
 
+// LoadConfig loads and validates a JSON encoded server config.
 func LoadConfig(configJson []byte) (*Config, error) {
 func LoadConfig(configJson []byte) (*Config, error) {
 
 
 	var config Config
 	var config Config
@@ -114,13 +182,30 @@ func LoadConfig(configJson []byte) (*Config, error) {
 	return &config, nil
 	return &config, nil
 }
 }
 
 
+// GenerateConfigParams specifies customizations to be applied to
+// a generated server config.
 type GenerateConfigParams struct {
 type GenerateConfigParams struct {
-	ServerIPAddress         string
-	WebServerPort           int
-	SSHServerPort           int
+
+	// ServerIPAddress is the public IP address of the server.
+	ServerIPAddress string
+
+	// WebServerPort is the listening port of the web server.
+	// When <= 0, no web server component is run.
+	WebServerPort int
+
+	// SSHServerPort is the listening port of the SSH server.
+	// When <= 0, no SSH server component is run.
+	SSHServerPort int
+
+	// ObfuscatedSSHServerPort is the listening port of the Obfuscated SSH server.
+	// When <= 0, no Obfuscated SSH server component is run.
 	ObfuscatedSSHServerPort int
 	ObfuscatedSSHServerPort int
 }
 }
 
 
+// GenerateConfig create a new Psiphon server config. It returns a JSON
+// 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.
 func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, error) {
 func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, error) {
 
 
 	// TODO: support disabling web server or a subset of protocols
 	// TODO: support disabling web server or a subset of protocols

+ 17 - 0
psiphon/server/geoip.go

@@ -30,6 +30,12 @@ import (
 
 
 const UNKNOWN_GEOIP_VALUE = "None"
 const UNKNOWN_GEOIP_VALUE = "None"
 
 
+// GeoIPData stores GeoIP data for a client session. Individual client
+// IP addresses are neither logged nor explicitly referenced during a session.
+// The GeoIP country, city, and ISP corresponding to a client IP address are
+// resolved and then logged along with usage stats. The DiscoveryValue is
+// a special value derived from the client IP that's used to compartmentalize
+// discoverable servers (see calculateDiscoveryValue for details).
 type GeoIPData struct {
 type GeoIPData struct {
 	Country        string
 	Country        string
 	City           string
 	City           string
@@ -37,6 +43,8 @@ type GeoIPData struct {
 	DiscoveryValue int
 	DiscoveryValue int
 }
 }
 
 
+// NewGeoIPData returns a GeoIPData initialized with the expected
+// UNKNOWN_GEOIP_VALUE values to be used when GeoIP lookup fails.
 func NewGeoIPData() GeoIPData {
 func NewGeoIPData() GeoIPData {
 	return GeoIPData{
 	return GeoIPData{
 		Country: UNKNOWN_GEOIP_VALUE,
 		Country: UNKNOWN_GEOIP_VALUE,
@@ -45,6 +53,7 @@ func NewGeoIPData() GeoIPData {
 	}
 	}
 }
 }
 
 
+// GeoIPLookup determines a GeoIPData for a given client IP address.
 func GeoIPLookup(ipAddress string) GeoIPData {
 func GeoIPLookup(ipAddress string) GeoIPData {
 
 
 	result := NewGeoIPData()
 	result := NewGeoIPData()
@@ -88,6 +97,12 @@ func GeoIPLookup(ipAddress string) GeoIPData {
 	return result
 	return result
 }
 }
 
 
+// calculateDiscoveryValue derives a value from the client IP address to be
+// used as input in the server discovery algorithm. Since we do not explicitly
+// store the client IP address, we must derive the value here and store it for
+// later use by the discovery algorithm.
+// See https://bitbucket.org/psiphon/psiphon-circumvention-system/src/tip/Automation/psi_ops_discovery.py
+// for full details.
 func calculateDiscoveryValue(ipAddress string) int {
 func calculateDiscoveryValue(ipAddress string) int {
 	// From: psi_ops_discovery.calculate_ip_address_strategy_value:
 	// From: psi_ops_discovery.calculate_ip_address_strategy_value:
 	//     # Mix bits from all octets of the client IP address to determine the
 	//     # Mix bits from all octets of the client IP address to determine the
@@ -102,6 +117,8 @@ func calculateDiscoveryValue(ipAddress string) int {
 var geoIPReader *maxminddb.Reader
 var geoIPReader *maxminddb.Reader
 var discoveryValueHMACKey string
 var discoveryValueHMACKey string
 
 
+// InitGeoIP opens a GeoIP2/GeoLite2 MaxMind database and prepares
+// it for lookups.
 func InitGeoIP(config *Config) error {
 func InitGeoIP(config *Config) error {
 
 
 	discoveryValueHMACKey = config.DiscoveryValueHMACKey
 	discoveryValueHMACKey = config.DiscoveryValueHMACKey

+ 26 - 21
psiphon/server/log.go

@@ -93,11 +93,16 @@ func InitLogging(config *Config) error {
 
 
 	if config.SyslogAddress != "" {
 	if config.SyslogAddress != "" {
 
 
+		tag := DEFAULT_SYSLOG_TAG
+		if config.SyslogTag != "" {
+			tag = config.SyslogTag
+		}
+
 		syslogHook, err = logrus_syslog.NewSyslogHook(
 		syslogHook, err = logrus_syslog.NewSyslogHook(
 			"udp",
 			"udp",
 			config.SyslogAddress,
 			config.SyslogAddress,
 			getSyslogPriority(config),
 			getSyslogPriority(config),
-			config.SyslogTag)
+			tag)
 
 
 		if err != nil {
 		if err != nil {
 			return psiphon.ContextError(err)
 			return psiphon.ContextError(err)
@@ -126,26 +131,26 @@ func getSyslogPriority(config *Config) syslog.Priority {
 	severity := syslog.LOG_DEBUG
 	severity := syslog.LOG_DEBUG
 
 
 	facilityCodes := map[string]syslog.Priority{
 	facilityCodes := map[string]syslog.Priority{
-		"KERN":     syslog.LOG_KERN,
-		"USER":     syslog.LOG_USER,
-		"MAIL":     syslog.LOG_MAIL,
-		"DAEMON":   syslog.LOG_DAEMON,
-		"AUTH":     syslog.LOG_AUTH,
-		"SYSLOG":   syslog.LOG_SYSLOG,
-		"LPR":      syslog.LOG_LPR,
-		"NEWS":     syslog.LOG_NEWS,
-		"UUCP":     syslog.LOG_UUCP,
-		"CRON":     syslog.LOG_CRON,
-		"AUTHPRIV": syslog.LOG_AUTHPRIV,
-		"FTP":      syslog.LOG_FTP,
-		"LOCAL0":   syslog.LOG_LOCAL0,
-		"LOCAL1":   syslog.LOG_LOCAL1,
-		"LOCAL2":   syslog.LOG_LOCAL2,
-		"LOCAL3":   syslog.LOG_LOCAL3,
-		"LOCAL4":   syslog.LOG_LOCAL4,
-		"LOCAL5":   syslog.LOG_LOCAL5,
-		"LOCAL6":   syslog.LOG_LOCAL6,
-		"LOCAL7":   syslog.LOG_LOCAL7,
+		"kern":     syslog.LOG_KERN,
+		"user":     syslog.LOG_USER,
+		"mail":     syslog.LOG_MAIL,
+		"daemon":   syslog.LOG_DAEMON,
+		"auth":     syslog.LOG_AUTH,
+		"syslog":   syslog.LOG_SYSLOG,
+		"lpr":      syslog.LOG_LPR,
+		"news":     syslog.LOG_NEWS,
+		"uucp":     syslog.LOG_UUCP,
+		"cron":     syslog.LOG_CRON,
+		"authpriv": syslog.LOG_AUTHPRIV,
+		"ftp":      syslog.LOG_FTP,
+		"local0":   syslog.LOG_LOCAL0,
+		"local1":   syslog.LOG_LOCAL1,
+		"local2":   syslog.LOG_LOCAL2,
+		"local3":   syslog.LOG_LOCAL3,
+		"local4":   syslog.LOG_LOCAL4,
+		"local5":   syslog.LOG_LOCAL5,
+		"local6":   syslog.LOG_LOCAL6,
+		"local7":   syslog.LOG_LOCAL7,
 	}
 	}
 
 
 	facility, ok := facilityCodes[config.SyslogFacility]
 	facility, ok := facilityCodes[config.SyslogFacility]

+ 2 - 0
psiphon/server/redis.go

@@ -83,6 +83,8 @@ func UpdateRedisForLegacyPsiWeb(psiphonSessionID string, geoIPData GeoIPData) er
 
 
 var redisPool *redis.Pool
 var redisPool *redis.Pool
 
 
+// InitRedis establishes a redis client connection pool and
+// also tests at least one single connection.
 func InitRedis(config *Config) error {
 func InitRedis(config *Config) error {
 	redisPool = &redis.Pool{
 	redisPool = &redis.Pool{
 		Dial: func() (redis.Conn, error) {
 		Dial: func() (redis.Conn, error) {

+ 8 - 0
psiphon/server/services.go

@@ -17,6 +17,10 @@
  *
  *
  */
  */
 
 
+// Package psiphon/server implements the core tunnel functionality of a Psiphon server.
+// The main function is RunServices, which runs one or all of a Psiphon API web server,
+// a tunneling SSH server, and an Obfuscated SSH protocol server. The server configuration
+// is created by the GenerateConfig function.
 package server
 package server
 
 
 import (
 import (
@@ -27,6 +31,10 @@ import (
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
 )
 )
 
 
+// RunServices initializes support functions including logging, GeoIP service, and
+// redis connection pooling; and then starts the server components and runs them
+// until os.Interrupt or os.Kill signals are received. The config determines
+// which components are run.
 func RunServices(encodedConfig []byte) error {
 func RunServices(encodedConfig []byte) error {
 
 
 	config, err := LoadConfig(encodedConfig)
 	config, err := LoadConfig(encodedConfig)

+ 17 - 0
psiphon/server/sshService.go

@@ -33,14 +33,31 @@ import (
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/crypto/ssh"
 )
 )
 
 
+// RunSSHServer runs an ssh server with plain SSH protocol.
 func RunSSHServer(config *Config, shutdownBroadcast <-chan struct{}) error {
 func RunSSHServer(config *Config, shutdownBroadcast <-chan struct{}) error {
 	return runSSHServer(config, false, shutdownBroadcast)
 	return runSSHServer(config, false, shutdownBroadcast)
 }
 }
 
 
+// RunSSHServer runs an ssh server with Obfuscated SSH protocol.
 func RunObfuscatedSSHServer(config *Config, shutdownBroadcast <-chan struct{}) error {
 func RunObfuscatedSSHServer(config *Config, shutdownBroadcast <-chan struct{}) error {
 	return runSSHServer(config, true, shutdownBroadcast)
 	return runSSHServer(config, true, shutdownBroadcast)
 }
 }
 
 
+// runSSHServer runs an SSH or Obfuscated SSH server. In the Obfuscated SSH case, an
+// ObfuscatedSSHConn is layered in front of the client TCP connection; otherwise, both
+// modes are identical.
+//
+// runSSHServer listens on the designated port and spawns new goroutines to handle
+// each client connection. It halts when shutdownBroadcast is signaled. A list of active
+// clients is maintained, and when halting all clients are first shutdown.
+//
+// Each client goroutine handles its own obfuscation (optional), SSH handshake, SSH
+// authentication, and then looping on client new channel requests. At this time, only
+// "direct-tcpip" channels, dynamic port fowards, are expected and supported.
+//
+// A new goroutine is spawned to handle each port forward. Each port forward tracks its
+// bytes transferred. Overall per-client stats for connection duration, GeoIP, number of
+// port forwards, and bytes transferred are tracked and logged when the client shuts down.
 func runSSHServer(
 func runSSHServer(
 	config *Config, useObfuscation bool, shutdownBroadcast <-chan struct{}) error {
 	config *Config, useObfuscation bool, shutdownBroadcast <-chan struct{}) error {
 
 

+ 6 - 0
psiphon/server/webService.go

@@ -38,6 +38,12 @@ type webServer struct {
 	config   *Config
 	config   *Config
 }
 }
 
 
+// RunWebServer runs a web server which responds to the following Psiphon API
+// web requests: handshake, connected, and status. At this time, this web
+// server is a stub for stand-alone testing. It provides the minimal response
+// required by the tunnel-core client. It doesn't return read landing pages,
+// serer discovery results, or other handshake response values. It also does
+// not log stats in the standard way.
 func RunWebServer(config *Config, shutdownBroadcast <-chan struct{}) error {
 func RunWebServer(config *Config, shutdownBroadcast <-chan struct{}) error {
 
 
 	webServer := &webServer{
 	webServer := &webServer{