Browse Source

Merge https://github.com/Psiphon-Labs/psiphon-tunnel-core

Miro Kuratczyk 9 năm trước cách đây
mục cha
commit
4eff565f98

+ 2 - 2
ConsoleClient/Dockerfile

@@ -22,7 +22,7 @@ RUN apt-get update -y && apt-get install -y --no-install-recommends \
   && rm -rf /var/lib/apt/lists/*
 
 # Install Go.
-ENV GOVERSION=go1.6.2 GOROOT=/usr/local/go GOPATH=/go PATH=$PATH:/usr/local/go/bin:/go/bin CGO_ENABLED=1
+ENV GOVERSION=go1.7 GOROOT=/usr/local/go GOPATH=/go PATH=$PATH:/usr/local/go/bin:/go/bin CGO_ENABLED=1
 
 RUN curl -L https://storage.googleapis.com/golang/$GOVERSION.linux-amd64.tar.gz -o /tmp/go.tar.gz \
    && tar -C /usr/local -xzf /tmp/go.tar.gz \
@@ -87,4 +87,4 @@ RUN cd $PKG_CONFIG_PATH_32 \
   && make depend \
   && make
 
-WORKDIR $GOPATH/src
+WORKDIR $GOPATH/src/github.com/Psiphon-Labs/psiphon-tunnel-core/ConsoleClient

+ 2 - 2
ConsoleClient/README.md

@@ -15,9 +15,9 @@ Note that you may need to use `sudo docker` below, depending on your OS.
   cd .. && \
     docker run \
     --rm \
-    -v $(pwd):/go/src/github.com/Psiphon-Labs/psiphon-tunnel-core \
+    -v $PWD:/go/src/github.com/Psiphon-Labs/psiphon-tunnel-core \
     psiclient \
-    /bin/bash -c 'cd /go/src/github.com/Psiphon-Labs/psiphon-tunnel-core/ConsoleClient && ./make.bash all' \
+    /bin/bash -c './make.bash all' \
   ; cd -
   ```
 This command can also be modified by:

+ 1 - 1
ConsoleClient/main.go

@@ -62,7 +62,7 @@ func main() {
 	}
 	psiphon.SetNoticeOutput(noticeWriter)
 
-	psiphon.EmitNoticeBuildInfo()
+	psiphon.NoticeBuildInfo()
 
 	// Handle required config file parameter
 

+ 37 - 20
ConsoleClient/make.bash

@@ -8,26 +8,40 @@ if [ ! -f make.bash ]; then
 fi
 
 EXE_BASENAME="psiphon-tunnel-core"
-BUILDINFOFILE="${EXE_BASENAME}_buildinfo.txt"
-BUILDDATE=$(date --iso-8601=seconds)
-BUILDREPO=$(git config --get remote.origin.url)
-BUILDREV=$(git rev-parse --short HEAD)
-GOVERSION=$(go version | perl -ne '/go version (.*?) / && print $1')
-
-LDFLAGS="\
--X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon.buildDate=$BUILDDATE \
--X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon.buildRepo=$BUILDREPO \
--X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon.buildRev=$BUILDREV \
--X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon.goVersion=$GOVERSION \
-"
-echo -e "${BUILDDATE}\n${BUILDREPO}\n${BUILDREV}\n" > $BUILDINFOFILE
-
-echo "Variables for ldflags:"
-echo " Build date: ${BUILDDATE}"
-echo " Build repo: ${BUILDREPO}"
-echo " Build revision: ${BUILDREV}"
-echo " Go version: ${GOVERSION}"
-echo ""
+
+prepare_build () {
+  BUILDINFOFILE="${EXE_BASENAME}_buildinfo.txt"
+  BUILDDATE=$(date --iso-8601=seconds)
+  BUILDREPO=$(git config --get remote.origin.url)
+  BUILDREV=$(git rev-parse --short HEAD)
+  GOVERSION=$(go version | perl -ne '/go version (.*?) / && print $1')
+
+  # - starts the string with a `{`
+  # - uses the `go list` command and passes it a template string (using the Go template syntax) saying I want all the dependencies of the package in the current directory, printing 1/line via printf
+  # - pipes to `xargs` to run a command on each line output from the first command
+  #  - uses `go list` with a template string to print the "Import Path" (from just below `$GOPATH/src`) if the package is not part of the standard library
+  # - pipes to `xargs` again, specifiying `pkg` as the placeholder name for each item being operated on (which is the list of non standard library import paths from the previous step)
+  #  - `xargs` runs a bash script (via `-c`) which changes to each import path in sequence, then echoes out `"<import path>":"<subshell output of getting the short git revision>",`
+  # - this leaves a trailing `,` at the end, and no close to the JSON object, so simply `sed` replace the comma before the end of the line with `}` and you now have valid JSON
+  DEPENDENCIES=$(echo -n "{" && go list -f '{{range $dep := .Deps}}{{printf "%s\n" $dep}}{{end}}' | xargs go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | xargs -I pkg bash -c 'cd $GOPATH/src/pkg && echo -n "\"pkg\":\"$(git rev-parse --short HEAD)\","' | sed 's/,$/}/')
+
+  LDFLAGS="\
+  -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.buildDate=$BUILDDATE \
+  -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.buildRepo=$BUILDREPO \
+  -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.buildRev=$BUILDREV \
+  -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.goVersion=$GOVERSION \
+  -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.dependencies=$DEPENDENCIES \
+  "
+  echo -e "${BUILDDATE}\n${BUILDREPO}\n${BUILDREV}\n" > $BUILDINFOFILE
+
+  echo "Variables for ldflags:"
+  echo " Build date: ${BUILDDATE}"
+  echo " Build repo: ${BUILDREPO}"
+  echo " Build revision: ${BUILDREV}"
+  echo " Go version: ${GOVERSION}"
+  echo " Dependencies: ${DEPENDENCIES}"
+  echo ""
+}
 
 if [ ! -d bin ]; then
   mkdir bin
@@ -36,6 +50,7 @@ fi
 build_for_windows () {
   echo "...Getting project dependencies (via go get) for Windows. Parameter is: '$1'"
   GOOS=windows go get -d -v ./...
+  prepare_build
   if [ $? != 0 ]; then
     echo "....'go get' failed, exiting"
     exit $?
@@ -90,6 +105,7 @@ build_for_windows () {
 build_for_linux () {
   echo "Getting project dependencies (via go get) for Linux. Parameter is: '$1'"
   GOOS=linux go get -d -v ./...
+  prepare_build
   if [ $? != 0 ]; then
     echo "...'go get' failed, exiting"
     exit $?
@@ -139,6 +155,7 @@ build_for_linux () {
 build_for_osx () {
   echo "Getting project dependencies (via go get) for OSX"
   GOOS=darwin go get -d -v ./...
+  prepare_build
   if [ $? != 0 ]; then
     echo "..'go get' failed, exiting"
     exit $?

+ 1 - 1
MobileLibrary/Android/Dockerfile

@@ -17,7 +17,7 @@ RUN apt-get update -y && apt-get install -y --no-install-recommends \
   && rm -rf /var/lib/apt/lists/*
 
 # Install Go.
-ENV GOVERSION=go1.6.2 GOROOT=/usr/local/go GOPATH=/go PATH=$PATH:/usr/local/go/bin:/go/bin CGO_ENABLED=1
+ENV GOVERSION=go1.7 GOROOT=/usr/local/go GOPATH=/go PATH=$PATH:/usr/local/go/bin:/go/bin CGO_ENABLED=1
 
 RUN curl -L https://storage.googleapis.com/golang/$GOVERSION.linux-amd64.tar.gz -o /tmp/go.tar.gz \
   && tar -C /usr/local -xzf /tmp/go.tar.gz \

+ 27 - 8
MobileLibrary/Android/make.bash

@@ -7,10 +7,18 @@ if [ ! -f make.bash ]; then
   exit 1
 fi
 
-GOOS=arm go get -d -v -u github.com/Psiphon-Inc/openssl
-GOOS=arm go get -d -v -u github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon
+# Don't use '-u' to force updates because the docker builds always pull
+# the latest versions. Outside of Docker, be aware that these dependencies
+# will not be overridden w/ new versions if they already exist in $GOPATH
+
+GOOS=arm go get -d -v github.com/Psiphon-Inc/openssl
 if [ $? != 0 ]; then
-  echo "..'go get' failed, exiting"
+  echo "..'go get -d -v github.com/psiphon-inc/openssl' failed, exiting"
+  exit $?
+fi
+GOOS=arm go get -d -v github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon
+if [ $? != 0 ]; then
+  echo "..'go get -d -v github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon' failed, exiting"
   exit $?
 fi
 
@@ -20,12 +28,22 @@ BUILDREV=$(git rev-parse --short HEAD)
 GOVERSION=$(go version | perl -ne '/go version (.*?) / && print $1')
 GOMOBILEVERSION=$(gomobile version | perl -ne '/gomobile version (.*?) / && print $1')
 
+# - starts the string with a `{`
+# - uses the `go list` command and passes it a template string (using the Go template syntax) saying I want all the dependencies of the package in the current directory, printing 1/line via printf
+# - pipes to `xargs` to run a command on each line output from the first command
+#  - uses `go list` with a template string to print the "Import Path" (from just below `$GOPATH/src`) if the package is not part of the standard library
+# - pipes to `xargs` again, specifiying `pkg` as the placeholder name for each item being operated on (which is the list of non standard library import paths from the previous step)
+#  - `xargs` runs a bash script (via `-c`) which changes to each import path in sequence, then echoes out `"<import path>":"<subshell output of getting the short git revision>",`
+# - this leaves a trailing `,` at the end, and no close to the JSON object, so simply `sed` replace the comma before the end of the line with `}` and you now have valid JSON
+DEPENDENCIES=$(echo -n "{" && go list -f '{{range $dep := .Deps}}{{printf "%s\n" $dep}}{{end}}' | xargs go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | xargs -I pkg bash -c 'cd $GOPATH/src/pkg && echo -n "\"pkg\":\"$(git rev-parse --short HEAD)\","' | sed 's/,$/}/')
+
 LDFLAGS="\
--X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon.buildDate=$BUILDDATE \
--X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon.buildRepo=$BUILDREPO \
--X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon.buildRev=$BUILDREV \
--X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon.goVersion=$GOVERSION \
--X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon.gomobileVersion=$GOMOBILEVERSION \
+-X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.buildDate=$BUILDDATE \
+-X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.buildRepo=$BUILDREPO \
+-X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.buildRev=$BUILDREV \
+-X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.goVersion=$GOVERSION \
+-X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.gomobileVersion=$GOMOBILEVERSION \
+-X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.dependencies=$DEPENDENCIES \
 "
 
 echo "Variables for ldflags:"
@@ -34,6 +52,7 @@ echo " Build repo: ${BUILDREPO}"
 echo " Build revision: ${BUILDREV}"
 echo " Go version: ${GOVERSION}"
 echo " Gomobile version: ${GOMOBILEVERSION}"
+echo " Dependencies: ${DEPENDENCIES}"
 echo ""
 
 gomobile bind -v -target=android/arm -ldflags="$LDFLAGS" github.com/Psiphon-Labs/psiphon-tunnel-core/MobileLibrary/psi

+ 1 - 1
MobileLibrary/psi/psi.go

@@ -73,7 +73,7 @@ func Start(
 			provider.Notice(string(notice))
 		}))
 
-	psiphon.EmitNoticeBuildInfo()
+	psiphon.NoticeBuildInfo()
 
 	// TODO: should following errors be Notices?
 

+ 9 - 4
README.md

@@ -29,11 +29,16 @@ Client Setup
     ```
     BUILDDATE=$(date --iso-8601=seconds)
     BUILDREPO=$(git config --get remote.origin.url)
-    BUILDREV=$(git rev-parse HEAD)
+    BUILDREV=$(git rev-parse --short HEAD)
+    GOVERSION=$(go version | perl -ne '/go version (.*?) / && print $1')
+    DEPENDENCIES=$(echo -n "{" && go list -f '{{range $dep := .Deps}}{{printf "%s\n" $dep}}{{end}}' | xargs go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | xargs -I pkg bash -c 'cd $GOPATH/src/pkg && echo -n "\"pkg\":\"$(git rev-parse --short HEAD)\","' | sed 's/,$/}/')
+
     LDFLAGS="\
-    -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon.buildDate=$BUILDDATE \
-    -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon.buildRepo=$BUILDREPO \
-    -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon.buildRev=$BUILDREV \
+    -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.buildDate=$BUILDDATE \
+    -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.buildRepo=$BUILDREPO \
+    -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.buildRev=$BUILDREV \
+    -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.goVersion=$GOVERSION \
+    -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.dependencies=$DEPENDENCIES \
     "
     ```
 

+ 4 - 3
Server/Dockerfile-binary-builder

@@ -1,6 +1,6 @@
 FROM alpine:latest
 
-ENV GOLANG_VERSION 1.6.2
+ENV GOLANG_VERSION 1.7
 ENV GOLANG_SRC_URL https://golang.org/dl/go$GOLANG_VERSION.src.tar.gz
 
 RUN set -ex \
@@ -8,10 +8,11 @@ RUN set -ex \
 		bash \
 		ca-certificates \
 		gcc \
-    git \
+		git \
+		go \
 		musl-dev \
 		openssl \
-		go \
+		perl \
 	\
 	&& export GOROOT_BOOTSTRAP="$(go env GOROOT)" \
 	\

+ 37 - 1
Server/make.bash

@@ -10,15 +10,51 @@ if [ ! -f make.bash ]; then
   exit 1
 fi
 
+prepare_build () {
+  BUILDINFOFILE="${EXE_BASENAME}_buildinfo.txt"
+  BUILDDATE=$(date -Iseconds)
+  BUILDREPO=$(git config --get remote.origin.url)
+  BUILDREV=$(git rev-parse --short HEAD)
+  GOVERSION=$(go version | perl -ne '/go version (.*?) / && print $1')
+
+  # - starts the string with a `{`
+  # - uses the `go list` command and passes it a template string (using the Go template syntax) saying I want all the dependencies of the package in the current directory, printing 1/line via printf
+  # - pipes to `xargs` to run a command on each line output from the first command
+  #  - uses `go list` with a template string to print the "Import Path" (from just below `$GOPATH/src`) if the package is not part of the standard library
+  # - pipes to `xargs` again, specifiying `pkg` as the placeholder name for each item being operated on (which is the list of non standard library import paths from the previous step)
+  #  - `xargs` runs a bash script (via `-c`) which changes to each import path in sequence, then echoes out `"<import path>":"<subshell output of getting the short git revision>",`
+  # - this leaves a trailing `,` at the end, and no close to the JSON object, so simply `sed` replace the comma before the end of the line with `}` and you now have valid JSON
+  DEPENDENCIES=$(echo -n "{" && go list -f '{{range $dep := .Deps}}{{printf "%s\n" $dep}}{{end}}' | xargs go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | xargs -I pkg bash -c 'cd $GOPATH/src/pkg && echo -n "\"pkg\":\"$(git rev-parse --short HEAD)\","' | sed 's/,$/}/')
+
+  LDFLAGS="\
+  -linkmode external -extldflags \"-static\" \
+  -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.buildDate=$BUILDDATE \
+  -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.buildRepo=$BUILDREPO \
+  -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.buildRev=$BUILDREV \
+  -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.goVersion=$GOVERSION \
+  -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.dependencies=$DEPENDENCIES \
+  "
+  echo -e "${BUILDDATE}\n${BUILDREPO}\n${BUILDREV}\n" > $BUILDINFOFILE
+
+  echo "Variables for ldflags:"
+  echo " Build date: ${BUILDDATE}"
+  echo " Build repo: ${BUILDREPO}"
+  echo " Build revision: ${BUILDREV}"
+  echo " Go version: ${GOVERSION}"
+  echo " Dependencies: ${DEPENDENCIES}"
+  echo ""
+}
+
 build_for_linux () {
   echo "Getting project dependencies (via go get) for Linux. Parameter is: '$1'"
   GOOS=linux GOARCH=amd64 go get -d -v ./...
+  prepare_build
   if [ $? != 0 ]; then
     echo "...'go get' failed, exiting"
     exit $?
   fi
 
-  GOOS=linux GOARCH=amd64 go build --ldflags '-linkmode external -extldflags "-static"' -o psiphond main.go
+  GOOS=linux GOARCH=amd64 go build -ldflags "$LDFLAGS" -o psiphond main.go
   if [ $? != 0 ]; then
     echo "...'go build' failed, exiting"
     exit $?

+ 0 - 54
psiphon/buildinfo.go

@@ -1,54 +0,0 @@
-/*
- * Copyright (c) 2015, Psiphon Inc.
- * All rights reserved.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package psiphon
-
-import "strings"
-
-/*
-These values should be filled in at build time using the `-X` option[1] to the
-Go linker (probably via `-ldflags` option to `go build` -- like `-ldflags "-X var1=abc -X var2=xyz"`).
-[1]: http://golang.org/cmd/ld/
-Without those build flags, the build info in the notice will simply be empty strings.
-Suggestions for how to fill in the values will be given for each variable.
-Note that any passed value must contain no whitespace.
-*/
-// -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon.buildDate=`date --iso-8601=seconds`
-var buildDate string
-
-// -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon.buildRepo=`git config --get remote.origin.url`
-var buildRepo string
-
-// -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon.buildRev=`git rev-parse --short HEAD`
-var buildRev string
-
-// -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon.goVersion=`go version | perl -ne '/go version (.*?) / && print $1'`
-var goVersion string
-
-// -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon.gomobileVersion=`gomobile version | perl -ne '/gomobile version (.*?) / && print $1'`
-var gomobileVersion string
-
-func EmitNoticeBuildInfo() {
-	NoticeBuildInfo(
-		strings.TrimSpace(buildDate),
-		strings.TrimSpace(buildRepo),
-		strings.TrimSpace(buildRev),
-		strings.TrimSpace(goVersion),
-		strings.TrimSpace(gomobileVersion))
-}

