Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: salt prefix support #1454

Merged
merged 16 commits into from
Nov 10, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ parcelable ShadowsocksConfig {
int port;
String password;
String method;
@nullable byte[] prefix;
fortuna marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import java.util.logging.Logger;
import tun2socks.OutlineTunnel;
import tun2socks.Tun2socks;
import org.outline.shadowsocks.ShadowsocksConfig;


/**
* Manages the life-cycle of the system VPN, and of the tunnel that processes its traffic.
Expand Down Expand Up @@ -120,20 +122,16 @@ public synchronized void tearDownVpn() {
/**
* Connects a tunnel between a Shadowsocks proxy server and the VPN TUN interface.
*
* @param host is IP address of the SOCKS proxy server.
* @param port is the port of the SOCKS proxy server.
* @param password is the password of the Shadowsocks proxy.
* @param cipher is the encryption cipher used by the Shadowsocks proxy.
* @param client provides access to the Shadowsocks proxy.
* @throws IllegalArgumentException if |socksServerAddress| is null.
* @throws IllegalStateException if the VPN has not been established, or the tunnel is already
* connected.
* @throws Exception when the tunnel fails to connect.
*/
public synchronized void connectTunnel(final String host, int port, final String password,
final String cipher, boolean isUdpEnabled) throws Exception {
public synchronized void connectTunnel(final shadowsocks.Client client, boolean isUdpEnabled) throws Exception {
fortuna marked this conversation as resolved.
Show resolved Hide resolved
LOG.info("Connecting the tunnel.");
if (host == null || port <= 0 || port > 65535 || password == null || cipher == null) {
throw new IllegalArgumentException("Must provide valid Shadowsocks proxy parameters.");
if (client == null) {
throw new IllegalArgumentException("Must provide a Shadowsocks client.");
}
if (tunFd == null) {
throw new IllegalStateException("Must establish the VPN before connecting the tunnel.");
Expand All @@ -143,8 +141,7 @@ public synchronized void connectTunnel(final String host, int port, final String
}

LOG.fine("Starting tun2socks...");
tunnel = Tun2socks.connectShadowsocksTunnel(
tunFd.getFd(), host, port, password, cipher, isUdpEnabled);
tunnel = Tun2socks.connectShadowsocksTunnel(tunFd.getFd(), client, isUdpEnabled);
}

/* Disconnects a tunnel created by a previous call to |connectTunnel|. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,16 @@ public static TunnelConfig makeTunnelConfig(final String tunnelId, final JSONObj
} catch (JSONException e) {
LOG.fine("Tunnel config missing name");
}
try {
fortuna marked this conversation as resolved.
Show resolved Hide resolved
String prefix = config.getString("prefix");
fortuna marked this conversation as resolved.
Show resolved Hide resolved
LOG.fine("Activating experimental prefix support");
tunnelConfig.proxy.prefix = new byte[prefix.length()];
for (int i = 0; i < prefix.length(); i++) {
tunnelConfig.proxy.prefix[i] = (byte)prefix.charAt(i);
fortuna marked this conversation as resolved.
Show resolved Hide resolved
}
} catch (JSONException e) {
// pass
}
return tunnelConfig;
}

Expand Down Expand Up @@ -201,13 +211,26 @@ private synchronized OutlinePlugin.ErrorCode startTunnel(
}
}

final ShadowsocksConfig proxyConfig = config.proxy;
final shadowsocks.Config configCopy = new shadowsocks.Config();
configCopy.setHost(config.proxy.host);
configCopy.setPort(config.proxy.port);
configCopy.setCipherName(config.proxy.method);
configCopy.setPassword(config.proxy.password);
configCopy.setPrefix(config.proxy.prefix);
final shadowsocks.Client client;
try {
client = new shadowsocks.Client(configCopy);
fortuna marked this conversation as resolved.
Show resolved Hide resolved
} catch (Exception e) {
tearDownActiveTunnel();
return OutlinePlugin.ErrorCode.ILLEGAL_SERVER_CONFIGURATION;
fortuna marked this conversation as resolved.
Show resolved Hide resolved
}

OutlinePlugin.ErrorCode errorCode = OutlinePlugin.ErrorCode.NO_ERROR;
if (!isAutoStart) {
try {
// Do not perform connectivity checks when connecting on startup. We should avoid failing
// the connection due to a network error, as network may not be ready.
errorCode = checkServerConnectivity(proxyConfig);
errorCode = checkServerConnectivity(client);
if (!(errorCode == OutlinePlugin.ErrorCode.NO_ERROR
|| errorCode == OutlinePlugin.ErrorCode.UDP_RELAY_NOT_ENABLED)) {
tearDownActiveTunnel();
Expand All @@ -233,8 +256,7 @@ private synchronized OutlinePlugin.ErrorCode startTunnel(
final boolean remoteUdpForwardingEnabled =
isAutoStart ? tunnelStore.isUdpSupported() : errorCode == OutlinePlugin.ErrorCode.NO_ERROR;
try {
vpnTunnel.connectTunnel(proxyConfig.host, proxyConfig.port, proxyConfig.password,
proxyConfig.method, remoteUdpForwardingEnabled);
vpnTunnel.connectTunnel(client, remoteUdpForwardingEnabled);
fortuna marked this conversation as resolved.
Show resolved Hide resolved
} catch (Exception e) {
LOG.log(Level.SEVERE, "Failed to connect the tunnel", e);
tearDownActiveTunnel();
Expand Down Expand Up @@ -286,10 +308,9 @@ private void stopVpnTunnel() {

// Shadowsocks

private OutlinePlugin.ErrorCode checkServerConnectivity(final ShadowsocksConfig config) {
private OutlinePlugin.ErrorCode checkServerConnectivity(final shadowsocks.Client client) {
try {
long errorCode = shadowsocks.Shadowsocks.checkConnectivity(
config.host, config.port, config.password, config.method);
long errorCode = Shadowsocks.checkConnectivity(client);
OutlinePlugin.ErrorCode result = OutlinePlugin.ErrorCode.values()[(int) errorCode];
LOG.info(String.format(Locale.ROOT, "Go connectivity check result: %s", result.name()));
return result;
Expand Down Expand Up @@ -429,6 +450,15 @@ private void storeActiveTunnel(final TunnelConfig config, boolean isUdpSupported
proxyConfig.put("port", config.proxy.port);
proxyConfig.put("password", config.proxy.password);
proxyConfig.put("method", config.proxy.method);

if (config.proxy.prefix != null) {
char[] chars = new char[config.proxy.prefix.length];
for (int i = 0; i < config.proxy.prefix.length; i++) {
chars[i] = (char)(config.proxy.prefix[i] & 0xFF);
fortuna marked this conversation as resolved.
Show resolved Hide resolved
}
proxyConfig.put("prefix", new String(chars));
}

tunnel.put(TUNNEL_ID_KEY, config.id).put(TUNNEL_CONFIG_KEY, proxyConfig);
tunnelStore.save(tunnel);
} catch (JSONException e) {
Expand Down
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
8 changes: 6 additions & 2 deletions src/electron/go_vpn_tunnel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export class GoVpnTunnel implements VpnTunnel {
console.log(`UDP support: ${this.isUdpEnabled}`);
await this.tun2socks.start(this.isUdpEnabled);

console.log('starting routing daemon');
await this.routing.start();
}

Expand Down Expand Up @@ -225,7 +226,8 @@ class GoTun2socks {
// -tunName outline-tap0 -tunDNS 1.1.1.1,9.9.9.9 \
// -tunAddr 10.0.85.2 -tunGw 10.0.85.1 -tunMask 255.255.255.0 \
// -proxyHost 127.0.0.1 -proxyPort 1080 -proxyPassword mypassword \
// -proxyCipher chacha20-ietf-poly1035 [-dnsFallback] [-checkConnectivity]
// -proxyCipher chacha20-ietf-poly1035
// [-dnsFallback] [-checkConnectivity] [-proxyPrefix]
const args: string[] = [];
args.push('-tunName', TUN2SOCKS_TAP_DEVICE_NAME);
args.push('-tunAddr', TUN2SOCKS_TAP_DEVICE_IP);
Expand All @@ -236,6 +238,7 @@ class GoTun2socks {
args.push('-proxyPort', `${this.config.port}`);
args.push('-proxyPassword', this.config.password || '');
args.push('-proxyCipher', this.config.method || '');
args.push('-proxyPrefix', encodeURI(this.config.prefix || ''));
fortuna marked this conversation as resolved.
Show resolved Hide resolved
args.push('-logLevel', this.process.isDebugModeEnabled ? 'debug' : 'info');
if (!isUdpEnabled) {
args.push('-dnsFallback');
Expand Down Expand Up @@ -287,6 +290,7 @@ async function checkConnectivity(config: ShadowsocksSessionConfig) {
args.push('-proxyPort', `${config.port}`);
args.push('-proxyPassword', config.password || '');
args.push('-proxyCipher', config.method || '');
args.push('-proxyPrefix', encodeURI(config.prefix || ''));
fortuna marked this conversation as resolved.
Show resolved Hide resolved
// Checks connectivity and exits with an error code as defined in `errors.ErrorCode`
// -tun* and -dnsFallback options have no effect on this mode.
args.push('-checkConnectivity');
Expand All @@ -296,7 +300,7 @@ async function checkConnectivity(config: ShadowsocksSessionConfig) {
await exec(pathToEmbeddedBinary('outline-go-tun2socks', 'tun2socks'), args);
} catch (e) {
console.error(`connectivity check failed: ${e}`);
const code = e.status;
const code = e.code;
if (code === errors.ErrorCode.UDP_RELAY_NOT_ENABLED) {
// Don't treat lack of UDP support as an error, relay to the caller.
return false;
Expand Down
3 changes: 2 additions & 1 deletion src/electron/routing_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ export class RoutingDaemon {
);
}));

const initialErrorHandler = () => {
const initialErrorHandler = (err: Error) => {
console.error('Routing daemon socket setup failed', err);
this.socket = null;
reject(new SystemConfigurationException('routing daemon is not running'));
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function staticKeyToShadowsocksSessionConfig(staticKey: string): Shadowso
port: config.port.data,
method: config.method.data,
password: config.password.data,
prefix: config.extra['prefix'],
};
} catch (error) {
throw new errors.ServerAccessKeyInvalid(error.message || 'Failed to parse static access key.');
Expand All @@ -38,19 +39,21 @@ export function staticKeyToShadowsocksSessionConfig(staticKey: string): Shadowso
function parseShadowsocksSessionConfigJson(maybeJsonText: string): ShadowsocksSessionConfig | null {
let sessionConfig;
try {
const {method, password, server: host, server_port: port} = JSON.parse(maybeJsonText);
const {method, password, server: host, server_port: port, extra} = JSON.parse(maybeJsonText);
fortuna marked this conversation as resolved.
Show resolved Hide resolved

sessionConfig = {
method,
password,
host,
port,
prefix: extra['prefix'],
};
} catch (_) {
// It's not JSON, so return null.
return null;
}

// These are the mandatory keys.
for (const key of ['method', 'password', 'host', 'port']) {
if (sessionConfig && !sessionConfig[key]) {
throw new errors.ServerAccessKeyInvalid(
Expand Down
8 changes: 7 additions & 1 deletion src/www/app/outline_server_repository/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@ function staticKeysMatch(a: string, b: string): boolean {
try {
const l = staticKeyToShadowsocksSessionConfig(a);
const r = staticKeyToShadowsocksSessionConfig(b);
return l.host === r.host && l.port === r.port && l.password === r.password && l.method === r.method;
return (
l.host === r.host &&
l.port === r.port &&
l.password === r.password &&
l.method === r.method &&
l.prefix == r.prefix
fortuna marked this conversation as resolved.
Show resolved Hide resolved
);
} catch (e) {
console.debug(`failed to parse access key for comparison`);
}
Expand Down
1 change: 1 addition & 0 deletions src/www/app/tunnel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface ShadowsocksSessionConfig {
port?: number;
password?: string;
method?: string;
prefix?: string;
}

export const enum TunnelStatus {
Expand Down
Binary file not shown.
Binary file not shown.
Binary file modified third_party/outline-go-tun2socks/android/jni/x86/libgojni.so
Binary file not shown.
Binary file not shown.
Binary file modified third_party/outline-go-tun2socks/android/tun2socks.aar
Binary file not shown.
Binary file modified third_party/outline-go-tun2socks/linux/tun2socks
Binary file not shown.
Binary file modified tools/outline_proxy_controller/dist/OutlineProxyController
Binary file not shown.