Service to start and stop a Snowflake Proxy based off of IPtProxy. This is a work in progress at the moment...
In your app's AndroidManifest.xml
declare SnowflakeProxyService
within the <application>
tag:
<service android:name="com.bimmm.snowflakeproxyservice.SnowflakeProxyService"/>
To Use SnowflakeProxyService
in your Activity
create an Intent
and start it as follows:
val intent = Intent(this, SnowflakeProxyService::class.java)
.setAction(SnowflakeProxyService.ACTION_START)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
startForegroundService(intent)
else
startService(intent)
This will start the proxy service in your application. After starting the service, in your app, you can use BroadcastReceiver
to listen to events from SnowflakeProxyService
. It may broadcast SnowflakeProxyService.ACTION_CLIENT_CONNECTED
, SnowflakeProxyService.ACTION_PAUSING
and SnowflakeProxyService.ACTION_RESUMING
private val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) = when (intent?.action) {
SnowflakeProxyService.ACTION_CLIENT_CONNECTED -> {
// a client used the proxy to evade censorship, you can ask the Service to see how many clients used your proxy since starting it
val count = intent.getIntExtra(SnowflakeProxyService.EXTRA_CLIENT_CONNECTED_COUNT, -1)
}
SnowflakeProxyService.ACTION_PAUSING -> {
// the snowflake proxy is pausing, the reason it is pausing can be obtained as follows:
val reason = intent.getStringExtra(SnowflakeProxyService.EXTRA_PAUSING_REASON)
}
SnowflakeProxyService.ACTION_RESUMING -> {
// the snowflake proxy is starting up
}
else -> {}
}
}
Typically in your Activity
's onCreate(Bundle?)
method, your app specifies wihch events, if any, it wants to listen to:
LocalBroadcastManager.getInstance(this).apply {
registerReceiver(receiver, IntentFilter(SnowflakeProxyService.ACTION_CLIENT_CONNECTED))
registerReceiver(receiver, IntentFilter(SnowflakeProxyService.ACTION_PAUSING))
registerReceiver(receiver, IntentFilter(SnowflakeProxyService.ACTION_RESUMING))
}
SnowflakeProxyService
can be configured to pause the snowflake proxy. This means that the service will still continue to be running, but that your app won't act as a snowflake. Currently SnowflakeProxyService
can be paused if the device loses connection to power and/or if the device loses its network connection to an unmetered network connection.
The above example can be extended to pause the SnowflakeProxyService
by adding in these Intent
extras. In this example, your app will only function as a Snowflake proxy if the device is connected to power and an unmetered connection (typically a Wi-Fi connection):
val intent = Intent(this, SnowflakeProxyService::class.java)
.setAction(SnowflakeProxyService.ACTION_START)
.putExtra(SnowflakeProxyService.EXTRA_START_CHECK_POWER, true)
.putExtra(SnowflakeProxyService.EXTRA_START_CHECK_UNMETERED, true)
If either of these Intent
extra's are specified, SnowflakeProxyService
may emit events with the action SnowflakeProxyService.ACTION_PAUSING
. Your app may want to listen to these events and update its UI to explain why the proxy has stopped.
When the proxy resumes, an event of SnowflakeProxyService.ACTION_RESUMING
is emitted.
The idea at the moment behind pausing is for use cases where your app may be running for a long time, say when the uesr goes to bed or isn't using their Android device. In other cases, it may make sense to simply kill SnowflakeProxyService
rather than having it run in this paused state.
Your app can fully configure the underlying snowflake proxy, although by default SnowflakeProxyService
uses the defaults specified in IPtProxy. An up-to-date explanation of how snowflake functions can be found on The Tor Project's wiki.
val intent = Intent(this, SnowflakeProxyService::class.java)
.setAction(SnowflakeProxyService.ACTION_START)
// URL configuration
.putExtra(SnowflakeProxyService.EXTRA_PROXY_BROKER_URL, "https://snowflake-broker.torproject.net/")
.putExtra(SnowflakeProxyService.EXTRA_PROXY_RELAY_URL, "wss://snowflake.bamsoftware.com/")
.putExtra(SnowflakeProxyService.EXTRA_PROXY_BROKER_URL, "stun:stun.stunprotocol.org:3478")
putExtra(SnowflakeProxyService.EXTRA_PROXY_NAT_PROBE_URL, "https://snowflake-broker.torproject.net:8443/probe")
// Proxy configuration
.putExtra(SnowflakeProxyService.EXTRA_PROXY_LOG_FILE_NAME, "mylog.log") // log file, default is STDERR
.putExtra(SnowflakeProxyService.EXTRA_PROXY_CAPACITY, 10) // number of concurrent clients
.putExtra(SnowflakeProxyService.EXTRA_PROXY_USE_UNSAFE_LOGGING, false) // scrub logs
SnowflakeProxyService
can optionally display a Toast
to your users whenever someone uses your snowflake to bypass censorship.
val intent = Intent(this, SnowflakeProxyService::class.java)
.setAction(SnowflakeProxyService.ACTION_START)
.putExtra(SnowflakeProxyService.EXTRA_START_SHOW_TOAST, true)
// this displays: ❄️ Your snowflake proxy helped someone circumvent censorship ❄️
// unless you specify your own message:
.putExtra(SnowflakeProxyService.EXTRA_START_TOAST_MESSAGE, "yay, someone got connected thanks to you")
Using SnowflakeProxyService
introduces the following permissions into your app:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
This is a work in progress, right now there needs to be:
- a way to package and distribute the AAR for this library
- more documentation in the code
- more polish on the sample app
- graphical assets for the snowflake notification icon
- optimization on the receivers that the proxy uses for pausing. these system resources need not be allocated unless the service uses them
- documenation on stopping the service
- the service ought to be used in a real app, will experiment with this in Orbot
- a way for the handful of string resources this library uses to be translated
- refactor the library to a package name that's not
com.bimm.*...
- use a more forgiving minSdk version, right now there are just IDE defaults...