+ 97 - 0
psiphon/common/buildinfo.go

@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2015, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package common
+
+import (
+	"encoding/json"
+	"strings"
+)
+
+/*
+These values should be filled in at build time using the `-X` option[1] to the
+Go linker (probably via `-ldflags` option to `go build` -- like `-ldflags "-X var1=abc -X var2=xyz"`).
+[1]: http://golang.org/cmd/ld/
+Without those build flags, the build info in the notice will simply be empty strings.
+Suggestions for how to fill in the values will be given for each variable.
+Note that any passed value must contain no whitespace.
+*/
+// -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.buildDate=`date --iso-8601=seconds`
+var buildDate string
+
+// -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.buildRepo=`git config --get remote.origin.url`
+var buildRepo string
+
+// -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.buildRev=`git rev-parse --short HEAD`
+var buildRev string
+
+// -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.goVersion=`go version | perl -ne '/go version (.*?) / && print $1'`
+var goVersion string
+
+// -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common.gomobileVersion=`gomobile version | perl -ne '/gomobile version (.*?) / && print $1'`
+var gomobileVersion string
+
+// -X github.com/Psiphon-Labs/psiphon-tunnel-core/common.dependencies=`echo -n "{" && go list -f '{{range $dep := .Deps}}{{printf "%s\n" $dep}}{{end}}' | xargs go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | xargs -I pkg bash -c 'cd $GOPATH/src/pkg && echo -n "\"pkg\":\"$(git rev-parse --short HEAD)\","' | sed 's/,$/}/'`
+// Dependencies should be listed as a JSON object like the following (no spaces) {"github.com/Psiphon-Labs/psiphon-tunnel-core":"abcdef","...":"..."}
+var dependencies string
+
+// Capture relevant build information here for use in clients or servers
+type BuildInfo struct {
+	BuildDate       string          `json:"buildDate"`
+	BuildRepo       string          `json:"buildRepo"`
+	BuildRev        string          `json:"buildRev"`
+	GoVersion       string          `json:"goVersion"`
+	GomobileVersion string          `json:"gomobileVersion,omitempty"`
+	Dependencies    json.RawMessage `json:"dependencies"`
+}
+
+// Convert 'BuildInfo' struct to 'map[string]interface{}'
+func (bi *BuildInfo) ToMap() *map[string]interface{} {
+
+	var dependenciesMap map[string]interface{}
+	json.Unmarshal([]byte(bi.Dependencies), &dependenciesMap)
+
+	buildInfoMap := map[string]interface{}{
+		"buildDate":    bi.BuildDate,
+		"buildRepo":    bi.BuildRepo,
+		"buildRev":     bi.BuildRev,
+		"goVersion":    bi.GoVersion,
+		"dependencies": dependenciesMap,
+	}
+
+	return &buildInfoMap
+}
+
+// Return an instance of the BuildInfo struct
+func GetBuildInfo() *BuildInfo {
+	if strings.TrimSpace(dependencies) == "" {
+		dependencies = "{}"
+	}
+
+	buildInfo := BuildInfo{
+		BuildDate:       strings.TrimSpace(buildDate),
+		BuildRepo:       strings.TrimSpace(buildRepo),
+		BuildRev:        strings.TrimSpace(buildRev),
+		GoVersion:       strings.TrimSpace(goVersion),
+		GomobileVersion: strings.TrimSpace(gomobileVersion),
+		Dependencies:    json.RawMessage(strings.TrimSpace(dependencies)),
+	}
+
+	return &buildInfo
+}

