updated android/osx project and example project structure
@@ -0,0 +1,13 @@
|
||||
package com.zerotier.one;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
||||
public ApplicationTest() {
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.zerotier.one" >
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
|
||||
<application
|
||||
android:debuggable="true"
|
||||
android:name=".AnalyticsApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme" >
|
||||
<service
|
||||
android:name=".service.ZeroTierOneService"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE" >
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<activity
|
||||
android:name=".ui.NetworkListActivity"
|
||||
android:label="@string/app_name" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.JoinNetworkActivity"
|
||||
android:label="Join Network" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.PrefsActivity"
|
||||
android:label="@string/app_name" />
|
||||
|
||||
<receiver
|
||||
android:name=".service.StartupReceiver"
|
||||
android:exported="true" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- Optionally, register AnalyticsReceiver and AnalyticsService to support background
|
||||
dispatching on non-Google Play devices -->
|
||||
<receiver android:name="com.google.android.gms.analytics.AnalyticsReceiver"
|
||||
android:enabled="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.gms.analytics.ANALYTICS_DISPATCH" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<service android:name="com.google.android.gms.analytics.AnalyticsService"
|
||||
android:enabled="true"
|
||||
android:exported="false"/>
|
||||
|
||||
<!-- Optionally, register CampaignTrackingReceiver and CampaignTrackingService to enable
|
||||
installation campaign reporting -->
|
||||
<receiver android:name="com.google.android.gms.analytics.CampaignTrackingReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.vending.INSTALL_REFERRER" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<service android:name="com.google.android.gms.analytics.CampaignTrackingService" />
|
||||
|
||||
<!--
|
||||
<receiver
|
||||
android:name=".service.NetworkStateReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
-->
|
||||
|
||||
<service
|
||||
android:name=".service.RuntimeService"
|
||||
android:enabled="true"
|
||||
android:exported="true" >
|
||||
</service>
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.version"
|
||||
android:value="@integer/google_play_services_version" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,15 @@
|
||||
package Netcon;
|
||||
|
||||
/**
|
||||
* Created by Joseph Henry on 3/14/16.
|
||||
*/
|
||||
public class ZT_SDK_Wrapper {
|
||||
public native int loadsymbols();
|
||||
|
||||
// From SDK_ServiceSetup.cpp
|
||||
public native void startOneService();
|
||||
|
||||
static {
|
||||
System.loadLibrary("ZeroTierOneJNI");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.zerotier.one;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.google.android.gms.analytics.GoogleAnalytics;
|
||||
import com.google.android.gms.analytics.HitBuilders;
|
||||
import com.google.android.gms.analytics.Tracker;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Created by Grant on 8/25/2015.
|
||||
*/
|
||||
public class AnalyticsApplication extends Application {
|
||||
private Tracker mTracker;
|
||||
|
||||
/**
|
||||
* Gets the default {@link Tracker} for this {@link Application}.
|
||||
* @return tracker
|
||||
*/
|
||||
synchronized public Tracker getDefaultTracker() {
|
||||
if (mTracker == null) {
|
||||
SharedPreferences prefs = getSharedPreferences("user", MODE_PRIVATE);
|
||||
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
String savedUUID = prefs.getString("uuid", UUID.randomUUID().toString());
|
||||
|
||||
if(savedUUID.equals(uuid)) {
|
||||
// this is a newly generated UUID. Save it
|
||||
SharedPreferences.Editor e = prefs.edit();
|
||||
e.putString("uuid", savedUUID);
|
||||
e.apply();
|
||||
}
|
||||
|
||||
GoogleAnalytics analytics = GoogleAnalytics.getInstance(this);
|
||||
// To enable debug logging use: adb shell setprop log.tag.GAv4 DEBUG
|
||||
mTracker = analytics.newTracker(R.xml.app_tracker);
|
||||
|
||||
mTracker.set("&uid", savedUUID);
|
||||
|
||||
mTracker.send(new HitBuilders.ScreenViewBuilder()
|
||||
.setNewSession()
|
||||
.build());
|
||||
}
|
||||
return mTracker;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.zerotier.one;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.android.gms.analytics.GoogleAnalytics;
|
||||
import com.google.android.gms.analytics.Tracker;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A collection of Google Analytics trackers. Fetch the tracker you need using
|
||||
* {@code AnalyticsTrackers.getInstance().get(...)}
|
||||
* <p/>
|
||||
* This code was generated by Android Studio but can be safely modified by
|
||||
* hand at this point.
|
||||
* <p/>
|
||||
* TODO: Call {@link #initialize(Context)} from an entry point in your app
|
||||
* before using this!
|
||||
*/
|
||||
public final class AnalyticsTrackers {
|
||||
|
||||
public enum Target {
|
||||
APP,
|
||||
// Add more trackers here if you need, and update the code in #get(Target) below
|
||||
}
|
||||
|
||||
private static AnalyticsTrackers sInstance;
|
||||
|
||||
public static synchronized void initialize(Context context) {
|
||||
if (sInstance != null) {
|
||||
throw new IllegalStateException("Extra call to initialize analytics trackers");
|
||||
}
|
||||
|
||||
sInstance = new AnalyticsTrackers(context);
|
||||
}
|
||||
|
||||
public static synchronized AnalyticsTrackers getInstance() {
|
||||
if (sInstance == null) {
|
||||
throw new IllegalStateException("Call initialize() before getInstance()");
|
||||
}
|
||||
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
private final Map<Target, Tracker> mTrackers = new HashMap<Target, Tracker>();
|
||||
private final Context mContext;
|
||||
|
||||
/**
|
||||
* Don't instantiate directly - use {@link #getInstance()} instead.
|
||||
*/
|
||||
private AnalyticsTrackers(Context context) {
|
||||
mContext = context.getApplicationContext();
|
||||
}
|
||||
|
||||
public synchronized Tracker get(Target target) {
|
||||
if (!mTrackers.containsKey(target)) {
|
||||
Tracker tracker;
|
||||
switch (target) {
|
||||
case APP:
|
||||
tracker = GoogleAnalytics.getInstance(mContext).newTracker(R.xml.app_tracker);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unhandled analytics target " + target);
|
||||
}
|
||||
mTrackers.put(target, tracker);
|
||||
}
|
||||
|
||||
return mTrackers.get(target);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.zerotier.one.events;
|
||||
|
||||
import com.zerotier.sdk.ResultCode;
|
||||
|
||||
/**
|
||||
* Created by Grant on 6/23/2015.
|
||||
*/
|
||||
public class ErrorEvent {
|
||||
ResultCode result;
|
||||
|
||||
public ErrorEvent(ResultCode rc) {
|
||||
result = rc;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.zerotier.one.events;
|
||||
|
||||
import com.zerotier.one.ui.JoinNetworkFragment;
|
||||
|
||||
/**
|
||||
* Created by Grant on 6/23/2015.
|
||||
*/
|
||||
public class JoinNetworkEvent {
|
||||
private long networkId;
|
||||
|
||||
public JoinNetworkEvent(long nwid) {
|
||||
networkId = nwid;
|
||||
}
|
||||
|
||||
public long getNetworkId() {
|
||||
return networkId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.zerotier.one.events;
|
||||
|
||||
/**
|
||||
* Created by Grant on 6/23/2015.
|
||||
*/
|
||||
public class LeaveNetworkEvent {
|
||||
long networkId;
|
||||
|
||||
public LeaveNetworkEvent(long nwid) {
|
||||
networkId = nwid;
|
||||
}
|
||||
|
||||
public long getNetworkId() {
|
||||
return networkId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.zerotier.one.events;
|
||||
|
||||
/**
|
||||
* Created by Grant on 8/4/2015.
|
||||
*/
|
||||
public class ManualDisconnectEvent {
|
||||
public ManualDisconnectEvent() {}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.zerotier.one.events;
|
||||
|
||||
import android.net.NetworkInfo;
|
||||
|
||||
import com.zerotier.sdk.VirtualNetworkConfig;
|
||||
|
||||
/**
|
||||
* Created by Grant on 6/23/2015.
|
||||
*/
|
||||
public class NetworkInfoReplyEvent {
|
||||
private VirtualNetworkConfig vnc;
|
||||
|
||||
public NetworkInfoReplyEvent(VirtualNetworkConfig vnc) {
|
||||
this.vnc = vnc;
|
||||
}
|
||||
|
||||
public VirtualNetworkConfig getNetworkInfo() {
|
||||
return vnc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.zerotier.one.events;
|
||||
|
||||
import com.zerotier.sdk.VirtualNetworkConfig;
|
||||
|
||||
/**
|
||||
* Created by Grant on 6/23/2015.
|
||||
*/
|
||||
public class NetworkListReplyEvent {
|
||||
private VirtualNetworkConfig[] networks;
|
||||
|
||||
public NetworkListReplyEvent(VirtualNetworkConfig[] networks) {
|
||||
this.networks = networks;
|
||||
}
|
||||
|
||||
public VirtualNetworkConfig[] getNetworkList() {
|
||||
return networks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.zerotier.one.events;
|
||||
|
||||
import android.net.Network;
|
||||
|
||||
/**
|
||||
* Created by Grant on 6/24/2015.
|
||||
*/
|
||||
public class NetworkReconfigureEvent {
|
||||
public NetworkReconfigureEvent() {}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.zerotier.one.events;
|
||||
|
||||
/**
|
||||
* Created by Grant on 6/23/2015.
|
||||
*/
|
||||
public class NetworkRemovedEvent {
|
||||
private long networkId;
|
||||
|
||||
public NetworkRemovedEvent(long nwid) {
|
||||
networkId = nwid;
|
||||
}
|
||||
|
||||
public long getNetworkId() {
|
||||
return networkId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.zerotier.one.events;
|
||||
|
||||
/**
|
||||
* Created by Grant on 7/28/2015.
|
||||
*/
|
||||
public class NodeDestroyedEvent {
|
||||
public NodeDestroyedEvent() {}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.zerotier.one.events;
|
||||
|
||||
/**
|
||||
* Created by Grant on 7/9/2015.
|
||||
*/
|
||||
public class NodeIDEvent {
|
||||
private long nodeId;
|
||||
|
||||
public NodeIDEvent(long nodeId) {
|
||||
this.nodeId = nodeId;
|
||||
}
|
||||
|
||||
public long getNodeId() {
|
||||
return nodeId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.zerotier.one.events;
|
||||
|
||||
import com.zerotier.sdk.NodeStatus;
|
||||
|
||||
/**
|
||||
* Created by Grant on 7/9/2015.
|
||||
*/
|
||||
public class NodeStatusEvent {
|
||||
private NodeStatus status;
|
||||
|
||||
public NodeStatusEvent(NodeStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public NodeStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.zerotier.one.events;
|
||||
|
||||
/**
|
||||
* Created by Grant on 6/23/2015.
|
||||
*/
|
||||
public class RequestNetworkInfoEvent {
|
||||
private long networkId;
|
||||
|
||||
public RequestNetworkInfoEvent(long nwid) {
|
||||
networkId = nwid;
|
||||
}
|
||||
|
||||
public long getNetworkId() {
|
||||
return networkId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.zerotier.one.events;
|
||||
|
||||
/**
|
||||
* Created by Grant on 6/23/2015.
|
||||
*/
|
||||
public class RequestNetworkListEvent {
|
||||
public RequestNetworkListEvent() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.zerotier.one.events;
|
||||
|
||||
/**
|
||||
* Created by Grant on 8/1/2015.
|
||||
*/
|
||||
public class RequestNodeStatusEvent {
|
||||
public RequestNodeStatusEvent() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.zerotier.one.events;
|
||||
|
||||
/**
|
||||
* Created by Grant on 7/9/2015.
|
||||
*/
|
||||
public class StopEvent {
|
||||
public StopEvent() {}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
package com.zerotier.one.service;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class ARPTable {
|
||||
public static final String TAG = "ARPTable";
|
||||
|
||||
private static final int REQUEST = 1;
|
||||
private static final int REPLY = 2;
|
||||
|
||||
private final HashMap<InetAddress, Long> inetAddressToMacAddress;
|
||||
private final HashMap<Long, InetAddress> macAddressToInetAdddress;
|
||||
|
||||
private static final long ENTRY_TIMEOUT = 120000L; // 2 minutes
|
||||
|
||||
private final ArrayList<ArpEntry> entries;
|
||||
|
||||
public ARPTable() {
|
||||
entries = new ArrayList<ArpEntry>();
|
||||
inetAddressToMacAddress = new HashMap<InetAddress, Long>();
|
||||
macAddressToInetAdddress = new HashMap<Long, InetAddress>();
|
||||
timeoutThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
timeoutThread.interrupt();
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
void setAddress(InetAddress addr, long mac) {
|
||||
synchronized (inetAddressToMacAddress) {
|
||||
inetAddressToMacAddress.put(addr, mac);
|
||||
}
|
||||
synchronized (macAddressToInetAdddress) {
|
||||
macAddressToInetAdddress.put(mac, addr);
|
||||
}
|
||||
|
||||
synchronized (entries) {
|
||||
ArpEntry entry = new ArpEntry(mac, addr);
|
||||
if(entries.contains(entry)) {
|
||||
// update the entry time
|
||||
int index = entries.indexOf(entry);
|
||||
entries.get(index).updateTime();
|
||||
} else {
|
||||
entries.add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateArpEntryTime(long mac) {
|
||||
synchronized (entries) {
|
||||
for(ArpEntry e : entries) {
|
||||
if(mac == e.getMac()) {
|
||||
e.updateTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateArpEntryTime(InetAddress addr) {
|
||||
synchronized (entries) {
|
||||
for(ArpEntry e : entries) {
|
||||
if(addr.equals(e.getAddress())) {
|
||||
e.updateTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MAC address for a given InetAddress
|
||||
*
|
||||
* @param addr address to get the MAC for
|
||||
* @return MAC address as long. -1 if the address isn't known
|
||||
*/
|
||||
long getMacForAddress(InetAddress addr) {
|
||||
synchronized (inetAddressToMacAddress) {
|
||||
if(inetAddressToMacAddress.containsKey(addr)) {
|
||||
long mac = inetAddressToMacAddress.get(addr);
|
||||
updateArpEntryTime(mac);
|
||||
return mac;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the InetAddress for a given MAC address
|
||||
*
|
||||
* @param mac mac address to lookup.
|
||||
* @return InetAddress if it's in the map. Otherwise null.
|
||||
*/
|
||||
InetAddress getAddressForMac(long mac) {
|
||||
synchronized (macAddressToInetAdddress) {
|
||||
if (macAddressToInetAdddress.containsKey(mac)) {
|
||||
InetAddress addr = macAddressToInetAdddress.get(mac);
|
||||
updateArpEntryTime(addr);
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean hasMacForAddress(InetAddress addr) {
|
||||
synchronized (inetAddressToMacAddress) {
|
||||
return inetAddressToMacAddress.containsKey(addr);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasAddressForMac(long mac) {
|
||||
synchronized (macAddressToInetAdddress) {
|
||||
return macAddressToInetAdddress.containsKey(mac);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] longToBytes(long l) {
|
||||
// allocate an 8 byte buffer. Long.SIZE returns number of bits so divide by 8
|
||||
ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE/8);
|
||||
buffer.putLong(l);
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
public byte[] getRequestPacket(long senderMac, InetAddress senderAddress, InetAddress destinationAddress) {
|
||||
return getARPPacket(REQUEST, senderMac, 0, senderAddress, destinationAddress);
|
||||
}
|
||||
|
||||
public byte[] getReplyPacket(long senderMac, InetAddress senderAddress, long destMac, InetAddress destAddress) {
|
||||
return getARPPacket(REPLY, senderMac, destMac, senderAddress, destAddress);
|
||||
}
|
||||
|
||||
public byte[] getARPPacket(int packetType, long senderMac, long destMac, InetAddress senderAddress, InetAddress destinationAddress) {
|
||||
byte[] packet = new byte[28];
|
||||
|
||||
// Ethernet packet type
|
||||
packet[0] = 0;
|
||||
packet[1] = 1;
|
||||
|
||||
// IPV4 Protocol. 0x0800
|
||||
packet[2] = 0x08;
|
||||
packet[3] = 0;
|
||||
|
||||
// Hardware MAC address length
|
||||
packet[4] = 6;
|
||||
|
||||
// IP address length
|
||||
packet[5] = 4;
|
||||
|
||||
packet[6] = 0;
|
||||
packet[7] = (byte)packetType;
|
||||
|
||||
byte[] senderMacBuffer = longToBytes(senderMac);
|
||||
System.arraycopy(senderMacBuffer, 2, packet, 8, 6);
|
||||
|
||||
byte[] addrBuffer = senderAddress.getAddress();
|
||||
System.arraycopy(addrBuffer, 0, packet, 14, 4);
|
||||
|
||||
byte[] destMacBuffer = longToBytes(destMac);
|
||||
System.arraycopy(destMacBuffer, 2, packet, 18, 6);
|
||||
|
||||
byte[] destAddrBuffer = destinationAddress.getAddress();
|
||||
System.arraycopy(destAddrBuffer, 0, packet, 24, 4);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a reply is needed
|
||||
*
|
||||
* @param replyBuffer
|
||||
* @return
|
||||
*/
|
||||
public ARPReplyData processARPPacket(byte[] replyBuffer) {
|
||||
Log.d(TAG, "Processing ARP packet");
|
||||
|
||||
byte[] srcMacBuffer = new byte[8];
|
||||
System.arraycopy(replyBuffer, 8, srcMacBuffer, 2, 6);
|
||||
|
||||
byte[] senderAddressBuffer = new byte[4];
|
||||
System.arraycopy(replyBuffer, 14, senderAddressBuffer, 0, 4);
|
||||
|
||||
byte[] destMacBuffer = new byte[8];
|
||||
System.arraycopy(replyBuffer, 18, destMacBuffer, 2, 6);
|
||||
|
||||
byte[] destAddressBuffer = new byte[4];
|
||||
System.arraycopy(replyBuffer, 24, destAddressBuffer, 0, 4);
|
||||
|
||||
InetAddress senderAddress = null;
|
||||
try {
|
||||
senderAddress = InetAddress.getByAddress(senderAddressBuffer);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
InetAddress destAddress = null;
|
||||
try {
|
||||
destAddress = InetAddress.getByAddress(destAddressBuffer);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
long sourceMac = ByteBuffer.wrap(srcMacBuffer).getLong();
|
||||
long destMac = ByteBuffer.wrap(destMacBuffer).getLong();
|
||||
|
||||
if(sourceMac != 0 && senderAddress != null) {
|
||||
setAddress(senderAddress,sourceMac);
|
||||
}
|
||||
|
||||
if(destMac != 0 && destAddress != null) {
|
||||
setAddress(destAddress, destMac);
|
||||
}
|
||||
|
||||
|
||||
if(replyBuffer[7] == 1) {
|
||||
Log.d(TAG, "Reply needed");
|
||||
|
||||
ARPReplyData data = new ARPReplyData();
|
||||
data.destMac = sourceMac;
|
||||
data.destAddress = senderAddress;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public class ARPReplyData {
|
||||
public long senderMac;
|
||||
public InetAddress senderAddress;
|
||||
public long destMac;
|
||||
public InetAddress destAddress;
|
||||
}
|
||||
|
||||
private class ArpEntry {
|
||||
private long mac;
|
||||
private InetAddress address;
|
||||
private long time;
|
||||
|
||||
ArpEntry(long mac, InetAddress address) {
|
||||
this.mac = mac;
|
||||
this.address = address;
|
||||
updateTime();
|
||||
}
|
||||
|
||||
public long getMac() { return this.mac; }
|
||||
public InetAddress getAddress() { return this.address; }
|
||||
|
||||
public void updateTime() {
|
||||
this.time = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public boolean equals(ArpEntry e) {
|
||||
// purposely do not check time
|
||||
// mac & address alone determine equality
|
||||
return (mac == e.mac) &&
|
||||
(address.equals(e.address));
|
||||
}
|
||||
}
|
||||
|
||||
private Thread timeoutThread = new Thread("ARP Timeout Thread") {
|
||||
public void run() {
|
||||
try {
|
||||
synchronized (entries) {
|
||||
for (ArpEntry e : entries) {
|
||||
if(e.time < (System.currentTimeMillis() + ENTRY_TIMEOUT) ) {
|
||||
synchronized(macAddressToInetAdddress) {
|
||||
macAddressToInetAdddress.remove(e.mac);
|
||||
}
|
||||
|
||||
synchronized (inetAddressToMacAddress) {
|
||||
inetAddressToMacAddress.remove(e.address);
|
||||
}
|
||||
|
||||
entries.remove(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package com.zerotier.one.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import com.zerotier.sdk.DataStoreGetListener;
|
||||
import com.zerotier.sdk.DataStorePutListener;
|
||||
|
||||
public class DataStore implements DataStoreGetListener, DataStorePutListener {
|
||||
private static final String TAG = "DataStore";
|
||||
private Context _ctx;
|
||||
|
||||
public DataStore(Context ctx) {
|
||||
this._ctx = ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onDataStorePut(String name, byte[] buffer, boolean secure) {
|
||||
Log.d(TAG, "Writing File: " + name);
|
||||
try {
|
||||
if(name.contains("/")) {
|
||||
// filename has a directory separator.
|
||||
int ix = name.lastIndexOf('/');
|
||||
String path = name.substring(0, ix);
|
||||
|
||||
File f = new File(_ctx.getFilesDir(), path);
|
||||
if(!f.exists()) {
|
||||
f.mkdirs();
|
||||
}
|
||||
|
||||
File outputFile = new File(f, name.substring(name.lastIndexOf("/")+1));
|
||||
FileOutputStream fos = new FileOutputStream(outputFile);
|
||||
fos.write(buffer);
|
||||
fos.flush();
|
||||
fos.close();
|
||||
return 0;
|
||||
} else {
|
||||
|
||||
FileOutputStream fos = _ctx.openFileOutput(name, Context.MODE_PRIVATE);
|
||||
fos.write(buffer);
|
||||
fos.flush();
|
||||
fos.close();
|
||||
return 0;
|
||||
}
|
||||
} catch (FileNotFoundException fnf) {
|
||||
fnf.printStackTrace();
|
||||
return -1;
|
||||
} catch (IOException io) {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
io.printStackTrace(pw);
|
||||
Log.e(TAG, sw.toString());
|
||||
return -2;
|
||||
} catch(IllegalArgumentException ie) {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
ie.printStackTrace(pw);
|
||||
Log.e(TAG, sw.toString());
|
||||
return -3;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onDelete(String name) {
|
||||
Log.d(TAG, "Deleting File: " + name);
|
||||
boolean deleted = false;
|
||||
if(name.contains("/")) {
|
||||
// filename has a directory separator.
|
||||
|
||||
File f = new File(_ctx.getFilesDir(), name);
|
||||
if (!f.exists()) {
|
||||
deleted = true;
|
||||
} else {
|
||||
deleted = f.delete();
|
||||
}
|
||||
} else {
|
||||
deleted = _ctx.deleteFile(name);
|
||||
}
|
||||
return deleted ? 0 : 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long onDataStoreGet(String name, byte[] out_buffer,
|
||||
long bufferIndex, long[] out_objectSize) {
|
||||
Log.d(TAG, "Reading File: " + name);
|
||||
try {
|
||||
if(name.contains("/")) {
|
||||
// filename has a directory separator.
|
||||
int ix = name.lastIndexOf('/');
|
||||
String path = name.substring(0, ix);
|
||||
|
||||
File f = new File(_ctx.getFilesDir(), path);
|
||||
if(!f.exists()) {
|
||||
f.mkdirs();
|
||||
}
|
||||
|
||||
File inputFile = new File(f, name.substring(name.lastIndexOf('/')+1));
|
||||
if(!inputFile.exists()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
FileInputStream fin = new FileInputStream(inputFile);
|
||||
if(bufferIndex > 0) {
|
||||
fin.skip(bufferIndex);
|
||||
}
|
||||
int read = fin.read(out_buffer);
|
||||
fin.close();
|
||||
return read;
|
||||
} else {
|
||||
FileInputStream fin = _ctx.openFileInput(name);
|
||||
out_objectSize[0] = fin.getChannel().size();
|
||||
if (bufferIndex > 0) {
|
||||
fin.skip(bufferIndex);
|
||||
}
|
||||
int read = fin.read(out_buffer);
|
||||
fin.close();
|
||||
return read;
|
||||
}
|
||||
} catch (FileNotFoundException fnf) {
|
||||
// Can't read a file that doesn't exist!
|
||||
out_objectSize[0] = 0;
|
||||
return -1;
|
||||
} catch (IOException io) {
|
||||
io.printStackTrace();
|
||||
return -2;
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
return -3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.zerotier.one.service;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.util.Log;
|
||||
|
||||
import com.zerotier.one.events.StopEvent;
|
||||
|
||||
import de.greenrobot.event.EventBus;
|
||||
|
||||
/**
|
||||
* Created by Grant on 7/7/2015.
|
||||
*/
|
||||
public class NetworkStateReceiver extends BroadcastReceiver {
|
||||
private static String TAG = "NetworkStateReceiver";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent intent) {
|
||||
ConnectivityManager cm =
|
||||
(ConnectivityManager)ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
|
||||
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
|
||||
boolean isConnected = (activeNetwork != null &&
|
||||
activeNetwork.isConnectedOrConnecting());
|
||||
|
||||
Intent i = new Intent(ctx, RuntimeService.class);
|
||||
if(isConnected) {
|
||||
Log.d(TAG, "Network State: Connected");
|
||||
i.putExtra(RuntimeService.START_TYPE, RuntimeService.START_NETWORK_CHANGE);
|
||||
ctx.startService(i);
|
||||
} else {
|
||||
Log.d(TAG, "Network State: Disconnected");
|
||||
i.putExtra(RuntimeService.START_TYPE, RuntimeService.STOP_NETWORK_CHANGE);
|
||||
ctx.startService(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.zerotier.one.service;
|
||||
|
||||
import com.zerotier.one.util.InetAddressUtils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Created by Grant on 6/13/2015.
|
||||
*/
|
||||
public class Route {
|
||||
InetAddress address;
|
||||
int prefix;
|
||||
|
||||
public Route(InetAddress address, int prefix) {
|
||||
this.address = address;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public boolean belongsToRoute(InetAddress otherAddr) {
|
||||
InetAddress route = InetAddressUtils.addressToRoute(otherAddr, prefix);
|
||||
|
||||
return address.equals(route);
|
||||
}
|
||||
|
||||
public boolean equals(Route r) {
|
||||
return address.equals(r.address) && prefix == r.prefix;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.zerotier.one.service;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import com.zerotier.one.events.ManualDisconnectEvent;
|
||||
import com.zerotier.one.events.StopEvent;
|
||||
|
||||
import de.greenrobot.event.EventBus;
|
||||
|
||||
public class RuntimeService extends Service {
|
||||
public static final String START_TYPE = "com.zerotier.one.service.RuntimeService.start_type";
|
||||
public static final int START_NETWORK_CHANGE = 1;
|
||||
public static final int START_BOOT = 2;
|
||||
public static final int START_USER_INTERFACE = 3;
|
||||
public static final int STOP_NETWORK_CHANGE = 4;
|
||||
public static final int STOP_USER_INTERFACE = 5;
|
||||
|
||||
private static final String TAG = "RuntimeService";
|
||||
private EventBus eventBus = EventBus.getDefault();
|
||||
|
||||
private boolean serviceStarted = false;
|
||||
|
||||
private NetworkStateReceiver nsReceiver = null;
|
||||
|
||||
public RuntimeService() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
// TODO: Return the communication channel to the service.
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Log.d(TAG, "RuntimeService started");
|
||||
|
||||
if(nsReceiver == null) {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
|
||||
nsReceiver = new NetworkStateReceiver();
|
||||
registerReceiver(nsReceiver, filter);
|
||||
}
|
||||
|
||||
if(intent == null) {
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
int startMode = intent.getIntExtra(START_TYPE, 0);
|
||||
// in each of the following cases, Prepare should always return null as the prepare call
|
||||
// is called the first time the UI is started granting permission for ZeroTier to use
|
||||
// the VPN API
|
||||
switch (startMode) {
|
||||
case START_NETWORK_CHANGE: {
|
||||
Log.d(TAG, "Start Network change");
|
||||
if(serviceStarted) {
|
||||
// start ZeroTierOne service.
|
||||
Log.d(TAG, "Start Network Change");
|
||||
Intent i = ZeroTierOneService.prepare(this);
|
||||
if(i == null) {
|
||||
i = new Intent(this, ZeroTierOneService.class);
|
||||
startService(i);
|
||||
serviceStarted = true;
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Ignore Start Network Change: Service has not been manually started.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case START_BOOT: {
|
||||
// if start on boot, start service
|
||||
Log.d(TAG, "Start Boot");
|
||||
Intent i = ZeroTierOneService.prepare(this);
|
||||
if(i == null) {
|
||||
i = new Intent(this, ZeroTierOneService.class);
|
||||
startService(i);
|
||||
serviceStarted = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case START_USER_INTERFACE: {
|
||||
Log.d(TAG, "Start User Interface");
|
||||
Intent i = ZeroTierOneService.prepare(this);
|
||||
if(i == null) {
|
||||
i = new Intent(this, ZeroTierOneService.class);
|
||||
startService(i);
|
||||
serviceStarted = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STOP_NETWORK_CHANGE: {
|
||||
Log.d(TAG, "Stop Network Change. Ignored.");
|
||||
// Intent i = new Intent(this, ZeroTierOneService.class);
|
||||
// stopService(i);
|
||||
// EventBus.getDefault().post(new StopEvent());
|
||||
break;
|
||||
}
|
||||
case STOP_USER_INTERFACE: {
|
||||
Log.d(TAG, "Stop User Interface");
|
||||
Intent i = new Intent(this, ZeroTierOneService.class);
|
||||
stopService(i);
|
||||
EventBus.getDefault().post(new ManualDisconnectEvent());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Log.e(TAG, "Unknown start ID: " + startId);
|
||||
break;
|
||||
}
|
||||
return START_STICKY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.zerotier.one.service;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Broadcast receiver that listens for system bootup and starts
|
||||
* the ZeroTier service
|
||||
*/
|
||||
public class StartupReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = "StartupReceiver";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent intent) {
|
||||
Log.i(TAG, "Received: " + intent.getAction()+ ". Starting ZeroTier One service.");
|
||||
|
||||
// TODO: Start service
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||
|
||||
boolean shouldStart = prefs.getBoolean("general_start_zerotier_on_boot", true);
|
||||
if(shouldStart) {
|
||||
Log.i(TAG, "Preferences set to start ZeroTier on boot");
|
||||
Intent i = new Intent(ctx, RuntimeService.class);
|
||||
i.putExtra(RuntimeService.START_TYPE, RuntimeService.START_BOOT);
|
||||
ctx.startService(i);
|
||||
} else {
|
||||
Log.i(TAG, "Preferences set to not start ZeroTier on boot");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
package com.zerotier.one.service;
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
import com.zerotier.one.util.IPPacketUtils;
|
||||
import com.zerotier.sdk.Node;
|
||||
import com.zerotier.sdk.ResultCode;
|
||||
import com.zerotier.sdk.VirtualNetworkConfig;
|
||||
import com.zerotier.sdk.VirtualNetworkFrameListener;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Created by Grant on 6/3/2015.
|
||||
*/
|
||||
public class TunTapAdapter implements VirtualNetworkFrameListener {
|
||||
public static final String TAG = "TunTapAdapter";
|
||||
|
||||
private static final int ARP_PACKET = 0x0806;
|
||||
private static final int IPV4_PACKET = 0x0800;
|
||||
private static final int IPV6_PACKET = 0x86dd;
|
||||
|
||||
public static final long BROADCAST_MAC = 0xffffffffffffL;
|
||||
|
||||
private ARPTable arpTable;
|
||||
private Node node;
|
||||
private ZeroTierOneService ztService;
|
||||
private ParcelFileDescriptor vpnSocket;
|
||||
private FileInputStream in;
|
||||
private FileOutputStream out;
|
||||
|
||||
private final HashMap<Route, Long> routeMap;
|
||||
|
||||
private Thread receiveThread;
|
||||
|
||||
public TunTapAdapter(ZeroTierOneService ztService) {
|
||||
this.ztService = ztService;
|
||||
arpTable = new ARPTable();
|
||||
routeMap = new HashMap<Route, Long>();
|
||||
}
|
||||
|
||||
public void setNode(Node node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public void setVpnSocket(ParcelFileDescriptor vpnSocket) {
|
||||
this.vpnSocket = vpnSocket;
|
||||
}
|
||||
|
||||
public void setFileStreams(FileInputStream in, FileOutputStream out) {
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
public void addRouteAndNetwork(Route route, long networkId) {
|
||||
synchronized (routeMap) {
|
||||
routeMap.put(route, networkId);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearRouteMap() {
|
||||
synchronized (routeMap) {
|
||||
routeMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void startThreads() {
|
||||
receiveThread = new Thread("Tunnel Receive Thread") {
|
||||
public void run() {
|
||||
try {
|
||||
Log.d(TAG, "TUN Receive Thread Started");
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(32767);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
try {
|
||||
while (!isInterrupted()) {
|
||||
boolean idle = true;
|
||||
int length = in.read(buffer.array());
|
||||
if (length > 0) {
|
||||
Log.d(TAG, "Sending packet to ZeroTier. " + length + " bytes.");
|
||||
idle = false;
|
||||
|
||||
byte packet[] = new byte[length];
|
||||
System.arraycopy(buffer.array(), 0, packet, 0, length);
|
||||
|
||||
InetAddress destAddress = IPPacketUtils.getDestIP(packet);
|
||||
InetAddress sourceAddress = IPPacketUtils.getSourceIP(packet);
|
||||
|
||||
long nwid = networkIdForDestination(destAddress);
|
||||
if (nwid == 0) {
|
||||
Log.e(TAG, "Unable to find network ID for destination address: " + destAddress);
|
||||
continue;
|
||||
}
|
||||
|
||||
VirtualNetworkConfig cfg = node.networkConfig(nwid);
|
||||
InetAddress myAddress = cfg.assignedAddresses()[0].getAddress();
|
||||
|
||||
long srcMac = cfg.macAddress();
|
||||
long bgt_dl[] = new long[1];
|
||||
|
||||
if (arpTable.hasMacForAddress(destAddress)) {
|
||||
long destMac = arpTable.getMacForAddress(destAddress);
|
||||
|
||||
ResultCode rc = node.processVirtualNetworkFrame(
|
||||
System.currentTimeMillis(),
|
||||
nwid,
|
||||
srcMac,
|
||||
destMac,
|
||||
IPV4_PACKET,
|
||||
0,
|
||||
packet,
|
||||
bgt_dl);
|
||||
|
||||
if (rc != ResultCode.RESULT_OK) {
|
||||
Log.e(TAG, "Error calling processVirtualNetworkFrame: " + rc.toString());
|
||||
} else {
|
||||
Log.d(TAG, "Packet sent to ZT");
|
||||
ztService.setNextBackgroundTaskDeadline(bgt_dl[0]);
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Unknown dest MAC address. Need to look it up. " + destAddress);
|
||||
byte[] arpRequest = arpTable.getRequestPacket(srcMac, myAddress, destAddress);
|
||||
|
||||
ResultCode rc = node.processVirtualNetworkFrame(
|
||||
System.currentTimeMillis(),
|
||||
nwid,
|
||||
srcMac,
|
||||
BROADCAST_MAC,
|
||||
ARP_PACKET,
|
||||
0,
|
||||
arpRequest,
|
||||
bgt_dl);
|
||||
|
||||
if (rc != ResultCode.RESULT_OK) {
|
||||
Log.e(TAG, "Error sending ARP packet: " + rc.toString());
|
||||
} else {
|
||||
Log.d(TAG, "ARP Request Sent!");
|
||||
ztService.setNextBackgroundTaskDeadline(bgt_dl[0]);
|
||||
}
|
||||
}
|
||||
buffer.clear();
|
||||
} else {
|
||||
//Log.d(TAG, "No bytes read: " + length);
|
||||
}
|
||||
|
||||
if (idle) {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in TUN Receive: " + e.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// swallow InterruptedException
|
||||
}
|
||||
Log.d(TAG, "TUN Receive Thread ended");
|
||||
}
|
||||
};
|
||||
|
||||
receiveThread.start();
|
||||
}
|
||||
|
||||
public void interrupt() {
|
||||
if (receiveThread != null) {
|
||||
receiveThread.interrupt();
|
||||
try {
|
||||
receiveThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
// swallow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
if (receiveThread == null) {
|
||||
return false;
|
||||
} else {
|
||||
return receiveThread.isAlive();
|
||||
}
|
||||
}
|
||||
|
||||
public void onVirtualNetworkFrame(
|
||||
long nwid,
|
||||
long srcMac,
|
||||
long destMac,
|
||||
long etherType,
|
||||
long vlanId,
|
||||
byte[] frameData) {
|
||||
Log.d(TAG, "Got Virtual Network Frame. Network ID: " + Long.toHexString(nwid) + " Source MAC: " + Long.toHexString(srcMac) +
|
||||
" Dest MAC: " + Long.toHexString(destMac) + " Ether type: " + etherType + " VLAN ID: " + vlanId + " Frame Length: " + frameData.length);
|
||||
|
||||
if (vpnSocket == null) {
|
||||
Log.e(TAG, "vpnSocket is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (in == null || out == null) {
|
||||
Log.e(TAG, "no in/out streams");
|
||||
return;
|
||||
}
|
||||
|
||||
if (etherType == ARP_PACKET) {
|
||||
Log.d(TAG, "Got ARP Packet");
|
||||
|
||||
ARPTable.ARPReplyData data = arpTable.processARPPacket(frameData);
|
||||
|
||||
if (data != null && data.destMac != 0 && data.destAddress != null) {
|
||||
// We need to reply here.
|
||||
long deadline[] = new long[1];
|
||||
|
||||
VirtualNetworkConfig cfg = node.networkConfig(nwid);
|
||||
|
||||
InetAddress myAddress = cfg.assignedAddresses()[0].getAddress();
|
||||
|
||||
byte[] replyPacket = arpTable.getReplyPacket(cfg.macAddress(), myAddress, data.destMac, data.destAddress);
|
||||
ResultCode rc = node.processVirtualNetworkFrame(
|
||||
System.currentTimeMillis(),
|
||||
nwid,
|
||||
cfg.macAddress(),
|
||||
srcMac,
|
||||
ARP_PACKET,
|
||||
0,
|
||||
replyPacket,
|
||||
deadline);
|
||||
|
||||
if (rc != ResultCode.RESULT_OK) {
|
||||
Log.e(TAG, "Error sending ARP packet: " + rc.toString());
|
||||
} else {
|
||||
Log.d(TAG, "ARP Reply Sent!");
|
||||
ztService.setNextBackgroundTaskDeadline(deadline[0]);
|
||||
}
|
||||
}
|
||||
|
||||
} else if ((etherType == IPV4_PACKET) ||
|
||||
(etherType == IPV6_PACKET)) {
|
||||
// Got IPv4 or IPv6 packet!
|
||||
Log.d(TAG, "Got IP packet. Length: " + frameData.length + " Bytes");
|
||||
|
||||
try {
|
||||
InetAddress sourceAddress = IPPacketUtils.getSourceIP(frameData);
|
||||
if (sourceAddress != null) {
|
||||
arpTable.setAddress(sourceAddress, srcMac);
|
||||
}
|
||||
out.write(frameData);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error writing data to vpn socket: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Unknown Packet Type Received: 0x" + String.format("%02X%02X", frameData[12], frameData[13]));
|
||||
}
|
||||
}
|
||||
|
||||
private long networkIdForDestination(InetAddress destination) {
|
||||
synchronized (routeMap) {
|
||||
for (Route r : routeMap.keySet()) {
|
||||
if (r.belongsToRoute(destination)) {
|
||||
return routeMap.get(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.zerotier.one.service;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.zerotier.sdk.Node;
|
||||
import com.zerotier.sdk.PacketSender;
|
||||
import com.zerotier.sdk.ResultCode;
|
||||
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
/**
|
||||
* Created by Grant on 6/3/2015.
|
||||
*/
|
||||
public class UdpCom implements PacketSender, Runnable {
|
||||
private final static String TAG = "UdpCom";
|
||||
|
||||
Node node;
|
||||
ZeroTierOneService ztService;
|
||||
DatagramSocket svrSocket;
|
||||
|
||||
UdpCom(ZeroTierOneService service, DatagramSocket socket) {
|
||||
svrSocket = socket;
|
||||
this.ztService = service;
|
||||
}
|
||||
|
||||
public void setNode(Node node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public int onSendPacketRequested(
|
||||
InetSocketAddress localAddress,
|
||||
InetSocketAddress remoteAddress,
|
||||
byte[] packetData,
|
||||
int ttl) {
|
||||
// TTL is ignored. No way to set it on a UDP packet in Java
|
||||
if(svrSocket == null) {
|
||||
Log.e(TAG, "Attempted to send packet on a null socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
DatagramPacket p = new DatagramPacket(packetData, packetData.length, remoteAddress);
|
||||
Log.d(TAG, "onSendPacketRequested: Sent "+ p.getLength() + " bytes to " + remoteAddress.toString());
|
||||
svrSocket.send(p);
|
||||
} catch (Exception ex) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
Log.d(TAG, "UDP Listen Thread Started.");
|
||||
try {
|
||||
long[] bgtask = new long[1];
|
||||
byte[] buffer = new byte[16384];
|
||||
while(!Thread.interrupted()) {
|
||||
|
||||
bgtask[0] = 0;
|
||||
DatagramPacket p = new DatagramPacket(buffer, buffer.length);
|
||||
|
||||
try {
|
||||
svrSocket.receive(p);
|
||||
if(p.getLength() > 0)
|
||||
{
|
||||
byte[] received = new byte[p.getLength()];
|
||||
System.arraycopy(p.getData(), 0, received, 0, p.getLength());
|
||||
Log.d(TAG, "Got " + p.getLength() + " Bytes From: " + p.getAddress().toString() +":" + p.getPort());
|
||||
|
||||
ResultCode rc = node.processWirePacket(System.currentTimeMillis(), null, new InetSocketAddress(p.getAddress(), p.getPort()), received, bgtask);
|
||||
|
||||
if(rc != ResultCode.RESULT_OK) {
|
||||
Log.e(TAG, "procesWirePacket returned: " + rc.toString());
|
||||
ztService.shutdown();
|
||||
}
|
||||
|
||||
ztService.setNextBackgroundTaskDeadline( bgtask[0] );
|
||||
}
|
||||
} catch (SocketTimeoutException e) {
|
||||
Log.d(TAG, "Socket Timeout");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Log.d(TAG, "UDP Listen Thread Ended.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,537 @@
|
||||
package com.zerotier.one.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.VpnService;
|
||||
import android.content.Intent;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.zerotier.one.events.ErrorEvent;
|
||||
import com.zerotier.one.events.JoinNetworkEvent;
|
||||
import com.zerotier.one.events.LeaveNetworkEvent;
|
||||
import com.zerotier.one.events.ManualDisconnectEvent;
|
||||
import com.zerotier.one.events.NetworkInfoReplyEvent;
|
||||
import com.zerotier.one.events.NetworkListReplyEvent;
|
||||
import com.zerotier.one.events.NetworkReconfigureEvent;
|
||||
import com.zerotier.one.events.NetworkRemovedEvent;
|
||||
import com.zerotier.one.events.NodeDestroyedEvent;
|
||||
import com.zerotier.one.events.NodeIDEvent;
|
||||
import com.zerotier.one.events.NodeStatusEvent;
|
||||
import com.zerotier.one.events.RequestNetworkInfoEvent;
|
||||
import com.zerotier.one.events.RequestNetworkListEvent;
|
||||
import com.zerotier.one.events.RequestNodeStatusEvent;
|
||||
import com.zerotier.one.events.StopEvent;
|
||||
import com.zerotier.one.util.InetAddressUtils;
|
||||
import com.zerotier.one.util.NetworkIdUtils;
|
||||
import com.zerotier.sdk.Event;
|
||||
import com.zerotier.sdk.EventListener;
|
||||
import com.zerotier.sdk.Node;
|
||||
import com.zerotier.sdk.NodeException;
|
||||
import com.zerotier.sdk.NodeStatus;
|
||||
import com.zerotier.sdk.ResultCode;
|
||||
import com.zerotier.sdk.VirtualNetworkConfig;
|
||||
import com.zerotier.sdk.VirtualNetworkConfigListener;
|
||||
import com.zerotier.sdk.VirtualNetworkConfigOperation;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
|
||||
import de.greenrobot.event.EventBus;
|
||||
|
||||
public class ZeroTierOneService extends VpnService implements Runnable, EventListener, VirtualNetworkConfigListener {
|
||||
|
||||
private static final String TAG = "ZT1_Service";
|
||||
|
||||
public static final String ZT1_NETWORK_ID = "com.zerotier.one.network_id";
|
||||
|
||||
public static final int MSG_JOIN_NETWORK = 1;
|
||||
public static final int MSG_LEAVE_NETWORK = 2;
|
||||
|
||||
private Thread vpnThread;
|
||||
private UdpCom udpCom;
|
||||
private Thread udpThread;
|
||||
|
||||
private TunTapAdapter tunTapAdapter;
|
||||
|
||||
private Node node;
|
||||
private DataStore dataStore;
|
||||
|
||||
DatagramSocket svrSocket;
|
||||
ParcelFileDescriptor vpnSocket;
|
||||
FileInputStream in;
|
||||
FileOutputStream out;
|
||||
|
||||
private final HashMap<Long, VirtualNetworkConfig> networkConfigs;
|
||||
|
||||
private EventBus eventBus = EventBus.getDefault();
|
||||
|
||||
private long nextBackgroundTaskDeadline = 0;
|
||||
private long lastMulticastGroupCheck = 0;
|
||||
|
||||
protected void setNextBackgroundTaskDeadline(long deadline) {
|
||||
synchronized (this) {
|
||||
nextBackgroundTaskDeadline = deadline;
|
||||
}
|
||||
}
|
||||
|
||||
public ZeroTierOneService() {
|
||||
super();
|
||||
dataStore = new DataStore(this);
|
||||
networkConfigs = new HashMap<>();
|
||||
eventBus.register(this);
|
||||
|
||||
Netcon.ZT_SDK_Wrapper wrapper = new Netcon.ZT_SDK_Wrapper();
|
||||
|
||||
|
||||
wrapper.startOneService();
|
||||
/*
|
||||
if(wrapper.loadsymbols() == 4)
|
||||
{
|
||||
Log.e(TAG,"loadsymbols(): Symbol found");
|
||||
//Toast t = Toast.makeText(this, "WORKED", Toast.LENGTH_SHORT);
|
||||
}
|
||||
else {
|
||||
Log.e(TAG,"loadsymbols(): Symbol NOT found");
|
||||
//Toast t = Toast.makeText(this, "DIDNT WORK", Toast.LENGTH_SHORT);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
|
||||
|
||||
|
||||
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
public void stopZeroTier() {
|
||||
if(udpThread != null && udpThread.isAlive()) {
|
||||
udpThread.interrupt();
|
||||
udpThread = null;
|
||||
}
|
||||
|
||||
if(vpnThread != null && vpnThread.isAlive()) {
|
||||
vpnThread.interrupt();
|
||||
vpnThread = null;
|
||||
}
|
||||
|
||||
if(svrSocket != null) {
|
||||
svrSocket.close();
|
||||
svrSocket = null;
|
||||
}
|
||||
|
||||
if(node != null) {
|
||||
eventBus.post(new NodeDestroyedEvent());
|
||||
node.close();
|
||||
node = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
stopZeroTier();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRevoke() {
|
||||
Intent i = new Intent(this, RuntimeService.class);
|
||||
i.putExtra(RuntimeService.START_TYPE, RuntimeService.STOP_USER_INTERFACE);
|
||||
this.startService(i);
|
||||
|
||||
stopZeroTier();
|
||||
try {
|
||||
vpnSocket.close();
|
||||
} catch (Exception e) {
|
||||
// swallow it
|
||||
}
|
||||
vpnSocket = null;
|
||||
|
||||
stopSelf();
|
||||
|
||||
Intent stopIntent = new Intent(this, RuntimeService.class);
|
||||
stopService(stopIntent);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
/*
|
||||
Log.d(TAG, "ZeroTierOne Service Started");
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
boolean autoRejoin = prefs.getBoolean("network_auto_rejoin_networks", true);
|
||||
|
||||
if(autoRejoin) {
|
||||
// find all local network configs and join them
|
||||
File networksFolder = new File(getFilesDir(), "networks.d");
|
||||
if (networksFolder.exists()) {
|
||||
File[] networks = networksFolder.listFiles();
|
||||
for (File f : networks) {
|
||||
if (f.getName().endsWith(".conf")) {
|
||||
String filename = f.getName();
|
||||
filename = filename.substring(0, filename.lastIndexOf('.'));
|
||||
Log.d(TAG, "Loading network: " + filename);
|
||||
ResultCode rc = node.join(NetworkIdUtils.hexStringToLong(filename));
|
||||
if (rc != ResultCode.RESULT_OK) {
|
||||
Log.d(TAG, "Error joining network: " + rc.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "This Node Address: " + Long.toHexString(node.address()));
|
||||
|
||||
while(!Thread.interrupted()) {
|
||||
try {
|
||||
|
||||
long dl = nextBackgroundTaskDeadline;
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
if (dl <= now) {
|
||||
long[] returnDeadline = {0};
|
||||
ResultCode rc = node.processBackgroundTasks(now, returnDeadline);
|
||||
|
||||
synchronized(this) {
|
||||
nextBackgroundTaskDeadline = returnDeadline[0];
|
||||
}
|
||||
|
||||
if(rc != ResultCode.RESULT_OK) {
|
||||
Log.e(TAG, "Error on processBackgroundTasks: " + rc.toString());
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
long delay = (dl > now) ? (dl - now) : 100;
|
||||
Thread.sleep(delay);
|
||||
} catch (InterruptedException ie) {
|
||||
break;
|
||||
} catch (Exception ex) {
|
||||
Log.e(TAG, ex.toString());
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "ZeroTierOne Service Ended");
|
||||
*/
|
||||
}
|
||||
|
||||
//
|
||||
// EventBus events
|
||||
//
|
||||
|
||||
public void onEvent(StopEvent e) {
|
||||
stopZeroTier();
|
||||
}
|
||||
|
||||
public void onEvent(ManualDisconnectEvent e) {
|
||||
stopZeroTier();
|
||||
}
|
||||
|
||||
public void onEventBackgroundThread(JoinNetworkEvent e) {
|
||||
/*
|
||||
Log.d(TAG, "Join Network Event");
|
||||
if(node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Remove this once multi-network support is in place
|
||||
for(long nwid : networkConfigs.keySet()) {
|
||||
onEventBackgroundThread(new LeaveNetworkEvent(nwid));
|
||||
}
|
||||
|
||||
networkConfigs.clear();
|
||||
|
||||
ResultCode rc = node.join(e.getNetworkId());
|
||||
if(rc != ResultCode.RESULT_OK) {
|
||||
eventBus.post(new ErrorEvent(rc));
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public void onEventBackgroundThread(LeaveNetworkEvent e) {
|
||||
/*
|
||||
Log.d(TAG, "Leave Network Event");
|
||||
|
||||
if(node != null) {
|
||||
ResultCode rc = node.leave(e.getNetworkId());
|
||||
if (rc != ResultCode.RESULT_OK) {
|
||||
eventBus.post(new ErrorEvent(rc));
|
||||
return;
|
||||
}
|
||||
String certsFile = "networks.d/" + Long.toHexString(e.getNetworkId()) + ".mcerts";
|
||||
String confFile = "networks.d/" + Long.toHexString(e.getNetworkId()) + ".conf";
|
||||
dataStore.onDelete(confFile);
|
||||
dataStore.onDelete(certsFile);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public void onEventBackgroundThread(RequestNetworkInfoEvent e) {
|
||||
/*
|
||||
if(node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
VirtualNetworkConfig vnc = node.networkConfig(e.getNetworkId());
|
||||
if(vnc != null) {
|
||||
eventBus.post(new NetworkInfoReplyEvent(vnc));
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public void onEventBackgroundThread(RequestNetworkListEvent e) {
|
||||
/*
|
||||
if(node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
VirtualNetworkConfig[] networks = node.networks();
|
||||
if(networks != null && networks.length > 0) {
|
||||
eventBus.post(new NetworkListReplyEvent(networks));
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public void onEventBackgroundThread(RequestNodeStatusEvent e) {
|
||||
/*
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
NodeStatus ns = node.status();
|
||||
|
||||
eventBus.post(new NodeStatusEvent(ns));
|
||||
*/
|
||||
}
|
||||
|
||||
public void onEventAsync(NetworkReconfigureEvent e) {
|
||||
updateTunnelConfig();
|
||||
}
|
||||
|
||||
//
|
||||
// Event Listener Overrides
|
||||
//
|
||||
public void onEvent(Event e) {
|
||||
/*
|
||||
Log.d(TAG, "Event: " + e.toString());
|
||||
|
||||
if(node != null) {
|
||||
NodeStatus status = node.status();
|
||||
NodeStatusEvent nse = new NodeStatusEvent(status);
|
||||
eventBus.post(nse);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public void onTrace(String msg) {
|
||||
Log.d(TAG, "Trace: " + msg);
|
||||
}
|
||||
|
||||
//
|
||||
// Virtual Network Config Listener overrides
|
||||
//
|
||||
|
||||
public int onNetworkConfigurationUpdated(
|
||||
long nwid,
|
||||
VirtualNetworkConfigOperation op,
|
||||
VirtualNetworkConfig config) {
|
||||
/*
|
||||
Log.d(TAG, "Virtual Network Config Operation: " + op.toString());
|
||||
switch(op) {
|
||||
case VIRTUAL_NETWORK_CONFIG_OPERATION_UP: {
|
||||
Log.d(TAG, "Network Type:" + config.networkType().toString() + " " +
|
||||
"Network Status: " + config.networkStatus().toString() + " " +
|
||||
"Network Name: " + config.name() + " ");
|
||||
|
||||
eventBus.post(new NetworkInfoReplyEvent(config));
|
||||
}
|
||||
break;
|
||||
|
||||
case VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE: {
|
||||
Log.d(TAG, "Network Config Update!");
|
||||
|
||||
|
||||
VirtualNetworkConfig cfg = null;
|
||||
synchronized (networkConfigs) {
|
||||
if (networkConfigs.containsKey(nwid)) {
|
||||
cfg = networkConfigs.get(nwid);
|
||||
}
|
||||
}
|
||||
|
||||
if(cfg == null) {
|
||||
// we don't already have this network config
|
||||
Log.d(TAG, "Adding new network.");
|
||||
synchronized (networkConfigs) {
|
||||
networkConfigs.put(nwid, config);
|
||||
}
|
||||
eventBus.post(new NetworkReconfigureEvent());
|
||||
eventBus.post(new NetworkInfoReplyEvent(config));
|
||||
break;
|
||||
}
|
||||
|
||||
if(!cfg.equals(config)) {
|
||||
Log.d(TAG, "Updating network config");
|
||||
synchronized (networkConfigs) {
|
||||
networkConfigs.remove(nwid);
|
||||
networkConfigs.put(nwid, config);
|
||||
}
|
||||
eventBus.post(new NetworkReconfigureEvent());
|
||||
}
|
||||
|
||||
eventBus.post(new NetworkInfoReplyEvent(config));
|
||||
}
|
||||
break;
|
||||
|
||||
case VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN:
|
||||
case VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY:
|
||||
Log.d(TAG, "Network Down!");
|
||||
synchronized (networkConfigs) {
|
||||
if (networkConfigs.containsKey(nwid)) {
|
||||
networkConfigs.remove(nwid);
|
||||
}
|
||||
}
|
||||
|
||||
eventBus.post(new NetworkReconfigureEvent());
|
||||
eventBus.post(new NetworkRemovedEvent(nwid));
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Unknown Network Config Operation!");
|
||||
break;
|
||||
}
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected void shutdown() {
|
||||
stopZeroTier();
|
||||
this.stopSelf();
|
||||
}
|
||||
|
||||
/**
|
||||
* This should ONLY be called from onEventAsync(NetworkReconfigureEvent)
|
||||
*/
|
||||
private void updateTunnelConfig() {
|
||||
|
||||
/*
|
||||
synchronized (networkConfigs) {
|
||||
if (networkConfigs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tunTapAdapter.isRunning()) {
|
||||
tunTapAdapter.interrupt();
|
||||
}
|
||||
|
||||
tunTapAdapter.clearRouteMap();
|
||||
|
||||
if (vpnSocket != null) {
|
||||
try {
|
||||
vpnSocket.close();
|
||||
in.close();
|
||||
out.close();
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
vpnSocket = null;
|
||||
in = null;
|
||||
out = null;
|
||||
}
|
||||
|
||||
Builder builder = new Builder();
|
||||
|
||||
int highestMtu = 0;
|
||||
|
||||
for (VirtualNetworkConfig config : networkConfigs.values()) {
|
||||
if (config.isEnabled()) {
|
||||
long nwid = config.networkId();
|
||||
|
||||
InetSocketAddress addresses[] = config.assignedAddresses();
|
||||
|
||||
int adi = 0;
|
||||
|
||||
for (int i = 0; i < addresses.length; ++i) {
|
||||
Log.d(TAG, "Adding VPN Address: " + addresses[i].getAddress() + " Mac: " + Long.toHexString(config.macAddress()));
|
||||
|
||||
byte[] addrBytes = addresses[i].getAddress().getAddress();
|
||||
|
||||
try {
|
||||
adi = ByteBuffer.wrap(addrBytes).getInt();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exception calculating multicast ADI: " + e.getMessage());
|
||||
continue;
|
||||
}
|
||||
|
||||
int routeSub = addresses[i].getPort();
|
||||
InetAddress address = addresses[i].getAddress();
|
||||
|
||||
// TODO: Support IPv6
|
||||
if(address instanceof Inet6Address) {
|
||||
Log.d(TAG, "Got an IPV6 Address. Not adding it to the adapter");
|
||||
continue;
|
||||
}
|
||||
|
||||
InetAddress route = InetAddressUtils.addressToRoute(address, routeSub);
|
||||
if(route == null) {
|
||||
Log.e(TAG, "NULL route calculated!");
|
||||
continue;
|
||||
}
|
||||
|
||||
ResultCode rc = node.multicastSubscribe(nwid, TunTapAdapter.BROADCAST_MAC, adi);
|
||||
if (rc != ResultCode.RESULT_OK) {
|
||||
Log.e(TAG, "Error joining multicast group");
|
||||
} else {
|
||||
Log.d(TAG, "Joined multicast group");
|
||||
}
|
||||
|
||||
builder.addAddress(address, routeSub);
|
||||
builder.addRoute(route, routeSub);
|
||||
|
||||
Route r = new Route(route, routeSub);
|
||||
tunTapAdapter.addRouteAndNetwork(r, nwid);
|
||||
|
||||
|
||||
int mtu = config.mtu();
|
||||
if (mtu > highestMtu) {
|
||||
highestMtu = mtu;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.setMtu(highestMtu);
|
||||
builder.setSession("ZeroTier One");
|
||||
|
||||
|
||||
vpnSocket = builder.establish();
|
||||
if(vpnSocket == null) {
|
||||
Log.e(TAG, "vpnSocket is NULL after builder.establish()!!!!");
|
||||
stopZeroTier();
|
||||
return;
|
||||
}
|
||||
|
||||
in = new FileInputStream(vpnSocket.getFileDescriptor());
|
||||
out = new FileOutputStream(vpnSocket.getFileDescriptor());
|
||||
|
||||
tunTapAdapter.setVpnSocket(vpnSocket);
|
||||
tunTapAdapter.setFileStreams(in, out);
|
||||
tunTapAdapter.startThreads();
|
||||
Log.i(TAG, "ZeroTier One Connected");
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* Copyright 2013 Maarten Pennings
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* If you use this software in a product, an acknowledgment in the product
|
||||
* documentation would be appreciated but is not required.
|
||||
*/
|
||||
|
||||
package com.zerotier.one.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.inputmethodservice.Keyboard;
|
||||
import android.inputmethodservice.KeyboardView;
|
||||
import android.inputmethodservice.KeyboardView.OnKeyboardActionListener;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnFocusChangeListener;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
|
||||
/**
|
||||
* When an activity hosts a keyboardView, this class allows several EditText's to register for it.
|
||||
*
|
||||
* @author Maarten Pennings
|
||||
* @date 2012 December 23
|
||||
*/
|
||||
class HexKeyboard {
|
||||
private final static String TAG = "HexKeyboard";
|
||||
|
||||
/** A link to the KeyboardView that is used to render this CustomKeyboard. */
|
||||
private KeyboardView mKeyboardView;
|
||||
/** A link to the activity that hosts the {@link #mKeyboardView}. */
|
||||
private Activity mHostActivity;
|
||||
|
||||
/** The key (code) handler. */
|
||||
private OnKeyboardActionListener mOnKeyboardActionListener = new OnKeyboardActionListener() {
|
||||
|
||||
public final static int CodeDelete = -5; // Keyboard.KEYCODE_DELETE
|
||||
public final static int CodeCancel = -3; // Keyboard.KEYCODE_CANCEL
|
||||
public final static int CodePrev = 55000;
|
||||
public final static int CodeAllLeft = 55001;
|
||||
public final static int CodeLeft = 55002;
|
||||
public final static int CodeRight = 55003;
|
||||
public final static int CodeAllRight = 55004;
|
||||
public final static int CodeNext = 55005;
|
||||
public final static int CodeClear = 55006;
|
||||
|
||||
@Override
|
||||
public void onKey(int primaryCode, int[] keyCodes) {
|
||||
// NOTE We can say '<Key android:codes="49,50" ... >' in the xml file; all codes come in keyCodes, the first in this list in primaryCode
|
||||
// Get the EditText and its Editable
|
||||
View focusCurrent = mHostActivity.getWindow().getCurrentFocus();
|
||||
|
||||
if( focusCurrent==null) return;
|
||||
|
||||
EditText edittext = (EditText) focusCurrent;
|
||||
Editable editable = edittext.getText();
|
||||
int start = edittext.getSelectionStart();
|
||||
// Apply the key to the edittext
|
||||
if( primaryCode==CodeCancel ) {
|
||||
hideCustomKeyboard();
|
||||
} else if( primaryCode==CodeDelete ) {
|
||||
if( editable!=null && start>0 ) editable.delete(start - 1, start);
|
||||
} else if( primaryCode==CodeClear ) {
|
||||
if( editable!=null ) editable.clear();
|
||||
} else if( primaryCode==CodeLeft ) {
|
||||
if( start>0 ) edittext.setSelection(start - 1);
|
||||
} else if( primaryCode==CodeRight ) {
|
||||
if (start < edittext.length()) edittext.setSelection(start + 1);
|
||||
} else if( primaryCode==CodeAllLeft ) {
|
||||
edittext.setSelection(0);
|
||||
} else if( primaryCode==CodeAllRight ) {
|
||||
edittext.setSelection(edittext.length());
|
||||
} else if( primaryCode==CodePrev ) {
|
||||
View focusNew= edittext.focusSearch(View.FOCUS_LEFT);
|
||||
if( focusNew!=null ) focusNew.requestFocus();
|
||||
} else if( primaryCode==CodeNext ) {
|
||||
View focusNew= edittext.focusSearch(View.FOCUS_RIGHT);
|
||||
if( focusNew!=null ) focusNew.requestFocus();
|
||||
} else { // insert character
|
||||
editable.insert(start, Character.toString((char) primaryCode));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPress(int arg0) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRelease(int primaryCode) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onText(CharSequence text) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void swipeDown() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void swipeLeft() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void swipeRight() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void swipeUp() {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a custom keyboard, that uses the KeyboardView (with resource id <var>viewid</var>) of the <var>host</var> activity,
|
||||
* and load the keyboard layout from xml file <var>layoutid</var> (see {@link Keyboard} for description).
|
||||
* Note that the <var>host</var> activity must have a <var>KeyboardView</var> in its layout (typically aligned with the bottom of the activity).
|
||||
* Note that the keyboard layout xml file may include key codes for navigation; see the constants in this class for their values.
|
||||
* Note that to enable EditText's to use this custom keyboard, call the {@link #registerEditText(int)}.
|
||||
*
|
||||
* @param host The hosting activity.
|
||||
* @param viewid The id of the KeyboardView.
|
||||
* @param layoutid The id of the xml file containing the keyboard layout.
|
||||
*/
|
||||
public HexKeyboard(Activity host, int viewid, int layoutid) {
|
||||
mHostActivity= host;
|
||||
mKeyboardView= (KeyboardView)mHostActivity.findViewById(viewid);
|
||||
mKeyboardView.setKeyboard(new Keyboard(mHostActivity, layoutid));
|
||||
mKeyboardView.setPreviewEnabled(false); // NOTE Do not show the preview balloons
|
||||
mKeyboardView.setOnKeyboardActionListener(mOnKeyboardActionListener);
|
||||
// Hide the standard keyboard initially
|
||||
mHostActivity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||
}
|
||||
|
||||
/** Returns whether the CustomKeyboard is visible. */
|
||||
public boolean isCustomKeyboardVisible() {
|
||||
return mKeyboardView.getVisibility() == View.VISIBLE;
|
||||
}
|
||||
|
||||
/** Make the CustomKeyboard visible, and hide the system keyboard for view v. */
|
||||
public void showCustomKeyboard( View v ) {
|
||||
mKeyboardView.setVisibility(View.VISIBLE);
|
||||
mKeyboardView.setEnabled(true);
|
||||
if( v!=null ) ((InputMethodManager)mHostActivity.getSystemService(Activity.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
/** Make the CustomKeyboard invisible. */
|
||||
public void hideCustomKeyboard() {
|
||||
mKeyboardView.setVisibility(View.GONE);
|
||||
mKeyboardView.setEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register <var>EditText<var> with resource id <var>resid</var> (on the hosting activity) for using this custom keyboard.
|
||||
*
|
||||
* @param resid The resource id of the EditText that registers to the custom keyboard.
|
||||
*/
|
||||
public void registerEditText(int resid) {
|
||||
// Find the EditText 'resid'
|
||||
EditText edittext= (EditText)mHostActivity.findViewById(resid);
|
||||
// Make the custom keyboard appear
|
||||
edittext.setOnFocusChangeListener(new OnFocusChangeListener() {
|
||||
// NOTE By setting the on focus listener, we can show the custom keyboard when the edit box gets focus, but also hide it when the edit box loses focus
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (hasFocus) showCustomKeyboard(v);
|
||||
else hideCustomKeyboard();
|
||||
}
|
||||
});
|
||||
edittext.setOnClickListener(new OnClickListener() {
|
||||
// NOTE By setting the on click listener, we can show the custom keyboard again, by tapping on an edit box that already had focus (but that had the keyboard hidden).
|
||||
@Override public void onClick(View v) {
|
||||
showCustomKeyboard(v);
|
||||
}
|
||||
});
|
||||
// Disable standard keyboard hard way
|
||||
// NOTE There is also an easy way: 'edittext.setInputType(InputType.TYPE_NULL)' (but you will not have a cursor, and no 'edittext.setCursorVisible(true)' doesn't work )
|
||||
edittext.setOnTouchListener(new OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
EditText edittext = (EditText) v;
|
||||
int inType = edittext.getInputType(); // Backup the input type
|
||||
edittext.setInputType(InputType.TYPE_NULL); // Disable standard keyboard
|
||||
edittext.onTouchEvent(event); // Call native handler
|
||||
edittext.setInputType(inType); // Restore input type
|
||||
return true; // Consume touch event
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
// Disable spell check (hex strings look like words to Android)
|
||||
edittext.setInputType(edittext.getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.zerotier.one.ui;
|
||||
|
||||
import android.app.Fragment;
|
||||
|
||||
/**
|
||||
* Created by Grant on 5/20/2015.
|
||||
*/
|
||||
public class JoinNetworkActivity extends SingleFragmentActivity {
|
||||
public Fragment createFragment() {
|
||||
return new JoinNetworkFragment();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package com.zerotier.one.ui;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import com.google.android.gms.analytics.HitBuilders;
|
||||
import com.google.android.gms.analytics.Tracker;
|
||||
|
||||
import com.zerotier.one.AnalyticsApplication;
|
||||
import com.zerotier.one.R;
|
||||
import com.zerotier.one.events.JoinNetworkEvent;
|
||||
import com.zerotier.one.util.NetworkIdUtils;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import de.greenrobot.event.EventBus;
|
||||
|
||||
/**
|
||||
* Created by Grant on 5/20/2015.
|
||||
*/
|
||||
public class JoinNetworkFragment extends Fragment {
|
||||
public final static String TAG = "JoinNetwork";
|
||||
|
||||
private Tracker tracker = null;
|
||||
|
||||
private Button mJoinButton;
|
||||
private EditText mNetworkIdTextView;
|
||||
private ListView mRecentNetworksList;
|
||||
|
||||
EventBus eventBus = EventBus.getDefault();
|
||||
|
||||
HexKeyboard mHexKeyboard;
|
||||
|
||||
public JoinNetworkFragment() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if(tracker == null) {
|
||||
tracker = ((AnalyticsApplication) getActivity().getApplication()).getDefaultTracker();
|
||||
}
|
||||
|
||||
tracker.setScreenName("Join Network");
|
||||
tracker.send(new HitBuilders.ScreenViewBuilder().build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if(tracker == null) {
|
||||
tracker = ((AnalyticsApplication) getActivity().getApplication()).getDefaultTracker();
|
||||
}
|
||||
|
||||
tracker.setScreenName("Join Network");
|
||||
tracker.send(new HitBuilders.ScreenViewBuilder().build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mHexKeyboard = new HexKeyboard(getActivity(), R.id.join_network_keyboard, R.xml.hex_keyboard);
|
||||
mHexKeyboard.registerEditText(R.id.join_network_edit_text);
|
||||
mHexKeyboard.hideCustomKeyboard();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, parent, savedInstanceState);
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_join_network, parent, false);
|
||||
|
||||
mNetworkIdTextView = (EditText)v.findViewById(R.id.join_network_edit_text);
|
||||
mNetworkIdTextView.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
if (s.toString().length() == 16) {
|
||||
mJoinButton.setEnabled(true);
|
||||
} else {
|
||||
mJoinButton.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
mJoinButton = (Button)v.findViewById(R.id.button_join_network);
|
||||
mJoinButton.setEnabled(false);
|
||||
mJoinButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
try {
|
||||
Log.d(TAG, "Joining network " + mNetworkIdTextView.getText().toString());
|
||||
String netIdString = mNetworkIdTextView.getText().toString();
|
||||
long networkId = NetworkIdUtils.hexStringToLong(netIdString);
|
||||
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences("recent_networks", Context.MODE_PRIVATE);
|
||||
try {
|
||||
String nws = prefs.getString("recent_networks", (new JSONArray()).toString());
|
||||
JSONArray jArray = new JSONArray(nws);
|
||||
ArrayList<String> array = new ArrayList<>();
|
||||
|
||||
// convert the JSON array to an actual array for ease of modification
|
||||
for(int i = 0; i < jArray.length(); ++i) {
|
||||
array.add(jArray.getString(i));
|
||||
}
|
||||
|
||||
boolean containsValue = false;
|
||||
|
||||
for(String id : array) {
|
||||
if(id.equals(netIdString)) {
|
||||
containsValue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(containsValue) {
|
||||
// remove the item
|
||||
array.remove(netIdString);
|
||||
} else {
|
||||
// pop off the last item
|
||||
if (array.size() > 4) {
|
||||
array.remove(4);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// insert ID at the beginning of the list
|
||||
array.add(0, mNetworkIdTextView.getText().toString());
|
||||
|
||||
// convert the list back to a JSON array
|
||||
jArray = new JSONArray();
|
||||
for(String id : array) {
|
||||
jArray.put(id);
|
||||
}
|
||||
|
||||
// write JSON array to preferences
|
||||
SharedPreferences.Editor e = prefs.edit();
|
||||
e.putString("recent_networks", jArray.toString());
|
||||
e.apply();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exception setting recent networks: " + e.getMessage());
|
||||
}
|
||||
eventBus.post(new JoinNetworkEvent(networkId));
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
Log.d(TAG, "Join Network: Error parsing network ID");
|
||||
} finally {
|
||||
getActivity().onBackPressed();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mRecentNetworksList = (ListView) v.findViewById(R.id.recent_networks_list);
|
||||
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences("recent_networks", Context.MODE_PRIVATE);
|
||||
try {
|
||||
String nws = prefs.getString("recent_networks", new JSONArray().toString());
|
||||
JSONArray networks = new JSONArray(nws);
|
||||
|
||||
|
||||
TextView recentNetworksText = (TextView) v.findViewById(R.id.recent_networks);
|
||||
|
||||
if (networks.length() == 0) {
|
||||
mRecentNetworksList.setVisibility(View.GONE);
|
||||
recentNetworksText.setVisibility(View.GONE);
|
||||
} else {
|
||||
mRecentNetworksList.setVisibility(View.VISIBLE);
|
||||
recentNetworksText.setVisibility(View.VISIBLE);
|
||||
|
||||
ArrayList<String> recentNetworks = new ArrayList<>();
|
||||
for(int i = 0; i < networks.length(); ++i) {
|
||||
recentNetworks.add(networks.getString(i));
|
||||
}
|
||||
|
||||
final NetworkIDAdapter adapter = new NetworkIDAdapter(recentNetworks);
|
||||
mRecentNetworksList.setAdapter(adapter);
|
||||
|
||||
mRecentNetworksList.setOnItemClickListener(new ListView.OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
mNetworkIdTextView.setText(adapter.getItem(position));
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "JSON Error: " + e.getMessage());
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
private class NetworkIDAdapter extends ArrayAdapter<String> {
|
||||
public NetworkIDAdapter(ArrayList<String> config) {
|
||||
super(getActivity(), 0, config);
|
||||
Log.d(TAG, "Created network list item adapter");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
if(convertView == null) {
|
||||
convertView = getActivity().getLayoutInflater().inflate(R.layout.list_item_recent_network, null);
|
||||
}
|
||||
|
||||
String networkId = getItem(position);
|
||||
TextView network = (TextView) convertView.findViewById(R.id.list_recent_network_id);
|
||||
network.setText(networkId);
|
||||
|
||||
return convertView;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.zerotier.one.ui;
|
||||
|
||||
import android.app.Fragment;
|
||||
|
||||
/**
|
||||
* Created by Grant on 5/20/2015.
|
||||
*/
|
||||
public class NetworkListActivity extends SingleFragmentActivity {
|
||||
@Override
|
||||
public Fragment createFragment() {
|
||||
return new NetworkListFragment();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,487 @@
|
||||
package com.zerotier.one.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.gms.analytics.HitBuilders;
|
||||
import com.google.android.gms.analytics.Tracker;
|
||||
import com.zerotier.one.AnalyticsApplication;
|
||||
import com.zerotier.one.R;
|
||||
import com.zerotier.one.events.LeaveNetworkEvent;
|
||||
import com.zerotier.one.events.ManualDisconnectEvent;
|
||||
import com.zerotier.one.events.NetworkInfoReplyEvent;
|
||||
import com.zerotier.one.events.NetworkListReplyEvent;
|
||||
import com.zerotier.one.events.NetworkRemovedEvent;
|
||||
import com.zerotier.one.events.NodeDestroyedEvent;
|
||||
import com.zerotier.one.events.NodeIDEvent;
|
||||
import com.zerotier.one.events.NodeStatusEvent;
|
||||
import com.zerotier.one.events.RequestNetworkListEvent;
|
||||
import com.zerotier.one.events.RequestNodeStatusEvent;
|
||||
import com.zerotier.one.events.StopEvent;
|
||||
import com.zerotier.one.service.RuntimeService;
|
||||
import com.zerotier.one.service.ZeroTierOneService;
|
||||
import com.zerotier.sdk.NodeStatus;
|
||||
import com.zerotier.sdk.VirtualNetworkConfig;
|
||||
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
import de.greenrobot.event.EventBus;
|
||||
|
||||
public class NetworkListFragment extends Fragment {
|
||||
enum ConnectStatus {
|
||||
CONNECTED,
|
||||
DISCONNECTED
|
||||
}
|
||||
|
||||
private Tracker tracker = null;
|
||||
|
||||
public static final String TAG = "NetworkListFragment";
|
||||
|
||||
public static final int START_VPN = 2;
|
||||
|
||||
private ArrayList<VirtualNetworkConfig> mNetworkConfigs;
|
||||
|
||||
private EventBus eventBus;
|
||||
private NetworkAdapter adapter;
|
||||
private ListView listView;
|
||||
private TextView nodeIdView;
|
||||
private TextView nodeStatusView;
|
||||
private Button connectButton;
|
||||
private MenuItem joinNetworkMenu;
|
||||
|
||||
private ConnectStatus cstatus = ConnectStatus.DISCONNECTED;
|
||||
|
||||
/**
|
||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
||||
* fragment (e.g. upon screen orientation changes).
|
||||
*/
|
||||
public NetworkListFragment() {
|
||||
Log.d(TAG, "Network List Fragment created");
|
||||
mNetworkConfigs = new ArrayList<VirtualNetworkConfig>();
|
||||
eventBus = EventBus.getDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
eventBus.register(this);
|
||||
eventBus.post(new RequestNetworkListEvent());
|
||||
eventBus.post(new RequestNodeStatusEvent());
|
||||
}
|
||||
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
eventBus.unregister(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, parent, savedInstanceState);
|
||||
|
||||
View v = inflater.inflate(R.layout.network_list_fragment, parent, false);
|
||||
|
||||
listView = (ListView)v.findViewById(R.id.joined_networks_list);
|
||||
|
||||
adapter = new NetworkAdapter(mNetworkConfigs);
|
||||
listView.setAdapter(adapter);
|
||||
|
||||
nodeIdView = (TextView) v.findViewById(R.id.node_id);
|
||||
nodeStatusView = (TextView) v.findViewById(R.id.node_status);
|
||||
connectButton = (Button) v.findViewById(R.id.connect_button);
|
||||
|
||||
if(cstatus == ConnectStatus.CONNECTED) {
|
||||
connectButton.setText(getResources().getText(R.string.button_disconnect));
|
||||
} else {
|
||||
connectButton.setText(getResources().getText(R.string.button_connect));
|
||||
}
|
||||
|
||||
connectButton.setOnClickListener(new Button.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (cstatus == ConnectStatus.CONNECTED) {
|
||||
Intent i = new Intent(getActivity(), RuntimeService.class);
|
||||
i.putExtra(RuntimeService.START_TYPE, RuntimeService.STOP_USER_INTERFACE);
|
||||
getActivity().startService(i);
|
||||
cstatus = ConnectStatus.DISCONNECTED;
|
||||
nodeStatusView.setText("OFFLINE");
|
||||
connectButton.setText(getResources().getText(R.string.button_connect));
|
||||
|
||||
} else {
|
||||
sendStartServiceIntent();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
private void sendStartServiceIntent() {
|
||||
Intent i = ZeroTierOneService.prepare(getActivity());
|
||||
if(i != null) {
|
||||
startActivityForResult(i, START_VPN);
|
||||
} else {
|
||||
Log.d(TAG, "Intent is NULL. Already approved.");
|
||||
startService();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
Log.d(TAG, "NetworkListFragment.onCreate");
|
||||
super.onCreate(savedInstanceState);
|
||||
PreferenceManager.setDefaultValues(getActivity(), R.xml.preferences, false);
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
if(tracker == null) {
|
||||
tracker = ((AnalyticsApplication) getActivity().getApplication()).getDefaultTracker();
|
||||
}
|
||||
|
||||
tracker.setScreenName("Network List");
|
||||
tracker.send(new HitBuilders.ScreenViewBuilder().build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
cstatus = ConnectStatus.DISCONNECTED;
|
||||
connectButton.setText(getResources().getText(R.string.button_connect));
|
||||
nodeStatusView.setText("OFFLINE");
|
||||
mNetworkConfigs.clear();
|
||||
sortNetworkListAndNotify();
|
||||
|
||||
eventBus.post(new RequestNetworkListEvent());
|
||||
eventBus.post(new RequestNodeStatusEvent());
|
||||
|
||||
if(tracker == null) {
|
||||
tracker = ((AnalyticsApplication) getActivity().getApplication()).getDefaultTracker();
|
||||
}
|
||||
tracker.setScreenName("Network List");
|
||||
tracker.send(new HitBuilders.ScreenViewBuilder().build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
Log.d(TAG, "NetworkListFragment.onCreateOptionsMenu");
|
||||
inflater.inflate(R.menu.menu_network_list, menu);
|
||||
joinNetworkMenu = menu.findItem(R.id.menu_item_join_network);
|
||||
joinNetworkMenu.setEnabled(false);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
eventBus.post(new RequestNodeStatusEvent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_item_join_network: {
|
||||
Log.d(TAG, "Selected Join Network");
|
||||
Intent i = new Intent(getActivity(), JoinNetworkActivity.class);
|
||||
startActivity(i);
|
||||
return true;
|
||||
}
|
||||
case R.id.menu_item_prefs: {
|
||||
Log.d(TAG, "Selected Preferences");
|
||||
Intent i = new Intent(getActivity(), PrefsActivity.class);
|
||||
startActivity(i);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
|
||||
switch(requestCode)
|
||||
{
|
||||
case START_VPN:
|
||||
{
|
||||
// Start ZeroTierOneService
|
||||
startService();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
Log.d(TAG, "NetworkListFragment.onAttach");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
Log.d(TAG, "NetworkListFragment.onDetach");
|
||||
super.onDetach();
|
||||
}
|
||||
|
||||
public void onEventMainThread(NetworkListReplyEvent e) {
|
||||
Log.d(TAG, "Got network list");
|
||||
mNetworkConfigs.clear();
|
||||
for(int i = 0; i < e.getNetworkList().length; ++i) {
|
||||
mNetworkConfigs.add(e.getNetworkList()[i]);
|
||||
}
|
||||
|
||||
sortNetworkListAndNotify();
|
||||
}
|
||||
|
||||
public void onEventMainThread(NetworkInfoReplyEvent e) {
|
||||
Log.d(TAG, "Got Network Info");
|
||||
VirtualNetworkConfig vnc = e.getNetworkInfo();
|
||||
boolean hasNetworkWithId = false;
|
||||
|
||||
for(VirtualNetworkConfig c : mNetworkConfigs) {
|
||||
if(c.networkId() == vnc.networkId()) {
|
||||
hasNetworkWithId = true;
|
||||
int index = mNetworkConfigs.indexOf(c);
|
||||
mNetworkConfigs.set(index, vnc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!hasNetworkWithId) {
|
||||
mNetworkConfigs.add(vnc);
|
||||
}
|
||||
|
||||
sortNetworkListAndNotify();
|
||||
}
|
||||
|
||||
public void onEventMainThread(NetworkRemovedEvent e) {
|
||||
Log.d(TAG, "Removing network: " + Long.toHexString(e.getNetworkId()));
|
||||
for(VirtualNetworkConfig c : mNetworkConfigs) {
|
||||
if(c.networkId() == e.getNetworkId()) {
|
||||
mNetworkConfigs.remove(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sortNetworkListAndNotify();
|
||||
}
|
||||
|
||||
public void onEventMainThread(NodeIDEvent e) {
|
||||
long nodeId = e.getNodeId();
|
||||
String nodeHex = Long.toHexString(nodeId);
|
||||
while(nodeHex.length() < 10) {
|
||||
nodeHex = "0" + nodeHex;
|
||||
}
|
||||
nodeIdView.setText(nodeHex);
|
||||
}
|
||||
|
||||
public void onEventMainThread(NodeStatusEvent e) {
|
||||
NodeStatus status = e.getStatus();
|
||||
if(status.isOnline()) {
|
||||
nodeStatusView.setText("ONLINE");
|
||||
if(joinNetworkMenu != null) {
|
||||
joinNetworkMenu.setEnabled(true);
|
||||
}
|
||||
cstatus = ConnectStatus.CONNECTED;
|
||||
if(connectButton != null) {
|
||||
connectButton.setText(getResources().getText(R.string.button_disconnect));
|
||||
}
|
||||
if(nodeIdView != null) {
|
||||
nodeIdView.setText(Long.toHexString(status.getAddres()));
|
||||
}
|
||||
} else {
|
||||
setOfflineState();
|
||||
}
|
||||
}
|
||||
|
||||
public void onEventMainThread(NodeDestroyedEvent e) {
|
||||
setOfflineState();
|
||||
}
|
||||
|
||||
private void setOfflineState() {
|
||||
if(nodeStatusView != null) {
|
||||
nodeStatusView.setText("OFFLINE");
|
||||
}
|
||||
if(joinNetworkMenu != null) {
|
||||
joinNetworkMenu.setEnabled(false);
|
||||
}
|
||||
cstatus = ConnectStatus.DISCONNECTED;
|
||||
if(connectButton != null) {
|
||||
connectButton.setText(getResources().getText(R.string.button_connect));
|
||||
}
|
||||
}
|
||||
|
||||
private void sortNetworkListAndNotify() {
|
||||
Collections.sort(mNetworkConfigs);
|
||||
adapter.notifyDataSetChanged();
|
||||
listView.invalidateViews();
|
||||
}
|
||||
|
||||
private void startService() {
|
||||
Intent i = new Intent(getActivity(), RuntimeService.class);
|
||||
i.putExtra(RuntimeService.START_TYPE, RuntimeService.START_USER_INTERFACE);
|
||||
getActivity().startService(i);
|
||||
}
|
||||
|
||||
private class NetworkAdapter extends ArrayAdapter<VirtualNetworkConfig> {
|
||||
|
||||
public NetworkAdapter(ArrayList<VirtualNetworkConfig> config) {
|
||||
super(getActivity(), 0, config);
|
||||
Log.d(TAG, "Created network list item adapter");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
if(!listView.getItemsCanFocus()) {
|
||||
listView.setItemsCanFocus(true);
|
||||
}
|
||||
|
||||
if(convertView == null) {
|
||||
convertView = getActivity().getLayoutInflater().inflate(R.layout.list_item_network, null);
|
||||
}
|
||||
|
||||
VirtualNetworkConfig vnc = getItem(position);
|
||||
TextView networkId = (TextView)convertView.findViewById(R.id.network_list_network_id);
|
||||
networkId.setText(Long.toHexString(vnc.networkId()));
|
||||
|
||||
TextView networkName = (TextView) convertView.findViewById(R.id.network_list_network_name);
|
||||
networkName.setText(vnc.name());
|
||||
|
||||
|
||||
TextView networkStatus = (TextView)convertView.findViewById(R.id.network_status_textview);
|
||||
|
||||
CharSequence statusText;
|
||||
switch(vnc.networkStatus()) {
|
||||
case NETWORK_STATUS_OK:
|
||||
statusText = getResources().getText(R.string.network_status_ok);
|
||||
break;
|
||||
case NETWORK_STATUS_ACCESS_DENIED:
|
||||
statusText = getResources().getText(R.string.network_status_access_denied);
|
||||
break;
|
||||
case NETWORK_STATUS_CLIENT_TOO_OLD:
|
||||
statusText = getResources().getText(R.string.network_status_client_too_old);
|
||||
break;
|
||||
case NETWORK_STATUS_NOT_FOUND:
|
||||
statusText = getResources().getText(R.string.network_status_not_found);
|
||||
break;
|
||||
case NETWORK_STATUS_PORT_ERROR:
|
||||
statusText = getResources().getText(R.string.network_status_port_error);
|
||||
break;
|
||||
case NETWORK_STATUS_REQUESTING_CONFIGURATION:
|
||||
statusText = getResources().getText(R.string.network_status_requesting_configuration);
|
||||
break;
|
||||
default:
|
||||
statusText = getResources().getText(R.string.network_status_unknown);
|
||||
break;
|
||||
}
|
||||
networkStatus.setText(statusText);
|
||||
|
||||
TextView networkType = (TextView)convertView.findViewById(R.id.network_type_textview);
|
||||
switch(vnc.networkType()) {
|
||||
case NETWORK_TYPE_PUBLIC:
|
||||
networkType.setText(getResources().getText(R.string.network_type_public));
|
||||
break;
|
||||
case NETWORK_TYPE_PRIVATE:
|
||||
networkType.setText(getResources().getText(R.string.network_type_private));
|
||||
break;
|
||||
default:
|
||||
networkType.setText(getResources().getText(R.string.network_type_unknown));
|
||||
}
|
||||
|
||||
String mac = Long.toHexString(vnc.macAddress());
|
||||
while(mac.length() < 12) {
|
||||
mac = "0" + mac;
|
||||
}
|
||||
|
||||
StringBuilder displayMac = new StringBuilder();
|
||||
displayMac.append(mac.charAt(0));
|
||||
displayMac.append(mac.charAt(1));
|
||||
displayMac.append(':');
|
||||
displayMac.append(mac.charAt(2));
|
||||
displayMac.append(mac.charAt(3));
|
||||
displayMac.append(':');
|
||||
displayMac.append(mac.charAt(4));
|
||||
displayMac.append(mac.charAt(5));
|
||||
displayMac.append(':');
|
||||
displayMac.append(mac.charAt(6));
|
||||
displayMac.append(mac.charAt(7));
|
||||
displayMac.append(':');
|
||||
displayMac.append(mac.charAt(8));
|
||||
displayMac.append(mac.charAt(9));
|
||||
displayMac.append(':');
|
||||
displayMac.append(mac.charAt(10));
|
||||
displayMac.append(mac.charAt(11));
|
||||
|
||||
TextView macView = (TextView)convertView.findViewById(R.id.network_mac_textview);
|
||||
macView.setText(displayMac.toString());
|
||||
|
||||
TextView mtuView = (TextView) convertView.findViewById(R.id.network_mtu_textview);
|
||||
mtuView.setText(Integer.toString(vnc.mtu()));
|
||||
|
||||
TextView broadcastEnabledView = (TextView) convertView.findViewById(R.id.network_broadcast_textview);
|
||||
broadcastEnabledView.setText(vnc.broadcastEnabled() ? "Enabled" : "Disabled");
|
||||
|
||||
TextView bridgingEnabledView = (TextView) convertView.findViewById(R.id.network_bridging_textview);
|
||||
bridgingEnabledView.setText(vnc.isBridgeEnabled() ? "Enabled" : "Disabled");
|
||||
|
||||
InetSocketAddress[] addresses = vnc.assignedAddresses();
|
||||
StringBuilder addrText = new StringBuilder();
|
||||
for(InetSocketAddress addr : addresses) {
|
||||
InetAddress ipaddr = addr.getAddress();
|
||||
if(ipaddr instanceof Inet6Address) {
|
||||
continue;
|
||||
}
|
||||
String address = addr.toString();
|
||||
if(address.startsWith("/")) {
|
||||
address = address.substring(1);
|
||||
}
|
||||
int lastSlashPosition = address.lastIndexOf(':');
|
||||
address = address.substring(0, lastSlashPosition) + "/" + address.substring(lastSlashPosition+1, address.length());
|
||||
addrText.append(address);
|
||||
addrText.append('\n');
|
||||
}
|
||||
|
||||
TextView addressesView = (TextView) convertView.findViewById(R.id.network_ipaddresses_textview);
|
||||
addressesView.setText(addrText.toString());
|
||||
|
||||
Button leaveButton = (Button) convertView.findViewById(R.id.network_leave_button);
|
||||
leaveButton.setOnClickListener(new LeaveButtonClickListener(vnc.networkId()));
|
||||
|
||||
return convertView;
|
||||
}
|
||||
}
|
||||
|
||||
private class LeaveButtonClickListener implements Button.OnClickListener {
|
||||
|
||||
private long nwid;
|
||||
|
||||
public LeaveButtonClickListener(long nwid) {
|
||||
this.nwid = nwid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Log.d(TAG, "Leave Button Pressed for network: " + Long.toHexString(nwid));
|
||||
EventBus.getDefault().post(new LeaveNetworkEvent(nwid));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.zerotier.one.ui;
|
||||
|
||||
import android.app.Fragment;
|
||||
|
||||
/**
|
||||
* Created by Grant on 7/7/2015.
|
||||
*/
|
||||
public class PrefsActivity extends SingleFragmentActivity {
|
||||
@Override
|
||||
public Fragment createFragment() {
|
||||
return new PrefsFragment();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.zerotier.one.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceFragment;
|
||||
|
||||
import com.google.android.gms.analytics.HitBuilders;
|
||||
import com.google.android.gms.analytics.Tracker;
|
||||
import com.zerotier.one.AnalyticsApplication;
|
||||
import com.zerotier.one.R;
|
||||
import com.zerotier.one.service.ZeroTierOneService;
|
||||
|
||||
/**
|
||||
* Created by Grant on 7/7/2015.
|
||||
*/
|
||||
public class PrefsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private Tracker tracker = null;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
|
||||
if(tracker == null) {
|
||||
tracker = ((AnalyticsApplication) getActivity().getApplication()).getDefaultTracker();
|
||||
}
|
||||
|
||||
tracker.setScreenName("Preferences");
|
||||
tracker.send(new HitBuilders.ScreenViewBuilder().build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if(tracker == null) {
|
||||
tracker = ((AnalyticsApplication) getActivity().getApplication()).getDefaultTracker();
|
||||
}
|
||||
|
||||
tracker.setScreenName("Preferences");
|
||||
tracker.send(new HitBuilders.ScreenViewBuilder().build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if(key.equals("network_use_cellular_data")) {
|
||||
if(sharedPreferences.getBoolean("network_use_cellular_data", false)) {
|
||||
Intent i = new Intent(getActivity(), ZeroTierOneService.class);
|
||||
getActivity().startService(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.zerotier.one.ui;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import com.zerotier.one.R;
|
||||
|
||||
/**
|
||||
* Created by Grant on 5/16/2015.
|
||||
*/
|
||||
public abstract class SingleFragmentActivity extends AppCompatActivity {
|
||||
protected abstract Fragment createFragment();
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_fragment);
|
||||
|
||||
FragmentManager fm = getFragmentManager();
|
||||
Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);
|
||||
|
||||
if(fragment == null) {
|
||||
fragment = createFragment();
|
||||
fm.beginTransaction()
|
||||
.add(R.id.fragmentContainer, fragment)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.zerotier.one.util;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* Created by Grant on 6/19/2015.
|
||||
*/
|
||||
public class IPPacketUtils {
|
||||
private static final String TAG = "IPPacketUtils";
|
||||
|
||||
public static InetAddress getSourceIP(byte[] data) {
|
||||
byte[] addrBuffer = new byte[4];
|
||||
System.arraycopy(data, 12, addrBuffer, 0, 4);
|
||||
try {
|
||||
return InetAddress.getByAddress(addrBuffer);
|
||||
} catch (UnknownHostException e) {
|
||||
Log.e(TAG, "Error creating InetAddress: " + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static InetAddress getDestIP(byte[] data) {
|
||||
byte[] addrBuffer = new byte[4];
|
||||
System.arraycopy(data, 16, addrBuffer, 0, 4);
|
||||
try {
|
||||
return InetAddress.getByAddress(addrBuffer);
|
||||
} catch (UnknownHostException e) {
|
||||
Log.e(TAG, "Error creating InetAddress: " + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the 1's complement checksum of a region of bytes
|
||||
*
|
||||
* @param buffer
|
||||
* @param startValue
|
||||
* @param startByte
|
||||
* @param endByte
|
||||
* @return
|
||||
*/
|
||||
public static long calculateChecksum(byte[] buffer, long startValue, int startByte, int endByte) {
|
||||
|
||||
int length = endByte - startByte;
|
||||
int i = startByte;
|
||||
long sum = startValue;
|
||||
long data;
|
||||
|
||||
// Handle all pairs
|
||||
while (length > 1) {
|
||||
data = (((buffer[i] << 8) & 0xFF00) | ((buffer[i + 1]) & 0xFF));
|
||||
sum += data;
|
||||
// 1's complement carry bit correction in 16-bits (detecting sign extension)
|
||||
if ((sum & 0xFFFF0000) > 0) {
|
||||
sum = sum & 0xFFFF;
|
||||
sum += 1;
|
||||
}
|
||||
|
||||
i += 2;
|
||||
length -= 2;
|
||||
}
|
||||
|
||||
// Handle remaining byte in odd length buffers
|
||||
if (length > 0) {
|
||||
sum += (buffer[i] << 8 & 0xFF00);
|
||||
// 1's complement carry bit correction in 16-bits (detecting sign extension)
|
||||
if ((sum & 0xFFFF0000) > 0) {
|
||||
sum = sum & 0xFFFF;
|
||||
sum += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Final 1's complement value correction to 16-bits
|
||||
sum = ~sum;
|
||||
sum = sum & 0xFFFF;
|
||||
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.zerotier.one.util;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Created by Grant on 6/13/2015.
|
||||
*/
|
||||
public class InetAddressUtils {
|
||||
public static final String TAG = "InetAddressUtils";
|
||||
|
||||
public static byte[] addressToNetmask(InetAddress addr, int prefix) {
|
||||
int numAddressBytes = addr.getAddress().length;
|
||||
if(numAddressBytes > 4) {
|
||||
throw new UnsupportedOperationException("IPv6 is not yet supported");
|
||||
}
|
||||
int numAddressBits = numAddressBytes * 8;
|
||||
|
||||
byte[] maskBytes = new byte[numAddressBytes];
|
||||
for(int i = 0; i < numAddressBytes; ++i) {
|
||||
maskBytes[i] = (byte)255;
|
||||
}
|
||||
int mask = ByteBuffer.wrap(maskBytes).getInt();
|
||||
|
||||
mask = mask >> (numAddressBits - prefix);
|
||||
mask = mask << (numAddressBits - prefix);
|
||||
|
||||
return ByteBuffer.allocate(4).putInt(mask).array();
|
||||
}
|
||||
|
||||
public static InetAddress addressToRoute(InetAddress addr, int prefix) {
|
||||
byte[] maskBytes = addressToNetmask(addr, prefix);
|
||||
byte[] routeBytes = new byte[maskBytes.length];
|
||||
InetAddress route = null;
|
||||
try {
|
||||
for(int i = 0; i < maskBytes.length; ++i) {
|
||||
routeBytes[i] = (byte)(addr.getAddress()[i] & maskBytes[i]);
|
||||
}
|
||||
route = InetAddress.getByAddress(routeBytes);
|
||||
|
||||
} catch (UnknownHostException e) {
|
||||
Log.e(TAG, "Uknown Host Exception calculating route");
|
||||
}
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.zerotier.one.util;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Created by Grant on 5/26/2015.
|
||||
*/
|
||||
public class NetworkIdUtils {
|
||||
/**
|
||||
* Long will not parse a 64-bit hex string when the highest bit is 1 for some reasoon. So
|
||||
* for converting network IDs in hex to a long, we must use big integer.
|
||||
*
|
||||
* @param hexString
|
||||
* @return
|
||||
*/
|
||||
public static long hexStringToLong(String hexString) {
|
||||
BigInteger value = new BigInteger(hexString, 16);
|
||||
return value.longValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true" >
|
||||
<shape>
|
||||
<solid
|
||||
android:color="#ffb354" />
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="#000000" />
|
||||
<corners
|
||||
android:radius="3dp" />
|
||||
<padding
|
||||
android:left="5dp"
|
||||
android:top="5dp"
|
||||
android:right="5dp"
|
||||
android:bottom="5dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:state_focused="true" >
|
||||
<shape>
|
||||
<solid
|
||||
android:color="#ffb354" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#000000" />
|
||||
<corners
|
||||
android:radius="3dp" />
|
||||
<padding
|
||||
android:left="5dp"
|
||||
android:top="5dp"
|
||||
android:right="5dp"
|
||||
android:bottom="5dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<shape>
|
||||
<solid
|
||||
android:color="#ffb354" />
|
||||
<stroke
|
||||
android:width="0dp"
|
||||
android:color="#000000" />
|
||||
<corners
|
||||
android:radius="3dp" />
|
||||
<padding
|
||||
android:left="5dp"
|
||||
android:top="5dp"
|
||||
android:right="5dp"
|
||||
android:bottom="5dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
|
After Width: | Height: | Size: 689 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 624 B |
@@ -0,0 +1,4 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/fragmentContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/fragmentJoinNetwork"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/network_id_textview"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/network_id_string"
|
||||
android:textStyle="bold"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
style="?android:listSeparatorTextViewStyle"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/join_network_edit_text"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:digits="0123456789ABCDEF"
|
||||
android:inputType="textCapCharacters"
|
||||
android:layout_below="@id/network_id_textview"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:maxLength="16"
|
||||
android:textCursorDrawable="@null"
|
||||
android:backgroundTint="#ffb354"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_join_network"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:layout_below="@id/join_network_edit_text"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:paddingLeft="5dp"
|
||||
android:paddingRight="5dp"
|
||||
android:text="@string/join_network"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/recent_networks"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/recent_networks"
|
||||
android:textStyle="bold"
|
||||
android:layout_below="@id/button_join_network"
|
||||
style="?android:listSeparatorTextViewStyle"/>
|
||||
|
||||
<ListView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/recent_networks_list"
|
||||
android:layout_below="@+id/recent_networks"
|
||||
android:layout_centerHorizontal="true" />
|
||||
|
||||
<android.inputmethodservice.KeyboardView
|
||||
android:id="@+id/join_network_keyboard"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,176 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp">
|
||||
|
||||
<TextView android:id="@+id/network_list_network_id"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:text="Network ID"
|
||||
android:fontFamily="monospace"
|
||||
android:textColor="#555555" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
|
||||
<TextView android:id="@+id/network_list_network_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:paddingTop="4dp"
|
||||
android:text="Network Name"
|
||||
android:textColor="#000000" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@android:color/darker_gray"/>
|
||||
|
||||
<TableLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:columnCount="2"
|
||||
android:layout_margin="8dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="end"
|
||||
android:stretchColumns="1">
|
||||
|
||||
<TableRow android:layout_width="fill_parent">
|
||||
<TextView android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/network_status"
|
||||
android:paddingLeft="40dp"
|
||||
android:paddingRight="20dp"
|
||||
android:layout_gravity="right"/>
|
||||
|
||||
<TextView android:id="@+id/network_status_textview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text=""/>
|
||||
</TableRow>
|
||||
|
||||
<TableRow android:layout_width="fill_parent">
|
||||
<TextView android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/network_type"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingLeft="40dp"
|
||||
android:layout_gravity="right"/>
|
||||
|
||||
<TextView android:id="@+id/network_type_textview"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text=""/>
|
||||
</TableRow>
|
||||
|
||||
<TableRow android:layout_width="fill_parent">
|
||||
<TextView android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/network_mac"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingLeft="40dp"
|
||||
android:layout_gravity="right"/>
|
||||
|
||||
<TextView android:id="@+id/network_mac_textview"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text=""/>
|
||||
</TableRow>
|
||||
|
||||
<TableRow android:layout_width="fill_parent">
|
||||
<TextView android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/network_mtu"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingLeft="40dp"
|
||||
android:layout_gravity="right"/>
|
||||
|
||||
<TextView android:id="@+id/network_mtu_textview"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text=""/>
|
||||
</TableRow>
|
||||
|
||||
<TableRow android:layout_width="fill_parent">
|
||||
<TextView android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/network_broadcast"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingLeft="40dp"
|
||||
android:layout_gravity="right"/>
|
||||
|
||||
<TextView android:id="@+id/network_broadcast_textview"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text=""/>
|
||||
</TableRow>
|
||||
|
||||
<TableRow android:layout_width="fill_parent">
|
||||
<TextView android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/network_bridging"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingLeft="40dp"
|
||||
android:layout_gravity="right"/>
|
||||
|
||||
<TextView android:id="@+id/network_bridging_textview"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text=""/>
|
||||
</TableRow>
|
||||
|
||||
<TableRow android:layout_width="fill_parent">
|
||||
<TextView android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/network_ips"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingLeft="40dp"
|
||||
android:layout_gravity="right"/>
|
||||
|
||||
<TextView android:id="@+id/network_ipaddresses_textview"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:gravity="right"
|
||||
android:text=""/>
|
||||
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<View android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/network_leave_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:paddingLeft="5dp"
|
||||
android:paddingRight="5dp"
|
||||
android:text="@string/leave_button_text"
|
||||
/>
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView android:id="@+id/list_recent_network_id"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="5dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/network_list_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<RelativeLayout
|
||||
android:id="@+id/status_bar"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:padding="5dp">
|
||||
|
||||
<TextView android:id="@+id/node_id"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="5dp"
|
||||
android:text=""
|
||||
android:layout_centerVertical="true"
|
||||
android:gravity="center"/>
|
||||
|
||||
<TextView android:id="@+id/node_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="5dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:text="OFFLINE"
|
||||
android:layout_toRightOf="@id/node_id"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/connect_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:padding="5dp"
|
||||
android:text="@string/button_connect"
|
||||
android:layout_alignParentRight="true"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<ListView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@id/status_bar"
|
||||
android:id="@+id/joined_networks_list"
|
||||
android:layout_alignParentTop="true" />
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item android:id="@+id/menu_item_join_network"
|
||||
android:icon="@android:drawable/ic_menu_add"
|
||||
android:title="@string/menu_join_network"
|
||||
app:showAsAction="ifRoom|withText"/>
|
||||
|
||||
<item android:id="@+id/menu_item_prefs"
|
||||
android:icon="@android:drawable/ic_menu_preferences"
|
||||
android:title="@string/settings"
|
||||
app:showAsAction="ifRoom|withText"/>
|
||||
</menu>
|
||||
|
After Width: | Height: | Size: 774 B |
|
After Width: | Height: | Size: 519 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
|
||||
(such as screen margins) for screens with more than 820dp of available width. This
|
||||
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
|
||||
<dimen name="activity_horizontal_margin">64dp</dimen>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
</resources>
|
||||
@@ -0,0 +1,55 @@
|
||||
<resources>
|
||||
<string name="app_name">ZeroTier One</string>
|
||||
<string name="title_activity_zero_tier_one__main">ZeroTier One</string>
|
||||
|
||||
<string name="string_n_a">N/A</string>
|
||||
|
||||
<string name="network_name_string">Network Name:</string>
|
||||
<string name="network_id_string">Network ID:</string>
|
||||
<string name="recent_networks">Recent Networks:</string>
|
||||
<string name="network_type_label">Type:</string>
|
||||
<string name="status_label">Status:</string>
|
||||
<string name="mac_label">MAC:</string>
|
||||
<string name="device_label">Device:</string>
|
||||
<string name="ip_addresses_label">IP Addresses</string>
|
||||
|
||||
<string name="leave_button_text">Leave Network</string>
|
||||
|
||||
<string name="menu_join_network">Join Network</string>
|
||||
<string name="join_network">Join Network</string>
|
||||
|
||||
<string name="button_connect">Connect</string>
|
||||
<string name="button_disconnect">Disconnect</string>
|
||||
|
||||
<string name="network_status">Status</string>
|
||||
<string name="network_type">Type</string>
|
||||
<string name="network_mac">MAC</string>
|
||||
<string name="network_mtu">MTU</string>
|
||||
<string name="network_broadcast">Broadcast</string>
|
||||
<string name="network_bridging">Bridging</string>
|
||||
<string name="network_ips">Managed IPs</string>
|
||||
|
||||
<!-- Network Statuses -->
|
||||
<string name="network_status_ok">OK</string>
|
||||
<string name="network_status_access_denied">Access Denied</string>
|
||||
<string name="network_status_client_too_old">Client Out of Date</string>
|
||||
<string name="network_status_not_found">Network Not Found</string>
|
||||
<string name="network_status_port_error">Port Error</string>
|
||||
<string name="network_status_requesting_configuration">Requesting Configuration</string>
|
||||
<string name="network_status_unknown">Unknown</string>
|
||||
|
||||
<!-- Network Types -->
|
||||
<string name="network_type_public">Public</string>
|
||||
<string name="network_type_private">Private</string>
|
||||
<string name="network_type_unknown">Unknown</string>
|
||||
|
||||
|
||||
<!-- preferences -->
|
||||
<string name="settings">Settings</string>
|
||||
<string name="preferences_general">General Settings</string>
|
||||
<string name="preferences_general_start_on_boot">Start ZeroTier One on Device Startup</string>
|
||||
|
||||
<string name="preferences_network">Network Settings</string>
|
||||
<string name="preferences_network_use_cell_data">Use Cellular Data</string>
|
||||
<string name="preferences_network_auto_rejoin">Automatically Re-join Networks on Start</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,13 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:buttonStyle">@style/button</item>
|
||||
</style>
|
||||
|
||||
<style name="button" parent="@android:style/Widget.Button">
|
||||
<item name="android:background">@drawable/button_style</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- End current session if app sleeps for a period of time -->
|
||||
<integer name="ga_sessionTimeout">300</integer>
|
||||
|
||||
<!-- Enable automatic Activity measurement -->
|
||||
<bool name="ga_autoActivityTracking">false</bool>
|
||||
|
||||
<!-- The property id associated with this analytics tracker -->
|
||||
<string name="ga_trackingId">UA-40176068-3</string>
|
||||
|
||||
<bool name="ga_anonymizeIp">true</bool>
|
||||
|
||||
<!--
|
||||
See Project Structure -> Analytics -> Google Analytics -> Learn More
|
||||
to learn more about configuring this file.
|
||||
-->
|
||||
</resources>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:keyWidth="12.4%p"
|
||||
android:keyHeight="10%p" >
|
||||
|
||||
<Row>
|
||||
<Key android:codes="55" android:keyLabel="7" android:keyEdgeFlags="left" />
|
||||
<Key android:codes="56" android:keyLabel="8" />
|
||||
<Key android:codes="57" android:keyLabel="9" />
|
||||
<Key android:codes="65" android:keyLabel="A" android:horizontalGap="6.25%p" />
|
||||
<Key android:codes="66" android:keyLabel="B" />
|
||||
<Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" android:isRepeatable="true" android:horizontalGap="6.25%p" />
|
||||
<Key android:codes="55006" android:keyLabel="CLR" android:keyEdgeFlags="right"/>
|
||||
</Row>
|
||||
<Row>
|
||||
<Key android:codes="52" android:keyLabel="4" android:keyEdgeFlags="left" />
|
||||
<Key android:codes="53" android:keyLabel="5" />
|
||||
<Key android:codes="54" android:keyLabel="6" />
|
||||
<Key android:codes="67" android:keyLabel="C" android:horizontalGap="6.25%p" />
|
||||
<Key android:codes="68" android:keyLabel="D" />
|
||||
<Key android:codes="55002" android:keyIcon="@drawable/sym_keyboard_left" android:isRepeatable="true" android:horizontalGap="6.25%p" />
|
||||
<Key android:codes="55003" android:keyIcon="@drawable/sym_keyboard_right" android:isRepeatable="true" android:keyEdgeFlags="right" />
|
||||
</Row>
|
||||
<Row>
|
||||
<Key android:codes="49" android:keyLabel="1" android:keyEdgeFlags="left" />
|
||||
<Key android:codes="50" android:keyLabel="2" />
|
||||
<Key android:codes="51" android:keyLabel="3" />
|
||||
<Key android:codes="69" android:keyLabel="E" android:horizontalGap="6.25%p" />
|
||||
<Key android:codes="70" android:keyLabel="F" />
|
||||
<Key android:codes="55001" android:keyIcon="@drawable/sym_keyboard_allleft" android:horizontalGap="6.25%p" />
|
||||
<Key android:codes="55004" android:keyIcon="@drawable/sym_keyboard_allright" android:keyEdgeFlags="right" />
|
||||
</Row>
|
||||
<Row>
|
||||
<Key android:codes="48" android:keyLabel="0" android:keyWidth="25%p" android:horizontalGap="6.25%p" android:keyEdgeFlags="left" />
|
||||
<Key android:codes="-3" android:keyIcon="@drawable/sym_keyboard_done" android:keyWidth="25%p" android:horizontalGap="43.65%p" android:keyEdgeFlags="right" />
|
||||
<!--<Key android:codes="55000" android:keyLabel="PREV" android:horizontalGap="6.25%p" />
|
||||
<Key android:codes="55005" android:keyLabel="NEXT" android:keyEdgeFlags="right" />-->
|
||||
</Row>
|
||||
</Keyboard>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceCategory android:title="@string/preferences_general">
|
||||
<CheckBoxPreference
|
||||
android:key="general_start_zerotier_on_boot"
|
||||
android:title="@string/preferences_general_start_on_boot"
|
||||
android:defaultValue="true"/>
|
||||
<CheckBoxPreference
|
||||
android:key="network_auto_rejoin_networks"
|
||||
android:title="@string/preferences_network_auto_rejoin"
|
||||
android:defaultValue="true"/>
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/preferences_network">
|
||||
<CheckBoxPreference
|
||||
android:key="network_use_cellular_data"
|
||||
android:title="@string/preferences_network_use_cell_data"
|
||||
android:defaultValue="false"/>
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||