mobileproxy

package
v0.0.0-...-6e3e411 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 20, 2024 License: Apache-2.0 Imports: 15 Imported by: 0

README

MobileProxy: Local Proxy Library for Mobile Apps

This package enables the use Go Mobile to generate a mobile library to run a local proxy and configure your app networking libraries.

Content app without MobileProxy: image

Content app with MobileProxy: image

The integration typically consists of the following steps:

  1. Build the mobile native library using Go Mobile.
  2. Add the library to your application.
  3. Configure and run MobileProxy within your app.
  4. Update your networking code to proxy traffic through the local MobileProxy.

Build the Go Mobile binaries with go build

From the x/ directory:

go build -o "$(pwd)/out/" golang.org/x/mobile/cmd/gomobile golang.org/x/mobile/cmd/gobind

Build the iOS and Android libraries with gomobile bind

PATH="$(pwd)/out:$PATH" gomobile bind -ldflags='-s -w' -target=ios -iosversion=11.0 -o "$(pwd)/out/mobileproxy.xcframework" github.com/Jigsaw-Code/outline-sdk/x/mobileproxy
PATH="$(pwd)/out:$PATH" gomobile bind -ldflags='-s -w' -target=android -androidapi=21 -o "$(pwd)/out/mobileproxy.aar" github.com/Jigsaw-Code/outline-sdk/x/mobileproxy

Note: Gomobile expects gobind to be in the PATH, that's why we need to prebuild it, and set up the PATH accordingly.

The -ldflags='-s -w' flag strips debug symbols to reduce the size of the output library.

See our Github Test Action for how we build the Mobileproxy in our tests.

Sample iOS generated Code

The header file below is an example of the Objective-C interface that Go Mobile generates.

Warning: this example may diverge from what is actually generated by the current code. Use the coed you generate instead.

Mobileproxy.objc.h:

// Objective-C API for talking to github.com/Jigsaw-Code/outline-sdk/x/mobileproxy Go package.
//   gobind -lang=objc github.com/Jigsaw-Code/outline-sdk/x/mobileproxy
//
// File is generated by gobind. Do not edit.

#ifndef __Mobileproxy_H__
#define __Mobileproxy_H__

@import Foundation;
#include "ref.h"
#include "Universe.objc.h"


@class MobileproxyProxy;
@class MobileproxyStreamDialer;
@class MobileproxyStringList;
@protocol MobileproxyLogWriter;
@class MobileproxyLogWriter;

@protocol MobileproxyLogWriter <NSObject>
- (BOOL)writeString:(NSString* _Nullable)s n:(long* _Nullable)n error:(NSError* _Nullable* _Nullable)error;
@end

/**
 * Proxy enables you to get the actual address bound by the server and stop the service when no longer needed.
 */
@interface MobileproxyProxy : NSObject <goSeqRefInterface> {
}
@property(strong, readonly) _Nonnull id _ref;

- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
- (nonnull instancetype)init;
/**
 * Address returns the IP and port the server is bound to.
 */
- (NSString* _Nonnull)address;
/**
 * Host returns the IP the server is bound to.
 */
- (NSString* _Nonnull)host;
/**
 * Port returns the port the server is bound to.
 */
- (long)port;
/**
 * Stop gracefully stops the proxy service, waiting for at most timeout seconds before forcefully closing it.
The function takes a timeoutSeconds number instead of a [time.Duration] so it's compatible with Go Mobile.
 */
- (void)stop:(long)timeoutSeconds;
@end

/**
 * StreamDialer encapsulates the logic to create stream connections (like TCP).
 */
@interface MobileproxyStreamDialer : NSObject <goSeqRefInterface> {
}
@property(strong, readonly) _Nonnull id _ref;

- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
/**
 * NewStreamDialerFromConfig creates a [StreamDialer] based on the given config.
The config format is specified in https://pkg.go.dev/github.com/Jigsaw-Code/outline-sdk/x/config#hdr-Config_Format.
 */
- (nullable instancetype)initFromConfig:(NSString* _Nullable)transportConfig;
// skipped field StreamDialer.StreamDialer with unsupported type: github.com/Jigsaw-Code/outline-sdk/transport.StreamDialer

// skipped method StreamDialer.DialStream with unsupported parameter or return types

@end

/**
 * StringList allows us to pass a list of strings to the Go Mobile functions, since Go Mobiule doesn't
support slices as parameters.
 */
@interface MobileproxyStringList : NSObject <goSeqRefInterface> {
}
@property(strong, readonly) _Nonnull id _ref;

- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
- (nonnull instancetype)init;
/**
 * Append adds the string value to the end of the list.
 */