+ 6 - 6
psiphon/config.go

@@ -328,10 +328,10 @@ type Config struct {
 	// If omitted, the default value is TUNNEL_CONNECT_TIMEOUT_SECONDS.
 	TunnelConnectTimeoutSeconds *int
 
-	// TunnelPortForwardTimeoutSeconds specifies a timeout per SSH port forward.
-	// Zero value means a port forward will not time out.
+	// TunnelPortForwardDialTimeoutSeconds specifies a dial timeout per SSH port forward.
+	// Zero value means a port forward dial will not time out.
 	// If omitted, the default value is TUNNEL_PORT_FORWARD_DIAL_TIMEOUT_SECONDS.
-	TunnelPortForwardTimeoutSeconds *int
+	TunnelPortForwardDialTimeoutSeconds *int
 
 	// TunnelSshKeepAliveProbeTimeoutSeconds specifies a timeout value for "probe"
 	// SSH keep-alive that is sent upon port forward failure.
@@ -481,9 +481,9 @@ func LoadConfig(configJson []byte) (*Config, error) {
 		config.TunnelConnectTimeoutSeconds = &defaultTunnelConnectTimeoutSeconds
 	}
 
-	if config.TunnelPortForwardTimeoutSeconds == nil {
-		defaultTunnelPortForwardTimeoutSeconds := TUNNEL_PORT_FORWARD_DIAL_TIMEOUT_SECONDS
-		config.TunnelPortForwardTimeoutSeconds = &defaultTunnelPortForwardTimeoutSeconds
+	if config.TunnelPortForwardDialTimeoutSeconds == nil {
+		TunnelPortForwardDialTimeoutSeconds := TUNNEL_PORT_FORWARD_DIAL_TIMEOUT_SECONDS
+		config.TunnelPortForwardDialTimeoutSeconds = &TunnelPortForwardDialTimeoutSeconds
 	}
 
 	if config.TunnelSshKeepAliveProbeTimeoutSeconds == nil {

+ 2 - 7
psiphon/notice.go

@@ -338,13 +338,8 @@ func NoticeConnectedTunnelDialStats(ipAddress string, tunnelDialStats *TunnelDia
 }
 
 // NoticeBuildInfo reports build version info.
-func NoticeBuildInfo(buildDate, buildRepo, buildRev, goVersion, gomobileVersion string) {
-	outputNotice("BuildInfo", 0,
-		"buildDate", buildDate,
-		"buildRepo", buildRepo,
-		"buildRev", buildRev,
-		"goVersion", goVersion,
-		"gomobileVersion", gomobileVersion)
+func NoticeBuildInfo() {
+	outputNotice("BuildInfo", 0, "buildInfo", common.GetBuildInfo())
 }
 
 // NoticeExiting indicates that tunnel-core is exiting imminently.

+ 9 - 8
psiphon/server/api.go

@@ -125,13 +125,13 @@ func handshakeAPIRequestHandler(
 		return nil, common.ContextError(err)
 	}
 
-	log.WithContextFields(
+	log.LogRawFieldsWithTimestamp(
 		getRequestLogFields(
 			support,
 			"handshake",
 			geoIPData,
 			params,
-			baseRequestParams)).Info("API event")
+			baseRequestParams))
 
 	// TODO: share struct definition with psiphon/serverApi.go?
 	var handshakeResponse struct {
@@ -197,13 +197,13 @@ func connectedAPIRequestHandler(
 		return nil, common.ContextError(err)
 	}
 
-	log.WithContextFields(
+	log.LogRawFieldsWithTimestamp(
 		getRequestLogFields(
 			support,
 			"connected",
 			geoIPData,
 			params,
-			connectedRequestParams)).Info("API event")
+			connectedRequestParams))
 
 	var connectedResponse struct {
 		ConnectedTimestamp string `json:"connected_timestamp"`
@@ -256,7 +256,7 @@ func statusAPIRequestHandler(
 	bytesTransferredFields := getRequestLogFields(
 		support, "bytes_transferred", geoIPData, params, statusRequestParams)
 	bytesTransferredFields["bytes"] = bytesTransferred
-	log.WithContextFields(bytesTransferredFields).Info("API event")
+	log.LogRawFieldsWithTimestamp(bytesTransferredFields)
 
 	// Domain bytes transferred stats
 	// Older clients may not submit this data
@@ -272,7 +272,7 @@ func statusAPIRequestHandler(
 		for domain, bytes := range hostBytes {
 			domainBytesFields["domain"] = domain
 			domainBytesFields["bytes"] = bytes
-			log.WithContextFields(domainBytesFields).Info("API event")
+			log.LogRawFieldsWithTimestamp(domainBytesFields)
 		}
 	}
 
@@ -336,7 +336,7 @@ func statusAPIRequestHandler(
 			}
 			sessionFields["total_bytes_received"] = totalBytesReceived
 
-			log.WithContextFields(sessionFields).Info("API event")
+			log.LogRawFieldsWithTimestamp(sessionFields)
 		}
 	}
 
@@ -400,7 +400,7 @@ func clientVerificationAPIRequestHandler(
 			logFields["safetynet_check"] = safetyNetCheckLogs
 		}
 
-		log.WithContextFields(logFields).Info("API event")
+		log.LogRawFieldsWithTimestamp(logFields)
 
 		if verified {
 			// TODO: change throttling treatment
@@ -525,6 +525,7 @@ func getRequestLogFields(
 
 	logFields["event_name"] = eventName
 	logFields["host_id"] = support.Config.HostID
+	logFields["build_rev"] = common.GetBuildInfo().BuildRev
 
 	// In psi_web, the space replacement was done to accommodate space
 	// delimited logging, which is no longer required; we retain the

+ 1 - 1
psiphon/server/config.go

@@ -32,10 +32,10 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/Psiphon-Inc/crypto/ssh"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
 	"golang.org/x/crypto/nacl/box"
-	"golang.org/x/crypto/ssh"
 )
 
 const (

+ 70 - 2
psiphon/server/log.go

@@ -20,6 +20,8 @@
 package server
 
 import (
+	"encoding/json"
+	"fmt"
 	"io"
 	"os"
 
@@ -60,12 +62,78 @@ func (logger *ContextLogger) WithContextFields(fields LogFields) *logrus.Entry {
 	return log.WithFields(logrus.Fields(fields))
 }
 
+// LogRawFieldsWithTimestamp directly logs the supplied fields adding only
+// an additional "timestamp" field. The stock "msg" and "level" fields are
+// omitted. This log is emitted at the Error level. This function exists to
+// support API logs which have neither a natural message nor severity; and
+// omitting these values here makes it easier to ship these logs to existing
+// API log consumers.
+func (logger *ContextLogger) LogRawFieldsWithTimestamp(fields LogFields) {
+	logger.WithFields(logrus.Fields(fields)).Error(
+		customJSONFormatterLogRawFieldsWithTimestamp)
+}
+
 // NewLogWriter returns an io.PipeWriter that can be used to write
 // to the global logger. Caller must Close() the writer.
 func NewLogWriter() *io.PipeWriter {
 	return log.Writer()
 }
 
+// CustomJSONFormatter is a customized version of logrus.JSONFormatter
+type CustomJSONFormatter struct {
+}
+
+const customJSONFormatterLogRawFieldsWithTimestamp = "CustomJSONFormatter.LogRawFieldsWithTimestamp"
+
+// Format implements logrus.Formatter. This is a customized version
+// of the standard logrus.JSONFormatter adapted from:
+// https://github.com/Sirupsen/logrus/blob/f1addc29722ba9f7651bc42b4198d0944b66e7c4/json_formatter.go
+//
+// The changes are:
+// - "time" is renamed to "timestamp"
+// - there's an option to omit the standard "msg" and "level" fields
+//
+func (f *CustomJSONFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+	data := make(logrus.Fields, len(entry.Data)+3)
+	for k, v := range entry.Data {
+		switch v := v.(type) {
+		case error:
+			// Otherwise errors are ignored by `encoding/json`
+			// https://github.com/Sirupsen/logrus/issues/137
+			data[k] = v.Error()
+		default:
+			data[k] = v
+		}
+	}
+
+	if t, ok := data["timestamp"]; ok {
+		data["fields.timestamp"] = t
+	}
+
+	data["timestamp"] = entry.Time.Format(logrus.DefaultTimestampFormat)
+
+	if entry.Message != customJSONFormatterLogRawFieldsWithTimestamp {
+
+		if m, ok := data["msg"]; ok {
+			data["fields.msg"] = m
+		}
+
+		if l, ok := data["level"]; ok {
+			data["fields.level"] = l
+		}
+
+		data["msg"] = entry.Message
+		data["level"] = entry.Level.String()
+	}
+
+	serialized, err := json.Marshal(data)
+	if err != nil {
+		return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
+	}
+
+	return append(serialized, '\n'), nil
+}
+
 var log *ContextLogger
 
 // InitLogging configures a logger according to the specified
@@ -93,7 +161,7 @@ func InitLogging(config *Config) error {
 	log = &ContextLogger{
 		&logrus.Logger{
 			Out:       logWriter,
-			Formatter: &logrus.JSONFormatter{},
+			Formatter: &CustomJSONFormatter{},
 			Level:     level,
 		},
 	}
@@ -105,7 +173,7 @@ func init() {
 	log = &ContextLogger{
 		&logrus.Logger{
 			Out:       os.Stderr,
-			Formatter: &logrus.JSONFormatter{},
+			Formatter: &CustomJSONFormatter{},
 			Hooks:     make(logrus.LevelHooks),
 			Level:     logrus.DebugLevel,
 		},

+ 16 - 16
psiphon/server/meek.go

@@ -233,11 +233,11 @@ func (server *MeekServer) ServeHTTP(responseWriter http.ResponseWriter, request
 		return
 	}
 
-	// PumpReads causes a TunnelServer/SSH goroutine blocking on a Read to
+	// pumpReads causes a TunnelServer/SSH goroutine blocking on a Read to
 	// read the request body as upstream traffic.
-	// TODO: run PumpReads and PumpWrites concurrently?
+	// TODO: run pumpReads and pumpWrites concurrently?
 
-	err = session.clientConn.PumpReads(request.Body)
+	err = session.clientConn.pumpReads(request.Body)
 	if err != nil {
 		if err != io.EOF {
 			log.WithContextFields(LogFields{"error": err}).Warning("pump reads failed")
@@ -257,10 +257,10 @@ func (server *MeekServer) ServeHTTP(responseWriter http.ResponseWriter, request
 		session.sessionIDSent = true
 	}
 
-	// PumpWrites causes a TunnelServer/SSH goroutine blocking on a Write to
+	// pumpWrites causes a TunnelServer/SSH goroutine blocking on a Write to
 	// write its downstream traffic through to the response body.
 
-	err = session.clientConn.PumpWrites(responseWriter)
+	err = session.clientConn.pumpWrites(responseWriter)
 	if err != nil {
 		if err != io.EOF {
 			log.WithContextFields(LogFields{"error": err}).Warning("pump writes failed")
@@ -612,11 +612,11 @@ func newMeekConn(remoteAddr net.Addr, protocolVersion int) *meekConn {
 	}
 }
 
-// PumpReads causes goroutines blocking on meekConn.Read() to read
+// pumpReads causes goroutines blocking on meekConn.Read() to read
 // from the specified reader. This function blocks until the reader
 // is fully consumed or the meekConn is closed.
-// Note: channel scheme assumes only one concurrent call to PumpReads
-func (conn *meekConn) PumpReads(reader io.Reader) error {
+// Note: channel scheme assumes only one concurrent call to pumpReads
+func (conn *meekConn) pumpReads(reader io.Reader) error {
 
 	// Assumes that readyReader won't block.
 	conn.readyReader <- reader
@@ -634,7 +634,7 @@ func (conn *meekConn) PumpReads(reader io.Reader) error {
 
 // Read reads from the meekConn into buffer. Read blocks until
 // some data is read or the meekConn closes. Under the hood, it
-// waits for PumpReads to submit a reader to read from.
+// waits for pumpReads to submit a reader to read from.
 // Note: lock is to conform with net.Conn concurrency semantics
 func (conn *meekConn) Read(buffer []byte) (int, error) {
 	conn.readLock.Lock()
@@ -658,7 +658,7 @@ func (conn *meekConn) Read(buffer []byte) (int, error) {
 	} else {
 		// There may be more data in the reader, but the caller's
 		// buffer is full, so put the reader back into the ready
-		// channel. PumpReads remains blocked waiting for another
+		// channel. pumpReads remains blocked waiting for another
 		// Read call.
 		// Note that the reader could be at EOF, while another call is
 		// required to get that result (https://golang.org/pkg/io/#Reader).
@@ -668,12 +668,12 @@ func (conn *meekConn) Read(buffer []byte) (int, error) {
 	return n, err
 }
 
-// PumpReads causes goroutines blocking on meekConn.Write() to write
+// pumpWrites causes goroutines blocking on meekConn.Write() to write
 // to the specified writer. This function blocks until the meek response
 // body limits (size for protocol v1, turn around time for protocol v2+)
 // are met, or the meekConn is closed.
-// Note: channel scheme assumes only one concurrent call to PumpWrites
-func (conn *meekConn) PumpWrites(writer io.Writer) error {
+// Note: channel scheme assumes only one concurrent call to pumpWrites
+func (conn *meekConn) pumpWrites(writer io.Writer) error {
 
 	startTime := time.Now()
 	timeout := time.NewTimer(MEEK_TURN_AROUND_TIMEOUT)
@@ -713,7 +713,7 @@ func (conn *meekConn) PumpWrites(writer io.Writer) error {
 
 // Write writes the buffer to the meekConn. It blocks until the
 // entire buffer is written to or the meekConn closes. Under the
-// hood, it waits for sufficient PumpWrites calls to consume the
+// hood, it waits for sufficient pumpWrites calls to consume the
 // write buffer.
 // Note: lock is to conform with net.Conn concurrency semantics
 func (conn *meekConn) Write(buffer []byte) (int, error) {
@@ -721,7 +721,7 @@ func (conn *meekConn) Write(buffer []byte) (int, error) {
 	defer conn.writeLock.Unlock()
 
 	// TODO: may be more efficient to send whole buffer
-	// and have PumpWrites stash partial buffer when can't
+	// and have pumpWrites stash partial buffer when can't
 	// send it all.
 
 	n := 0
@@ -756,7 +756,7 @@ func (conn *meekConn) Write(buffer []byte) (int, error) {
 }
 
 // Close closes the meekConn. This will interrupt any blocked
-// Read, Write, PumpReads, and PumpWrites.
+// Read, Write, pumpReads, and pumpWrites.
 func (conn *meekConn) Close() error {
 	if atomic.CompareAndSwapInt32(&conn.closed, 0, 1) {
 		close(conn.closeBroadcast)

+ 15 - 11
psiphon/server/services.go

@@ -58,6 +58,8 @@ func RunServices(configJSON []byte) error {
 		return common.ContextError(err)
 	}
 
+	log.WithContextFields(*common.GetBuildInfo().ToMap()).Info("startup")
+
 	waitGroup := new(sync.WaitGroup)
 	shutdownBroadcast := make(chan struct{})
 	errors := make(chan error)
@@ -151,21 +153,23 @@ func logServerLoad(server *TunnelServer) {
 	var memStats runtime.MemStats
 	runtime.ReadMemStats(&memStats)
 	fields := LogFields{
-		"NumGoroutine":           runtime.NumGoroutine(),
-		"MemStats.Alloc":         memStats.Alloc,
-		"MemStats.TotalAlloc":    memStats.TotalAlloc,
-		"MemStats.Sys":           memStats.Sys,
-		"MemStats.PauseTotalNs":  memStats.PauseTotalNs,
-		"MemStats.PauseNs":       memStats.PauseNs,
-		"MemStats.NumGC":         memStats.NumGC,
-		"MemStats.GCCPUFraction": memStats.GCCPUFraction,
+		"BuildRev":     common.GetBuildInfo().BuildRev,
+		"HostID":       server.sshServer.support.Config.HostID,
+		"NumGoroutine": runtime.NumGoroutine(),
+		"MemStats": map[string]interface{}{
+			"Alloc":         memStats.Alloc,
+			"TotalAlloc":    memStats.TotalAlloc,
+			"Sys":           memStats.Sys,
+			"PauseTotalNs":  memStats.PauseTotalNs,
+			"PauseNs":       memStats.PauseNs,
+			"NumGC":         memStats.NumGC,
+			"GCCPUFraction": memStats.GCCPUFraction,
+		},
 	}
 
 	// tunnel server stats
 	for tunnelProtocol, stats := range server.GetLoadStats() {
-		for stat, value := range stats {
-			fields[tunnelProtocol+"."+stat] = value
-		}
+		fields[tunnelProtocol] = stats
 	}
 
 	log.WithContextFields(fields).Info("load")

+ 13 - 2
psiphon/server/tunnelServer.go

@@ -30,9 +30,9 @@ import (
 	"sync/atomic"
 	"time"
 
+	"github.com/Psiphon-Inc/crypto/ssh"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
-	"golang.org/x/crypto/ssh"
 )
 
 const (
@@ -385,6 +385,17 @@ func (sshServer *sshServer) getLoadStats() map[string]map[string]int64 {
 		client.Unlock()
 	}
 
+	// Calculate and report totals across all protocols. It's easier to do this here
+	// than futher down the stats stack. Also useful for glancing at log files.
+
+	allProtocolsStats := make(map[string]int64)
+	for _, stats := range loadStats {
+		for name, value := range stats {
+			allProtocolsStats[name] += value
+		}
+	}
+	loadStats["ALL"] = allProtocolsStats
+
 	return loadStats
 }
 
@@ -479,7 +490,7 @@ func (sshServer *sshServer) handleClient(tunnelProtocol string, clientConn net.C
 			// TODO: ensure this won't block shutdown
 			conn, result.err = psiphon.NewObfuscatedSshConn(
 				psiphon.OBFUSCATION_CONN_MODE_SERVER,
-				clientConn,
+				conn,
 				sshServer.support.Config.ObfuscatedSSHKey)
 			if result.err != nil {
 				result.err = common.ContextError(result.err)

+ 1 - 1
psiphon/server/udp.go

@@ -30,8 +30,8 @@ import (
 	"sync/atomic"
 	"time"
 
+	"github.com/Psiphon-Inc/crypto/ssh"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
-	"golang.org/x/crypto/ssh"
 )
 
 // setUDPChannel sets the single UDP channel for this sshClient.

+ 3 - 3
psiphon/tunnel.go

@@ -32,10 +32,10 @@ import (
 	"sync/atomic"
 	"time"
 
+	"github.com/Psiphon-Inc/crypto/ssh"
 	regen "github.com/Psiphon-Inc/goregen"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/transferstats"
-	"golang.org/x/crypto/ssh"
 )
 
 // Tunneler specifies the interface required by components that use a tunnel.
@@ -268,8 +268,8 @@ func (tunnel *Tunnel) Dial(
 		err                error
 	}
 	resultChannel := make(chan *tunnelDialResult, 2)
-	if *tunnel.config.TunnelPortForwardTimeoutSeconds > 0 {
-		time.AfterFunc(time.Duration(*tunnel.config.TunnelPortForwardTimeoutSeconds)*time.Second, func() {
+	if *tunnel.config.TunnelPortForwardDialTimeoutSeconds > 0 {
+		time.AfterFunc(time.Duration(*tunnel.config.TunnelPortForwardDialTimeoutSeconds)*time.Second, func() {
 			resultChannel <- &tunnelDialResult{nil, errors.New("tunnel dial timeout")}
 		})
 	}