-
Notifications
You must be signed in to change notification settings - Fork 102
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Mobile companion app and tor implementation (#2715)
Co-authored-by: Brian Stafford <[email protected]>
- Loading branch information
Showing
95 changed files
with
4,021 additions
and
22 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
name: Android build | ||
on: | ||
workflow_dispatch: | ||
inputs: | ||
name: | ||
description: "Release-Build" | ||
default: "Build Android companion app" | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
# Checkout project code | ||
- name: Checking out branch | ||
uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4.0.0 | ||
with: | ||
# Use sparse checkout to only select files in mobile app directory | ||
# Turning off cone mode ensures that files in the project root are not included during checkout | ||
sparse-checkout: 'companionapp/android' | ||
sparse-checkout-cone-mode: false | ||
|
||
# This step is needed because expo-github-action does not support paths. | ||
# Therefore all mobile app assets should be moved to the project root. | ||
- name: Move mobile app files to root | ||
run: | | ||
ls -lah | ||
shopt -s dotglob | ||
mv companionapp/android/* . | ||
ls -lah | ||
- name: Setup Java | ||
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0 | ||
with: | ||
distribution: 'temurin' | ||
java-version: 17 | ||
|
||
- name: Setup Android SDK | ||
uses: android-actions/setup-android@00854ea68c109d98c75d956347303bf7c45b0277 # v3.2.1 | ||
|
||
- name: Build Debug apk | ||
run: ./gradlew assembleDebug --stacktrace | ||
|
||
- name: Get apk path | ||
id: debugApk | ||
run: echo "apkfile=$(find app/build/outputs/apk/debug/*.apk)" >> $GITHUB_OUTPUT | ||
|
||
- name: Upload Debug Build to Artifacts | ||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 | ||
with: | ||
name: debug-apk | ||
path: ${{ steps.debugApk.outputs.apkfile }} |
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,3 @@ | ||
### Embedding tor | ||
|
||
To embed `tor` in dexc binary, compile it with `./build_linux.sh` and build using `go build -tags tor`. |
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,5 @@ | ||
//go:build !tor | ||
|
||
package tor | ||
|
||
var torBinary []byte |
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,8 @@ | ||
//go:build tor | ||
|
||
package tor | ||
|
||
import _ "embed" | ||
|
||
//go:embed build/tor | ||
var torBinary []byte |
Empty file.
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,25 @@ | ||
#!/usr/bin/env bash | ||
|
||
BASE_DIR=$(realpath $(dirname $0)) | ||
BUILD_DIR="${BASE_DIR}/build" | ||
REPO_DIR="${BUILD_DIR}/torrepo" | ||
|
||
COMMIT_HASH=3cb6a690be60fcdab60130402ff88dcfc0657596 | ||
|
||
rm -r -f "${REPO_DIR}" | ||
mkdir -p "${REPO_DIR}" | ||
cd "${REPO_DIR}" | ||
|
||
git init | ||
git remote add origin https://gitlab.torproject.org/tpo/core/tor | ||
git fetch --depth 1 origin ${COMMIT_HASH} | ||
git checkout FETCH_HEAD | ||
|
||
./autogen.sh | ||
./configure --disable-asciidoc | ||
make -j$(nproc) | ||
|
||
cd "${BUILD_DIR}" | ||
rm -f tor | ||
cp "${REPO_DIR}/src/app/tor" . | ||
rm -rf "${REPO_DIR}" |
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,195 @@ | ||
// This code is available on the terms of the project LICENSE.md file, | ||
// also available online at https://blueoakcouncil.org/license/1.0.0. | ||
|
||
package tor | ||
|
||
import ( | ||
"context" | ||
_ "embed" | ||
"errors" | ||
"fmt" | ||
"net" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"strings" | ||
"sync" | ||
"syscall" | ||
"time" | ||
|
||
"decred.org/dcrdex/dex" | ||
"github.com/jrick/logrotate/rotator" | ||
) | ||
|
||
type HiddenService struct { | ||
log dex.Logger | ||
exePath string | ||
dataDir string | ||
logPath string | ||
|
||
serverAddr string | ||
onionAddr string | ||
} | ||
|
||
func New(dataDir string, log dex.Logger) (_ *HiddenService, err error) { | ||
if len(torBinary) == 0 { | ||
log.Error("It doesn't look like tor was packaged correctly") | ||
log.Error("Tor is only available on Linux for now") | ||
log.Error("If you are on linux and building bisonwallet from source, run client/build_linux.sh and then rebuild bisonwallet") | ||
log.Error("If this is a release, we really botched it") | ||
return nil, errors.New("tor not packaged") | ||
} | ||
|
||
logDir := filepath.Join(dataDir, "logs") | ||
if err := os.MkdirAll(logDir, 0700); err != nil { | ||
return nil, fmt.Errorf("error creating tor directory: %w", err) | ||
} | ||
|
||
exePath := filepath.Join(dataDir, "tor") | ||
if err := os.WriteFile(exePath, torBinary, 0700); err != nil { | ||
return nil, fmt.Errorf("error writing tor binary: %w", err) | ||
} | ||
|
||
return &HiddenService{ | ||
log: log, | ||
exePath: exePath, | ||
logPath: filepath.Join(logDir, "tor.Log"), | ||
dataDir: dataDir, | ||
}, nil | ||
} | ||
|
||
const torrcTemplate = `#Bison Wallet tor relay configuration | ||
SOCKSPort %s | ||
DataDirectory %s | ||
HiddenServiceDir %s | ||
HiddenServicePort 80 %s | ||
` | ||
|
||
func (s *HiddenService) Connect(ctx context.Context) (*sync.WaitGroup, error) { | ||
torrcPath := filepath.Join(s.dataDir, "torrc") | ||
|
||
hiddenServiceDir := filepath.Join(s.dataDir, "hidden-service") | ||
|
||
ports, err := findOpenPort(2) | ||
if err != nil { | ||
return nil, fmt.Errorf("error finding open port: %w", err) | ||
} | ||
|
||
socksPort, serverPort := ports[0], ports[1] | ||
s.serverAddr = "127.0.0.1:" + serverPort | ||
|
||
torrcData := []byte(fmt.Sprintf(torrcTemplate, socksPort, s.dataDir, hiddenServiceDir, s.serverAddr)) | ||
if err := os.WriteFile(torrcPath, torrcData, 0600); err != nil { | ||
return nil, fmt.Errorf("error writing torrc file: %w", err) | ||
} | ||
|
||
var wg sync.WaitGroup | ||
|
||
wg.Add(1) | ||
go func() { | ||
defer wg.Done() | ||
|
||
const maxLogRolls = 8 | ||
const logFileMaxSizeKB = 16 * 1024 // 16 MB | ||
logRotator, err := rotator.New(s.logPath, logFileMaxSizeKB, false, maxLogRolls) | ||
if err != nil { | ||
s.log.Errorf("error intializing log files: %w", err) | ||
return | ||
} | ||
defer logRotator.Close() | ||
|
||
cmd := exec.CommandContext(ctx, s.exePath, "-f", torrcPath) | ||
cmd.Stdout = logRotator | ||
cmd.Stderr = logRotator | ||
cmd.Cancel = func() error { | ||
if cmd.Process == nil { // probably not possible? | ||
return nil | ||
} | ||
if err := cmd.Process.Signal(syscall.SIGINT); err != nil { | ||
cmd.Process.Kill() | ||
return err | ||
} | ||
return nil | ||
} | ||
if err := cmd.Start(); err != nil { | ||
s.log.Errorf("tor node exited with an error: %v", err) | ||
return | ||
} | ||
|
||
<-ctx.Done() | ||
|
||
errC := make(chan error) | ||
go func() { | ||
_, err := cmd.Process.Wait() | ||
errC <- err | ||
}() | ||
|
||
select { | ||
case err := <-errC: | ||
if err != nil { | ||
s.log.Errorf("Error attempting clean shutdown: %v", err) | ||
} | ||
case <-time.After(time.Second * 5): | ||
cmd.Process.Kill() | ||
s.log.Error("Timed out waiting for clean shutdown") | ||
} | ||
}() | ||
|
||
hostnamePath := filepath.Join(hiddenServiceDir, "hostname") | ||
timeout := time.After(time.Second * 10) | ||
checkTick := time.After(0) | ||
out: | ||
for { | ||
select { | ||
case <-checkTick: | ||
b, err := os.ReadFile(hostnamePath) | ||
if err != nil { | ||
if !os.IsNotExist(err) { | ||
return nil, fmt.Errorf("error attemtpting to read hostname file: %w", err) | ||
} | ||
} else { | ||
s.onionAddr = strings.TrimSpace(string(b)) | ||
break out | ||
} | ||
case <-timeout: | ||
return nil, fmt.Errorf("timed out waiting for tor to publish hostname") | ||
case <-ctx.Done(): | ||
return nil, ctx.Err() | ||
} | ||
checkTick = time.After(time.Millisecond * 100) | ||
} | ||
|
||
return &wg, nil | ||
} | ||
|
||
func (s *HiddenService) ServerAddress() string { | ||
return s.serverAddr | ||
} | ||
|
||
func (s *HiddenService) OnionAddress() string { | ||
return "http://" + s.onionAddr | ||
} | ||
|
||
func findOpenPort(n int) ([]string, error) { | ||
ports := make([]string, n) | ||
for i := 0; i < n; i++ { | ||
addr, err := net.ResolveTCPAddr("tcp", "localhost:0") | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
l, err := net.ListenTCP("tcp", addr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer l.Close() | ||
_, port, err := net.SplitHostPort(l.Addr().String()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
ports[i] = port | ||
} | ||
|
||
return ports, nil | ||
} |
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,36 @@ | ||
//go:build live | ||
|
||
package tor | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"testing" | ||
|
||
"decred.org/dcrdex/dex" | ||
) | ||
|
||
func TestConnect(t *testing.T) { | ||
dataDir, _ := os.MkdirTemp("", "") | ||
defer os.RemoveAll(dataDir) | ||
|
||
log := dex.StdOutLogger("T", dex.LevelDebug) | ||
relay, err := New(dataDir, log) | ||
if err != nil { | ||
t.Fatalf("New error: %v", err) | ||
} | ||
|
||
cm := dex.NewConnectionMaster(relay) | ||
defer cm.Wait() | ||
|
||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
|
||
if err := cm.ConnectOnce(ctx); err != nil { | ||
t.Fatalf("Connect error: %v", err) | ||
} | ||
|
||
fmt.Println("Generated server address:", relay.ServerAddress()) | ||
fmt.Println("Generated onion address:", relay.OnionAddress()) | ||
} |
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
Oops, something went wrong.