- (void)append:(NSString* _Nullable)value;
@end

/**
 * NewListFromLines creates a StringList by splitting the input string on new lines.
 */
FOUNDATION_EXPORT MobileproxyStringList* _Nullable MobileproxyNewListFromLines(NSString* _Nullable lines);

/**
 * NewSmartStreamDialer automatically selects a DNS and TLS strategy to use, and return a [StreamDialer]
that will use the selected strategy.
It uses testDomain to find a strategy that works when accessing those domains.
The strategies to search are given in the searchConfig. An example can be found in
https://github.com/Jigsaw-Code/outline-sdk/x/examples/smart-proxy/config.json
 */
FOUNDATION_EXPORT MobileproxyStreamDialer* _Nullable MobileproxyNewSmartStreamDialer(MobileproxyStringList* _Nullable testDomains, NSString* _Nullable searchConfig, id<MobileproxyLogWriter> _Nullable logWriter, NSError* _Nullable* _Nullable error);

/**
 * NewStderrLogWriter creates a [LogWriter] that writes to the standard error output.
 */
FOUNDATION_EXPORT id<MobileproxyLogWriter> _Nullable MobileproxyNewStderrLogWriter(void);

/**
 * NewStreamDialerFromConfig creates a [StreamDialer] based on the given config.
The config format is specified in https://pkg.go.dev/github.com/Jigsaw-Code/outline-sdk/x/config#hdr-Config_Format.
 */
FOUNDATION_EXPORT MobileproxyStreamDialer* _Nullable MobileproxyNewStreamDialerFromConfig(NSString* _Nullable transportConfig, NSError* _Nullable* _Nullable error);

/**
 * RunProxy runs a local web proxy that listens on localAddress, and handles proxy requests by
establishing connections to requested destination using the [StreamDialer].
 */
FOUNDATION_EXPORT MobileproxyProxy* _Nullable MobileproxyRunProxy(NSString* _Nullable localAddress, MobileproxyStreamDialer* _Nullable dialer, NSError* _Nullable* _Nullable error);

@class MobileproxyLogWriter;

/**
 * LogWriter is used as a sink for logging.
 */
@interface MobileproxyLogWriter : NSObject <goSeqRefInterface, MobileproxyLogWriter> {
}
@property(strong, readonly) _Nonnull id _ref;

- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
- (BOOL)writeString:(NSString* _Nullable)s n:(long* _Nullable)n error:(NSError* _Nullable* _Nullable)error;
@end

#endif
Sample Android generated Code

The files below are examples of the Java interface that Go Mobile generates.

Warning: this example may diverge from what is actually generated by the current code. Use the coed you generate instead.

LogWriter.java:

// Code generated by gobind. DO NOT EDIT.

// Java class mobileproxy.LogWriter is a proxy for talking to a Go program.
//
//   autogenerated by gobind -lang=java github.com/Jigsaw-Code/outline-sdk/x/mobileproxy
package mobileproxy;

import go.Seq;

/**
 * LogWriter is used as a sink for logging.
 */
public interface LogWriter {
	public long writeString(String s) throws Exception;
	
}

StreamDialer.java:

// Code generated by gobind. DO NOT EDIT.

// Java class mobileproxy.StreamDialer is a proxy for talking to a Go program.
//
//   autogenerated by gobind -lang=java github.com/Jigsaw-Code/outline-sdk/x/mobileproxy
package mobileproxy;

import go.Seq;

/**
 * StreamDialer encapsulates the logic to create stream connections (like TCP).
 */
public final class StreamDialer implements Seq.Proxy {
	static { Mobileproxy.touch(); }
	
	private final int refnum;
	
	@Override public final int incRefnum() {
	      Seq.incGoRef(refnum, this);
	      return refnum;
	}
	
	/**
	 * NewStreamDialerFromConfig creates a [StreamDialer] based on the given config.
	The config format is specified in https://pkg.go.dev/github.com/Jigsaw-Code/outline-sdk/x/config#hdr-Config_Format.
	 */
	public StreamDialer(String transportConfig) {
		this.refnum = __NewStreamDialerFromConfig(transportConfig);
		Seq.trackGoRef(refnum, this);
	}
	
	private static native int __NewStreamDialerFromConfig(String transportConfig);
	
	StreamDialer(int refnum) { this.refnum = refnum; Seq.trackGoRef(refnum, this); }
	
	// skipped field StreamDialer.StreamDialer with unsupported type: github.com/Jigsaw-Code/outline-sdk/transport.StreamDialer
	
	// skipped method StreamDialer.DialStream with unsupported parameter or return types
	
