forked from Jigsaw-Code/Intra
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Ben Schwartz
committed
Aug 16, 2019
1 parent
583b583
commit d220d31
Showing
15 changed files
with
293 additions
and
55 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
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
93 changes: 93 additions & 0 deletions
93
Android/app/src/main/java/app/intra/net/socks/GoIntraListener.java
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,93 @@ | ||
/* | ||
Copyright 2019 Jigsaw Operations LLC | ||
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 | ||
https://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 app.intra.net.socks; | ||
|
||
import android.content.Context; | ||
import android.os.Bundle; | ||
import app.intra.sys.Names; | ||
import com.google.firebase.analytics.FirebaseAnalytics; | ||
import intra.RetryStats; | ||
import intra.TCPSocketSummary; | ||
import intra.UDPSocketSummary; | ||
|
||
/** | ||
* This is a callback class that is passed to our go-tun2socks code. Go calls this class's methods | ||
* when a socket has concluded, with performance metrics for that socket, and this class forwards | ||
* those metrics to Firebase. | ||
*/ | ||
public class GoIntraListener implements tunnel.IntraListener { | ||
|
||
// UDP is often used for one-off messages and pings. The relative overhead of reporting metrics | ||
// on these short messages would be large, so we only report metrics on sockets that transfer at | ||
// least this many bytes. | ||
private static final int UDP_THRESHOLD_BYTES = 10000; | ||
|
||
private final Context context; | ||
GoIntraListener(Context context) { | ||
this.context = context; | ||
} | ||
|
||
@Override | ||
public void onTCPSocketClosed(TCPSocketSummary summary) { | ||
// We had a functional socket long enough to record statistics. | ||
// Report the BYTES event : | ||
// - UPLOAD: total bytes uploaded over the lifetime of a socket | ||
// - DOWNLOAD: total bytes downloaded | ||
// - PORT: TCP port number (i.e. protocol type) | ||
// - TCP_HANDSHAKE_MS: TCP handshake latency in milliseconds | ||
// - DURATION: socket lifetime in seconds | ||
// - TODO: FIRST_BYTE_MS: Time between socket open and first byte from server, in milliseconds. | ||
|
||
Bundle bytesEvent = new Bundle(); | ||
bytesEvent.putLong(Names.UPLOAD.name(), summary.getUploadBytes()); | ||
bytesEvent.putLong(Names.DOWNLOAD.name(), summary.getDownloadBytes()); | ||
bytesEvent.putInt(Names.PORT.name(), summary.getServerPort()); | ||
bytesEvent.putInt(Names.TCP_HANDSHAKE_MS.name(), summary.getSynack()); | ||
bytesEvent.putInt(Names.DURATION.name(), summary.getDuration()); | ||
FirebaseAnalytics.getInstance(context).logEvent(Names.BYTES.name(), bytesEvent); | ||
|
||
RetryStats retry = summary.getRetry(); | ||
if (retry != null) { | ||
// Prepare an EARLY_RESET event to collect metrics on success rates for splitting: | ||
// - BYTES : Amount uploaded before reset | ||
// - CHUNKS : Number of upload writes before reset | ||
// - TIMEOUT : Whether the initial connection failed with a timeout. | ||
// - SPLIT : Number of bytes included in the first retry segment | ||
// - RETRY : 1 if retry succeeded, otherwise 0 | ||
Bundle resetEvent = new Bundle(); | ||
resetEvent.putInt(Names.BYTES.name(), retry.getBytes()); | ||
resetEvent.putInt(Names.CHUNKS.name(), retry.getChunks()); | ||
resetEvent.putInt(Names.TIMEOUT.name(), retry.getTimeout() ? 1 : 0); | ||
resetEvent.putInt(Names.SPLIT.name(), retry.getSplit()); | ||
boolean success = summary.getDownloadBytes() > 0; | ||
resetEvent.putInt(Names.RETRY.name(), success ? 1 : 0); | ||
FirebaseAnalytics.getInstance(context).logEvent(Names.EARLY_RESET.name(), resetEvent); | ||
} | ||
} | ||
|
||
@Override | ||
public void onUDPSocketClosed(UDPSocketSummary summary) { | ||
long totalBytes = summary.getUploadBytes() + summary.getDownloadBytes(); | ||
if (totalBytes < UDP_THRESHOLD_BYTES) { | ||
return; | ||
} | ||
Bundle event = new Bundle(); | ||
event.putLong(Names.UPLOAD.name(), summary.getUploadBytes()); | ||
event.putLong(Names.DOWNLOAD.name(), summary.getDownloadBytes()); | ||
event.putLong(Names.DURATION.name(), summary.getDuration()); | ||
FirebaseAnalytics.getInstance(context).logEvent(Names.UDP.name(), event); | ||
} | ||
} |
147 changes: 147 additions & 0 deletions
147
Android/app/src/main/java/app/intra/net/socks/GoVpnAdapter.java
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,147 @@ | ||
/* | ||
Copyright 2019 Jigsaw Operations LLC | ||
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 | ||
https://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 app.intra.net.socks; | ||
|
||
import android.annotation.TargetApi; | ||
import android.content.Context; | ||
import android.os.Build; | ||
import android.os.ParcelFileDescriptor; | ||
import android.util.Log; | ||
import androidx.annotation.NonNull; | ||
import app.intra.net.VpnAdapter; | ||
import app.intra.net.socks.TLSProbe.Result; | ||
import app.intra.sys.IntraVpnService; | ||
import app.intra.sys.LogWrapper; | ||
import java.io.IOException; | ||
import java.util.Locale; | ||
import tun2socks.IntraTunnel; | ||
import tun2socks.Tun2socks; | ||
|
||
/** | ||
* This is a VpnAdapter that captures all traffic and routes it through a go-tun2socks instance with | ||
* custom logic for Intra. | ||
*/ | ||
public class GoVpnAdapter extends VpnAdapter { | ||
private static final String LOG_TAG = "SocksVpnAdapter"; | ||
|
||
// IPv4 VPN constants | ||
private static final String IPV4_TEMPLATE = "10.111.222.%d"; | ||
private static final int IPV4_PREFIX_LENGTH = 24; | ||
|
||
// The VPN service and tun2socks must agree on the layout of the network. By convention, we | ||
// assign the following values to the final byte of an address within a subnet. | ||
private enum LanIp { | ||
GATEWAY(1), ROUTER(2), DNS(3); | ||
|
||
// Value of the final byte, to be substituted into the template. | ||
private final int value; | ||
|
||
LanIp(int value) { | ||
this.value = value; | ||
} | ||
|
||
String make(String template) { | ||
return String.format(Locale.ROOT, template, value); | ||
} | ||
} | ||
|
||
// Service context in which the VPN is running. | ||
private final Context context; | ||
|
||
// DNS resolver running on localhost. | ||
private final LocalhostResolver resolver; | ||
|
||
// TUN device representing the VPN. | ||
private final ParcelFileDescriptor tunFd; | ||
|
||
// The Intra session object from go-tun2socks. Initially null. | ||
private IntraTunnel tunnel; | ||
|
||
public static GoVpnAdapter establish(@NonNull IntraVpnService vpnService) { | ||
LocalhostResolver resolver = LocalhostResolver.get(vpnService); | ||
if (resolver == null) { | ||
return null; | ||
} | ||
ParcelFileDescriptor tunFd = establishVpn(vpnService); | ||
if (tunFd == null) { | ||
return null; | ||
} | ||
return new GoVpnAdapter(vpnService, tunFd, resolver); | ||
} | ||
|
||
private GoVpnAdapter(Context context, ParcelFileDescriptor tunFd, LocalhostResolver resolver) { | ||
this.context = context; | ||
this.resolver = resolver; | ||
this.tunFd = tunFd; | ||
} | ||
|
||
@Override | ||
public synchronized void start() { | ||
resolver.start(); | ||
|
||
// VPN parameters | ||
final String fakeDnsIp = LanIp.DNS.make(IPV4_TEMPLATE); | ||
final String fakeDns = fakeDnsIp + ":" + DNS_DEFAULT_PORT; | ||
|
||
Result r = TLSProbe.run(context); | ||
LogWrapper.log(Log.INFO, LOG_TAG, "TLS probe result: " + r.name()); | ||
boolean alwaysSplitHttps = r == Result.TLS_FAILED; | ||
|
||
String trueDns = resolver.getAddress().toString().substring(1); | ||
GoIntraListener listener = new GoIntraListener(context); | ||
|
||
try { | ||
LogWrapper.log(Log.INFO, LOG_TAG, "Starting go-tun2socks"); | ||
tunnel = Tun2socks.connectIntraTunnel(tunFd.getFd(), fakeDns, trueDns, trueDns, alwaysSplitHttps, listener); | ||
} catch (Exception e) { | ||
LogWrapper.logException(e); | ||
tunnel = null; | ||
close(); | ||
} | ||
} | ||
|
||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) | ||
private static ParcelFileDescriptor establishVpn(IntraVpnService vpnService) { | ||
try { | ||
return vpnService.newBuilder() | ||
.setSession("Intra go-tun2socks VPN") | ||
.setMtu(VPN_INTERFACE_MTU) | ||
.addAddress(LanIp.GATEWAY.make(IPV4_TEMPLATE), IPV4_PREFIX_LENGTH) | ||
.addRoute("0.0.0.0", 0) | ||
.addDnsServer(LanIp.DNS.make(IPV4_TEMPLATE)) | ||
.addDisallowedApplication(vpnService.getPackageName()) | ||
.establish(); | ||
} catch (Exception e) { | ||
LogWrapper.logException(e); | ||
return null; | ||
} | ||
} | ||
|
||
@Override | ||
public synchronized void close() { | ||
if (tunnel != null) { | ||
tunnel.disconnect(); | ||
} | ||
try { | ||
tunFd.close(); | ||
} catch (IOException e) { | ||
LogWrapper.logException(e); | ||
} | ||
if (resolver != null) { | ||
resolver.shutdown(); | ||
} | ||
} | ||
} |
Oops, something went wrong.