Skip to content
This repository has been archived by the owner on May 6, 2024. It is now read-only.

Commit

Permalink
Merge pull request #98 from Jigsaw-Code/bemasc-prefix
Browse files Browse the repository at this point in the history
Use the specified prefix for the connectivity checks
  • Loading branch information
Benjamin M. Schwartz authored Nov 9, 2022
2 parents c36d16e + d0b2784 commit a7f0bfb
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 61 deletions.
26 changes: 6 additions & 20 deletions outline/android/tun2socks.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@
package tun2socks

import (
"fmt"
"math"
"runtime/debug"

"github.com/Jigsaw-Code/outline-go-tun2socks/outline"
"github.com/Jigsaw-Code/outline-go-tun2socks/outline/shadowsocks"
"github.com/Jigsaw-Code/outline-go-tun2socks/tunnel"
"github.com/Jigsaw-Code/outline-ss-server/client"
"github.com/eycorsican/go-tun2socks/common/log"
)

Expand All @@ -40,32 +38,20 @@ type OutlineTunnel interface {
// Returns an OutlineTunnel instance and does *not* take ownership of the TUN file descriptor; the
// caller is responsible for closing after OutlineTunnel disconnects.
//
// `fd` is the TUN device. The OutlineTunnel acquires an additional reference to it, which
// - `fd` is the TUN device. The OutlineTunnel acquires an additional reference to it, which
// is released by OutlineTunnel.Disconnect(), so the caller must close `fd` _and_ call
// Disconnect() in order to close the TUN device.
// `host` is IP address of the Shadowsocks proxy server.
// `port` is the port of the Shadowsocks proxy server.
// `password` is the password of the Shadowsocks proxy.
// `cipher` is the encryption cipher the Shadowsocks proxy.
// `prefix` is the salt prefix to use for TCP connections (optional, use with care).
// `isUDPEnabled` indicates whether the tunnel and/or network enable UDP proxying.
// - `client` is the Shadowsocks client (created by [shadowsocks.NewClient]).
// - `isUDPEnabled` indicates whether the tunnel and/or network enable UDP proxying.
//
// Throws an exception if the TUN file descriptor cannot be opened, or if the tunnel fails to
// connect.
func ConnectShadowsocksTunnel(fd int, host string, port int, password, cipher string, prefix []byte, isUDPEnabled bool) (OutlineTunnel, error) {
if port <= 0 || port > math.MaxUint16 {
return nil, fmt.Errorf("Invalid port number: %v", port)
}
func ConnectShadowsocksTunnel(fd int, client *shadowsocks.Client, isUDPEnabled bool) (OutlineTunnel, error) {
tun, err := tunnel.MakeTunFile(fd)
if err != nil {
return nil, err
}
ssclient, err := client.NewClient(host, port, password, cipher)
if err != nil {
return nil, fmt.Errorf("failed to construct Shadowsocks client: %v", err)
}
ssclient.SetTCPSaltGenerator(client.NewPrefixSaltGenerator(prefix))
t, err := outline.NewTunnel(ssclient, isUDPEnabled, tun)
t, err := outline.NewTunnel(client, isUDPEnabled, tun)
if err != nil {
return nil, err
}
Expand Down
25 changes: 7 additions & 18 deletions outline/apple/tun2socks.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@ package tun2socks

import (
"errors"
"fmt"
"io"
"math"
"runtime/debug"
"time"

"github.com/Jigsaw-Code/outline-go-tun2socks/outline"
"github.com/Jigsaw-Code/outline-ss-server/client"
"github.com/Jigsaw-Code/outline-go-tun2socks/outline/shadowsocks"
)

// OutlineTunnel embeds the tun2socks.Tunnel interface so it gets exported by gobind.
Expand Down Expand Up @@ -54,24 +52,15 @@ func init() {
// Returns an OutlineTunnel instance that should be used to input packets to the tunnel.
//
// `tunWriter` is used to output packets to the TUN (VPN).
// `host` is IP address of the Shadowsocks proxy server.
// `port` is the port of the Shadowsocks proxy server.
// `password` is the password of the Shadowsocks proxy.
// `cipher` is the encryption cipher the Shadowsocks proxy.
// `prefix` is the salt prefix to use for TCP connections (optional, use with care).
// `client` is the Shadowsocks client (created by [shadowsocks.NewClient]).
// `isUDPEnabled` indicates whether the tunnel and/or network enable UDP proxying.
//
// Sets an error if the tunnel fails to connect.
func ConnectShadowsocksTunnel(tunWriter TunWriter, host string, port int, password, cipher string, prefix []byte, isUDPEnabled bool) (OutlineTunnel, error) {
func ConnectShadowsocksTunnel(tunWriter TunWriter, client *shadowsocks.Client, isUDPEnabled bool) (OutlineTunnel, error) {
if tunWriter == nil {
return nil, errors.New("Must provide a TunWriter")
} else if port <= 0 || port > math.MaxUint16 {
return nil, fmt.Errorf("Invalid port number: %v", port)
return nil, errors.New("must provide a TunWriter")
} else if client == nil {
return nil, errors.New("must provide a client")
}
ssclient, err := client.NewClient(host, port, password, cipher)
if err != nil {
return nil, fmt.Errorf("failed to construct Shadowsocks client: %v", err)
}
ssclient.SetTCPSaltGenerator(client.NewPrefixSaltGenerator(prefix))
return outline.NewTunnel(ssclient, isUDPEnabled, tunWriter)
return outline.NewTunnel(client, isUDPEnabled, tunWriter)
}
46 changes: 29 additions & 17 deletions outline/electron/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"flag"
"fmt"
"io"
"net/url"
"os"
"os/signal"
"strings"
Expand All @@ -27,7 +26,6 @@ import (

oss "github.com/Jigsaw-Code/outline-go-tun2socks/outline/shadowsocks"
"github.com/Jigsaw-Code/outline-go-tun2socks/shadowsocks"
"github.com/Jigsaw-Code/outline-ss-server/client"
"github.com/eycorsican/go-tun2socks/common/log"
_ "github.com/eycorsican/go-tun2socks/common/log/simple" // Register a simple logger.
"github.com/eycorsican/go-tun2socks/core"
Expand Down Expand Up @@ -70,7 +68,7 @@ func main() {
args.proxyPort = flag.Int("proxyPort", 0, "Shadowsocks proxy port number")
args.proxyPassword = flag.String("proxyPassword", "", "Shadowsocks proxy password")
args.proxyCipher = flag.String("proxyCipher", "chacha20-ietf-poly1305", "Shadowsocks proxy encryption cipher")
args.proxyPrefix = flag.String("proxyPrefix", "", "Shadowsocks connection prefix, URI-encoded (unsafe)")
args.proxyPrefix = flag.String("proxyPrefix", "", "Shadowsocks connection prefix, UTF8-encoded (unsafe)")
args.logLevel = flag.String("logLevel", "info", "Logging level: debug|info|warn|error|none")
args.dnsFallback = flag.Bool("dnsFallback", false, "Enable DNS fallback over TCP (overrides the UDP handler).")
args.checkConnectivity = flag.Bool("checkConnectivity", false, "Check the proxy TCP and UDP connectivity and exit.")
Expand Down Expand Up @@ -100,8 +98,33 @@ func main() {
os.Exit(oss.IllegalConfiguration)
}

config := oss.Config{
Host: *args.proxyHost,
Port: *args.proxyPort,
Password: *args.proxyPassword,
CipherName: *args.proxyCipher,
}

// The prefix is an 8-bit-clean byte sequence, stored in the codepoint
// values of a unicode string, which arrives here encoded in UTF-8.
prefixRunes := []rune(*args.proxyPrefix)
config.Prefix = make([]byte, len(prefixRunes))
for i, r := range prefixRunes {
if (r & 0xFF) != r {
log.Errorf("Character out of range: %r", r)
os.Exit(oss.IllegalConfiguration)
}
config.Prefix[i] = byte(r)
}

client, err := oss.NewClient(&config)
if err != nil {
log.Errorf("Failed to construct Shadowsocks client: %v", err)
os.Exit(oss.IllegalConfiguration)
}

if *args.checkConnectivity {
connErrCode, err := oss.CheckConnectivity(*args.proxyHost, *args.proxyPort, *args.proxyPassword, *args.proxyCipher)
connErrCode, err := oss.CheckConnectivity(client)
log.Debugf("Connectivity checks error code: %v", connErrCode)
if err != nil {
log.Errorf("Failed to perform connectivity checks: %v", err)
Expand All @@ -119,25 +142,14 @@ func main() {
// Output packets to TUN device
core.RegisterOutputFn(tunDevice.Write)

ssclient, err := client.NewClient(*args.proxyHost, *args.proxyPort, *args.proxyPassword, *args.proxyCipher)
if err != nil {
log.Errorf("Failed to construct Shadowsocks client: %v", err)
os.Exit(oss.IllegalConfiguration)
}
prefixBytes, err := url.PathUnescape(*args.proxyPrefix)
if err != nil {
log.Errorf("\"%s\" could not be URI-decoded", *args.proxyPrefix)
os.Exit(oss.IllegalConfiguration)
}
ssclient.SetTCPSaltGenerator(client.NewPrefixSaltGenerator([]byte(prefixBytes)))
// Register TCP and UDP connection handlers
core.RegisterTCPConnHandler(shadowsocks.NewTCPHandler(ssclient))
core.RegisterTCPConnHandler(shadowsocks.NewTCPHandler(client))
if *args.dnsFallback {
// UDP connectivity not supported, fall back to DNS over TCP.
log.Debugf("Registering DNS fallback UDP handler")
core.RegisterUDPConnHandler(dnsfallback.NewUDPHandler())
} else {
core.RegisterUDPConnHandler(shadowsocks.NewUDPHandler(ssclient, udpTimeout))
core.RegisterUDPConnHandler(shadowsocks.NewUDPHandler(client, udpTimeout))
}

// Configure LWIP stack to receive input data from the TUN device
Expand Down
50 changes: 50 additions & 0 deletions outline/shadowsocks/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2022 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package shadowsocks

import (
"github.com/Jigsaw-Code/outline-ss-server/client"
"github.com/eycorsican/go-tun2socks/common/log"
)

// Config represents a shadowsocks server configuration.
// Exported via gobind.
type Config struct {
Host string
Port int
Password string
CipherName string
Prefix []byte
}

// Client provides a transparent container for [client.Client] that
// is exportable (as an opaque object) via gobind.
type Client struct {
client.Client
}

// NewClient provides a gobind-compatible wrapper for [client.NewClient].
func NewClient(config *Config) (*Client, error) {
c, err := client.NewClient(config.Host, config.Port, config.Password, config.CipherName)
if err != nil {
return nil, err
}
if len(config.Prefix) > 0 {
log.Debugf("Using salt prefix: %s", string(config.Prefix))
c.SetTCPSaltGenerator(client.NewPrefixSaltGenerator(config.Prefix))
}

return &Client{c}, nil
}
7 changes: 1 addition & 6 deletions outline/shadowsocks/connectivity.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,7 @@ const reachabilityTimeout = 10 * time.Second
// the current network. Parallelizes the execution of TCP and UDP checks, selects the appropriate
// error code to return accounting for transient network failures.
// Returns an error if an unexpected error ocurrs.
func CheckConnectivity(host string, port int, password, cipher string) (int, error) {
client, err := shadowsocks.NewClient(host, port, password, cipher)
if err != nil {
// TODO: Inspect error for invalid cipher error or proxy host resolution failure.
return Unexpected, err
}
func CheckConnectivity(client *Client) (int, error) {
tcpChan := make(chan error)
// Check whether the proxy is reachable and that the client is able to authenticate to the proxy
go func() {
Expand Down

0 comments on commit a7f0bfb

Please sign in to comment.