	@Override public boolean equals(Object o) {
		if (o == null || !(o instanceof StreamDialer)) {
		    return false;
		}
		StreamDialer that = (StreamDialer)o;
		// skipped field StreamDialer.StreamDialer with unsupported type: github.com/Jigsaw-Code/outline-sdk/transport.StreamDialer
		
		return true;
	}
	
	@Override public int hashCode() {
	    return java.util.Arrays.hashCode(new Object[] {});
	}
	
	@Override public String toString() {
		StringBuilder b = new StringBuilder();
		b.append("StreamDialer").append("{");
		return b.append("}").toString();
	}
}

Mobileproxy.java:

// Code generated by gobind. DO NOT EDIT.

// Java class mobileproxy.Mobileproxy is a proxy for talking to a Go program.
//
//   autogenerated by gobind -lang=java github.com/Jigsaw-Code/outline-sdk/x/mobileproxy
package mobileproxy;

import go.Seq;

public abstract class Mobileproxy {
	static {
		Seq.touch(); // for loading the native library
		_init();
	}
	
	private Mobileproxy() {} // uninstantiable
	
	// touch is called from other bound packages to initialize this package
	public static void touch() {}
	
	private static native void _init();
	
	private static final class proxyLogWriter implements Seq.Proxy, LogWriter {
		private final int refnum;
		
		@Override public final int incRefnum() {
		      Seq.incGoRef(refnum, this);
		      return refnum;
		}
		
		proxyLogWriter(int refnum) { this.refnum = refnum; Seq.trackGoRef(refnum, this); }
		
		public native long writeString(String s) throws Exception;
	}
	
	
	/**
	 * NewListFromLines creates a StringList by splitting the input string on new lines.
	 */
	public static native StringList newListFromLines(String lines);
	/**
	 * NewSmartStreamDialer automatically selects a DNS and TLS strategy to use, and return a [StreamDialer]
	that will use the selected strategy.
	It uses testDomain to find a strategy that works when accessing those domains.
	The strategies to search are given in the searchConfig. An example can be found in
	https://github.com/Jigsaw-Code/outline-sdk/x/examples/smart-proxy/config.json
	 */
	public static native StreamDialer newSmartStreamDialer(StringList testDomains, String searchConfig, LogWriter logWriter) throws Exception;
	/**
	 * NewStderrLogWriter creates a [LogWriter] that writes to the standard error output.
	 */
	public static native LogWriter newStderrLogWriter();
	/**
	 * NewStreamDialerFromConfig creates a [StreamDialer] based on the given config.
	The config format is specified in https://pkg.go.dev/github.com/Jigsaw-Code/outline-sdk/x/config#hdr-Config_Format.
	 */
	public static native StreamDialer newStreamDialerFromConfig(String transportConfig) throws Exception;
	/**
	 * RunProxy runs a local web proxy that listens on localAddress, and handles proxy requests by
	establishing connections to requested destination using the [StreamDialer].
	 */
	public static native Proxy runProxy(String localAddress, StreamDialer dialer) throws Exception;
}

Proxy.java:

// Code generated by gobind. DO NOT EDIT.

// Java class mobileproxy.Proxy is a proxy for talking to a Go program.
//
//   autogenerated by gobind -lang=java github.com/Jigsaw-Code/outline-sdk/x/mobileproxy
package mobileproxy;

import go.Seq;

/**
 * Proxy enables you to get the actual address bound by the server and stop the service when no longer needed.
 */
public final class Proxy implements Seq.Proxy {
	static { Mobileproxy.touch(); }
	
	private final int refnum;
	
	@Override public final int incRefnum() {
	      Seq.incGoRef(refnum, this);
	      return refnum;
	}
	
	Proxy(int refnum) { this.refnum = refnum; Seq.trackGoRef(refnum, this); }
	
	public Proxy() { this.refnum = __New(); Seq.trackGoRef(refnum, this); }
	
	private static native int __New();
	
	/**
	 * Address returns the IP and port the server is bound to.
	 */
	public native String address();
	/**
	 * Host returns the IP the server is bound to.
	 */
	public native String host();
	/**
	 * Port returns the port the server is bound to.
	 */
	public native long port();
	/**
	 * Stop gracefully stops the proxy service, waiting for at most timeout seconds before forcefully closing it.
	The function takes a timeoutSeconds number instead of a [time.Duration] so it&#39;s compatible with Go Mobile.
	 */
	public native void stop(long timeoutSeconds);
	@Override public boolean equals(Object o) {
		if (o == null || !(o instanceof Proxy)) {
		    return false;
		}
		Proxy that = (Proxy)o;
		return true;
	}
	
	@Override public int hashCode() {
	    return java.util.Arrays.hashCode(new Object[] {});
	}
	
	@Override public String toString() {
		StringBuilder b = new StringBuilder();
		b.append("Proxy").append("{");
		return b.append("}").toString();
	}
}

StringList.java:

// Code generated by gobind. DO NOT EDIT.

// Java class mobileproxy.StringList is a proxy for talking to a Go program.
//
//   autogenerated by gobind -lang=java github.com/Jigsaw-Code/outline-sdk/x/mobileproxy
package mobileproxy;

import go.Seq;

/**
 * StringList allows us to pass a list of strings to the Go Mobile functions, since Go Mobiule doesn&#39;t
support slices as parameters.
 */
public final class StringList implements Seq.Proxy {
	static { Mobileproxy.touch(); }
	
	private final int refnum;
	
	@Override public final int incRefnum() {
	      Seq.incGoRef(refnum, this);
	      return refnum;
	}
	
	StringList(int refnum) { this.refnum = refnum; Seq.trackGoRef(refnum, this); }
	
	public StringList() { this.refnum = __New(); Seq.trackGoRef(refnum, this); }
	
	private static native int __New();
	
	/**
	 * Append adds the string value to the end of the list.
	 */
	public native void append(String value);
	@Override public boolean equals(Object o) {
		if (o == null || !(o instanceof StringList)) {
		    return false;
		}
		StringList that = (StringList)o;
		return true;
	}
	
	@Override public int hashCode() {
	    return java.util.Arrays.hashCode(new Object[] {});
	}
	
	@Override public String toString() {
		StringBuilder b = new StringBuilder();
		b.append("StringList").append("{");
		return b.append("}").toString();
	}
}

Add the library to your mobile project

To add the library to your mobile project, see Go Mobile's Building and deploying to iOS and Building and deploying to Android.

Using the basic local proxy forwarder

You need to call the RunProxy function passing the local address to use, and the transport configuration.

On Android, you can have the following Kotlin code:

// Use port zero to let the system pick an open port for you.
val dialer = StreamDialer("split:3")

val proxy = Mobileproxy.runProxy("localhost:0", dialer)
// Configure your networking library using proxy.host() and proxy.port() or proxy.address().
// ...
// Stop running the proxy.
proxy.stop()

Using the smart local proxy forwarder ("Smart Proxy")

The Smart Proxy can automatically try multiple strategies to unblock access to the test domains you specify. You need to specify a strategy config in JSON format (example).

On Android, the Kotlin code would look like this:

// Use port zero to let the system pick an open port for you.
val testDomains = Mobileproxy.newListFromLines("www.youtube.com\ni.ytimg.com")
val strategiesConfig = "..."  // Config JSON.
val dialer = Mobileproxy.newSmartStreamDialer(testDomains, strategiesConfig, Mobileproxy.newStderrLogWriter())

val proxy = Mobileproxy.runProxy("localhost:0", dialer)
// Configure your networking library using proxy.host() and proxy.port() or proxy.address().
// ...
// Stop running the proxy.
proxy.stop()

Configure your HTTP client or networking library

You need to configure your networking library to use the local proxy. How you do it depends on the networking library you are using.

Dart/Flutter HttpClient

Set the proxy with the HttpClient.findProxy function.

Dart example:

  HttpClient client = HttpClient();
  client.findProxy = (Uri uri) {
    return "PROXY " + proxy.address();
  };
OkHttp (Android only)

Set the proxy with OkHttpClient.Builder.proxy.

Kotlin example:

val proxyConfig = Proxy(Proxy.Type.HTTP, InetSocketAddress(proxy.host(), proxy.port()))
val client = OkHttpClient.Builder().proxy(proxyConfig).build()
JVM (Java, Kotlin)

In the JVM, you can configure the proxy to use with system properties:

System.setProperty("http.proxyHost", proxy.host())
System.setProperty("http.proxyPort", String.valueOf(proxy.port()))
System.setProperty("https.proxyHost", proxy.host())
System.setProperty("https.proxyPort", String.valueOf(proxy.port()))

Note that this may not fully work on Android, since it will only affect the JVM, not native code. You should also make sure you set this early in your code.

Web View
Android

On Android, you can easily apply a proxy configuration to all the web views in your application with the androidx.webview library like so:

ProxyController.getInstance()
		.setProxyOverride(
				ProxyConfig.Builder()
						.addProxyRule(this.proxy!!.address())
						.build(),
				{}, // execution context for the following callback - do anything needed here once the proxy is applied, like refreshing web views 
				{} // callback to be called once the ProxyConfig is applied
		)
iOS

As of iOS 17, you can add a proxy configuration to a WKWebView via its WKWebsiteDataStore property.

let configuration = WKWebViewConfiguration()

let endpoint = NWEndpoint.hostPort(
		host: NWEndpoint.Host(proxyHost),
		port: NWEndpoint.Port(proxyPort)!
)
let proxyConfig = ProxyConfiguration.init(httpCONNECTProxy: endpoint)

let websiteDataStore = WKWebsiteDataStore.default()
websiteDataStore.proxyConfigurations = [proxyConfig]

// Other webview configuration options... see https://developer.apple.com/documentation/webkit/wkwebviewconfiguration

let webview = WKWebView(
	configuration: configuration,
)

// use this webview as you would normally!

Clean up

rm -rf ./out/

Documentation

Overview

Package mobileproxy provides convenience utilities to help applications run a local proxy and use that to configure their networking libraries.

This package is suitable for use with Go Mobile, making it a convenient way to integrate with mobile apps.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type LogWriter

type LogWriter io.StringWriter

LogWriter is used as a sink for logging.

func NewStderrLogWriter

func NewStderrLogWriter() LogWriter

NewStderrLogWriter creates a LogWriter that writes to the standard error output.

type Proxy

type Proxy struct {
	// contains filtered or unexported fields
}

Proxy enables you to get the actual address bound by the server and stop the service when no longer needed.

func RunProxy

func RunProxy(localAddress string, dialer *StreamDialer) (*Proxy, error)

RunProxy runs a local web proxy that listens on localAddress, and handles proxy requests by establishing connections to requested destination using the StreamDialer.

func (*Proxy) AddURLProxy

func (p *Proxy) AddURLProxy(path string, dialer *StreamDialer)

AddURLProxy sets up a URL-based proxy handler that activates when an incoming HTTP request matches the specified path prefix. The pattern must represent a path segment, which is checked against the path of the incoming request.

This function is particularly useful for libraries or components that accept URLs but do not support proxy configuration directly. By leveraging AddURLProxy, such components can route requests through a proxy by constructing URLs in the format "http://${HOST}:${PORT}/${PATH}/${URL}", where "${URL}" is the target resource. For instance, using "http://localhost:8080/proxy/https://example.com" routes the request for "https://example.com" through a proxy at "http://localhost:8080/proxy".

The path should start with a forward slash ('/') for clarity, but one will be added if missing.

The function associates the given 'dialer' with the specified 'path', allowing different dialers to be used for different path-based proxies within the same application in the future. currently we only support one URL proxy.

func (*Proxy) Address

func (p *Proxy) Address() string

Address returns the IP and port the server is bound to.

func (*Proxy) Host

func (p *Proxy) Host() string

Host returns the IP the server is bound to.

func (*Proxy) Port

func (p *Proxy) Port() int

Port returns the port the server is bound to.

func (*Proxy) Stop

func (p *Proxy) Stop(timeoutSeconds int)

Stop gracefully stops the proxy service, waiting for at most timeout seconds before forcefully closing it. The function takes a timeoutSeconds number instead of a time.Duration so it's compatible with Go Mobile.

type StreamDialer

type StreamDialer struct {
	transport.StreamDialer
}

StreamDialer encapsulates the logic to create stream connections (like TCP).

func NewSmartStreamDialer

func NewSmartStreamDialer(testDomains *StringList, searchConfig string, logWriter LogWriter) (*StreamDialer, error)

NewSmartStreamDialer automatically selects a DNS and TLS strategy to use, and returns a StreamDialer that will use the selected strategy. It uses testDomains to find a strategy that works when accessing those domains. The strategies to search are given in the searchConfig. An example can be found in https://github.com/Jigsaw-Code/outline-sdk/x/examples/smart-proxy/config.json

func NewStreamDialerFromConfig

func NewStreamDialerFromConfig(transportConfig string) (*StreamDialer, error)

NewStreamDialerFromConfig creates a StreamDialer based on the given config. The config format is specified in https://pkg.go.dev/github.com/Jigsaw-Code/outline-sdk/x/config#hdr-Config_Format.

type StringList

type StringList struct {
	// contains filtered or unexported fields
}

StringList allows us to pass a list of strings to the Go Mobile functions, since Go Mobile doesn't support slices as parameters.

func NewListFromLines

func NewListFromLines(lines string) *StringList

NewListFromLines creates a StringList by splitting the input string on new lines.

func (*StringList) Append

func (l *StringList) Append(value string)

Append adds the string value to the end of the list.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL