diff --git a/app/src/main/java/com/zerotier/sdk/DataStoreGetListener.java b/app/src/main/java/com/zerotier/sdk/DataStoreGetListener.java index 317511e..105b14c 100644 --- a/app/src/main/java/com/zerotier/sdk/DataStoreGetListener.java +++ b/app/src/main/java/com/zerotier/sdk/DataStoreGetListener.java @@ -24,6 +24,7 @@ * redistribute it in a modified binary form, please contact ZeroTier Networks * LLC. Start here: http://www.zerotier.com/ */ + package com.zerotier.sdk; public interface DataStoreGetListener { @@ -48,7 +49,7 @@ public interface DataStoreGetListener { * @param out_buffer buffer to put the object in * @return size of the object */ - public long onDataStoreGet( + long onDataStoreGet( String name, byte[] out_buffer); } diff --git a/app/src/main/java/com/zerotier/sdk/DataStorePutListener.java b/app/src/main/java/com/zerotier/sdk/DataStorePutListener.java index 77e5502..0fa8e19 100644 --- a/app/src/main/java/com/zerotier/sdk/DataStorePutListener.java +++ b/app/src/main/java/com/zerotier/sdk/DataStorePutListener.java @@ -24,6 +24,7 @@ * redistribute it in a modified binary form, please contact ZeroTier Networks * LLC. Start here: http://www.zerotier.com/ */ + package com.zerotier.sdk; public interface DataStorePutListener { @@ -43,7 +44,7 @@ public interface DataStorePutListener { * @param secure set to user read/write only. * @return 0 on success. */ - public int onDataStorePut( + int onDataStorePut( String name, byte[] buffer, boolean secure); @@ -54,6 +55,6 @@ public int onDataStorePut( * @param name Object name * @return 0 on success. */ - public int onDelete( + int onDelete( String name); } diff --git a/app/src/main/java/com/zerotier/sdk/Event.java b/app/src/main/java/com/zerotier/sdk/Event.java index 22d350e..fbc016c 100644 --- a/app/src/main/java/com/zerotier/sdk/Event.java +++ b/app/src/main/java/com/zerotier/sdk/Event.java @@ -27,26 +27,32 @@ package com.zerotier.sdk; +/** + * Status codes sent to status update callback when things happen + * + * Defined in ZeroTierOne.h as ZT_Event + */ public enum Event { + /** * Node has been initialized * * This is the first event generated, and is always sent. It may occur * before Node's constructor returns. */ - EVENT_UP, + EVENT_UP(0), /** * Node is offline -- network does not seem to be reachable by any available strategy */ - EVENT_OFFLINE, + EVENT_OFFLINE(1), /** * Node is online -- at least one upstream node appears reachable * * Meta-data: none */ - EVENT_ONLINE, + EVENT_ONLINE(2), /** * Node is shutting down @@ -55,7 +61,7 @@ public enum Event { * It's done for convenience, since cleaning up other state in the event * handler may appear more idiomatic.

*/ - EVENT_DOWN, + EVENT_DOWN(3), /** * Your identity has collided with another node's ZeroTier address @@ -85,7 +91,7 @@ public enum Event { * condition is a good way to make sure it never arises. It's like how * umbrellas prevent rain and smoke detectors prevent fires. They do, right?

*/ - EVENT_FATAL_ERROR_IDENTITY_COLLISION, + EVENT_FATAL_ERROR_IDENTITY_COLLISION(4), /** * Trace (debugging) message @@ -94,5 +100,55 @@ public enum Event { * *

Meta-data: {@link String}, TRACE message

*/ - EVENT_TRACE -} \ No newline at end of file + EVENT_TRACE(5), + + /** + * VERB_USER_MESSAGE received + * + * These are generated when a VERB_USER_MESSAGE packet is received via + * ZeroTier VL1. + */ + EVENT_USER_MESSAGE(6), + + /** + * Remote trace received + * + * These are generated when a VERB_REMOTE_TRACE is received. Note + * that any node can fling one of these at us. It is your responsibility + * to filter and determine if it's worth paying attention to. If it's + * not just drop it. Most nodes that are not active controllers ignore + * these, and controllers only save them if they pertain to networks + * with remote tracing enabled. + */ + EVENT_REMOTE_TRACE(7); + + @SuppressWarnings({"FieldCanBeLocal", "unused"}) + private final int id; + + Event(int id) { + this.id = id; + } + + public static Event fromInt(int id) { + switch (id) { + case 0: + return EVENT_UP; + case 1: + return EVENT_OFFLINE; + case 2: + return EVENT_ONLINE; + case 3: + return EVENT_DOWN; + case 4: + return EVENT_FATAL_ERROR_IDENTITY_COLLISION; + case 5: + return EVENT_TRACE; + case 6: + return EVENT_USER_MESSAGE; + case 7: + return EVENT_REMOTE_TRACE; + default: + throw new RuntimeException("Unhandled value: " + id); + } + } +} diff --git a/app/src/main/java/com/zerotier/sdk/EventListener.java b/app/src/main/java/com/zerotier/sdk/EventListener.java index 91050aa..88fb8af 100644 --- a/app/src/main/java/com/zerotier/sdk/EventListener.java +++ b/app/src/main/java/com/zerotier/sdk/EventListener.java @@ -27,19 +27,17 @@ package com.zerotier.sdk; -import java.net.InetSocketAddress; -import java.lang.String; - /** * Interface to handle callbacks for ZeroTier One events. */ public interface EventListener { + /** * Callback for events with no other associated metadata * * @param event {@link Event} enum */ - public void onEvent(Event event); + void onEvent(Event event); /** * Trace messages @@ -48,5 +46,5 @@ public interface EventListener { * * @param message the trace message */ - public void onTrace(String message); + void onTrace(String message); } diff --git a/app/src/main/java/com/zerotier/sdk/NativeUtils.java b/app/src/main/java/com/zerotier/sdk/NativeUtils.java deleted file mode 100644 index 07e1ef5..0000000 --- a/app/src/main/java/com/zerotier/sdk/NativeUtils.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.zerotier.sdk; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Simple library class for working with JNI (Java Native Interface) - * - * @see http://adamheinrich.com/2012/how-to-load-native-jni-library-from-jar - * - * @author Adam Heirnich , http://www.adamh.cz - */ -public class NativeUtils { - - /** - * Private constructor - this class will never be instanced - */ - private NativeUtils() { - } - - /** - * Loads library from current JAR archive - * - * The file from JAR is copied into system temporary directory and then loaded. The temporary file is deleted after exiting. - * Method uses String as filename because the pathname is "abstract", not system-dependent. - * - * @param filename The filename inside JAR as absolute path (beginning with '/'), e.g. /package/File.ext - * @throws IOException If temporary file creation or read/write operation fails - * @throws IllegalArgumentException If source file (param path) does not exist - * @throws IllegalArgumentException If the path is not absolute or if the filename is shorter than three characters (restriction of {@see File#createTempFile(java.lang.String, java.lang.String)}). - */ - public static void loadLibraryFromJar(String path) throws IOException { - - if (!path.startsWith("/")) { - throw new IllegalArgumentException("The path has to be absolute (start with '/')."); - } - - // Obtain filename from path - String[] parts = path.split("/"); - String filename = (parts.length > 1) ? parts[parts.length - 1] : null; - - // Split filename to prexif and suffix (extension) - String prefix = ""; - String suffix = null; - if (filename != null) { - parts = filename.split("\\.", 2); - prefix = parts[0]; - suffix = (parts.length > 1) ? "."+parts[parts.length - 1] : null; // Thanks, davs! :-) - } - - // Check if the filename is okay - if (filename == null || prefix.length() < 3) { - throw new IllegalArgumentException("The filename has to be at least 3 characters long."); - } - - // Prepare temporary file - File temp = File.createTempFile(prefix, suffix); - temp.deleteOnExit(); - - if (!temp.exists()) { - throw new FileNotFoundException("File " + temp.getAbsolutePath() + " does not exist."); - } - - // Prepare buffer for data copying - byte[] buffer = new byte[1024]; - int readBytes; - - // Open and check input stream - InputStream is = NativeUtils.class.getResourceAsStream(path); - if (is == null) { - throw new FileNotFoundException("File " + path + " was not found inside JAR."); - } - - // Open output stream and copy data between source file in JAR and the temporary file - OutputStream os = new FileOutputStream(temp); - try { - while ((readBytes = is.read(buffer)) != -1) { - os.write(buffer, 0, readBytes); - } - } finally { - // If read/write fails, close streams safely before throwing an exception - os.close(); - is.close(); - } - - // Finally, load the library - System.load(temp.getAbsolutePath()); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/zerotier/sdk/Node.java b/app/src/main/java/com/zerotier/sdk/Node.java index ef6ac9d..a3f3ab4 100644 --- a/app/src/main/java/com/zerotier/sdk/Node.java +++ b/app/src/main/java/com/zerotier/sdk/Node.java @@ -28,95 +28,72 @@ package com.zerotier.sdk; import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.io.IOException; /** * A ZeroTier One node */ public class Node { - static { - try { - System.loadLibrary("ZeroTierOneJNI"); - } catch (UnsatisfiedLinkError e) { - try { - if(System.getProperty("os.name").startsWith("Windows")) { - System.out.println("Arch: " + System.getProperty("sun.arch.data.model")); - if(System.getProperty("sun.arch.data.model").equals("64")) { - NativeUtils.loadLibraryFromJar("/lib/ZeroTierOneJNI_win64.dll"); - } else { - NativeUtils.loadLibraryFromJar("/lib/ZeroTierOneJNI_win32.dll"); - } - } else if(System.getProperty("os.name").startsWith("Mac")) { - NativeUtils.loadLibraryFromJar("/lib/libZeroTierOneJNI.jnilib"); - } else { - // TODO: Linux - } - } catch (IOException ioe) { - ioe.printStackTrace(); - } - } - } + static { + System.loadLibrary("ZeroTierOneJNI"); + } private static final String TAG = "NODE"; /** * Node ID for JNI purposes. * Currently set to the now value passed in at the constructor - * - * -1 if the node has already been closed */ - private long nodeId; - - private final DataStoreGetListener getListener; - private final DataStorePutListener putListener; - private final PacketSender sender; - private final EventListener eventListener; - private final VirtualNetworkFrameListener frameListener; - private final VirtualNetworkConfigListener configListener; - private final PathChecker pathChecker; + private final long nodeId; /** * Create a new ZeroTier One node * + * @param now Current clock in milliseconds + */ + public Node(long now) { + this.nodeId = now; + } + + /** + * Init a new ZeroTier One node + * *

Note that this can take a few seconds the first time it's called, as it * will generate an identity.

* - * @param now Current clock in milliseconds * @param getListener User written instance of the {@link DataStoreGetListener} interface called to get objects from persistent storage. This instance must be unique per Node object. - * @param putListener User written intstance of the {@link DataStorePutListener} interface called to put objects in persistent storage. This instance must be unique per Node object. - * @param sender + * @param putListener User written instance of the {@link DataStorePutListener} interface called to put objects in persistent storage. This instance must be unique per Node object. + * @param sender User written instance of the {@link PacketSender} interface to send ZeroTier packets out over the wire. * @param eventListener User written instance of the {@link EventListener} interface to receive status updates and non-fatal error notices. This instance must be unique per Node object. - * @param frameListener + * @param frameListener User written instance of the {@link VirtualNetworkFrameListener} interface to send a frame out to a virtual network port. * @param configListener User written instance of the {@link VirtualNetworkConfigListener} interface to be called when virtual LANs are created, deleted, or their config parameters change. This instance must be unique per Node object. * @param pathChecker User written instance of the {@link PathChecker} interface. Not required and can be null. */ - public Node(long now, - DataStoreGetListener getListener, - DataStorePutListener putListener, - PacketSender sender, - EventListener eventListener, - VirtualNetworkFrameListener frameListener, - VirtualNetworkConfigListener configListener, - PathChecker pathChecker) throws NodeException - { - this.nodeId = now; - - this.getListener = getListener; - this.putListener = putListener; - this.sender = sender; - this.eventListener = eventListener; - this.frameListener = frameListener; - this.configListener = configListener; - this.pathChecker = pathChecker; - - ResultCode rc = node_init(now); - if(rc != ResultCode.RESULT_OK) - { - // TODO: Throw Exception + public ResultCode init( + DataStoreGetListener getListener, + DataStorePutListener putListener, + PacketSender sender, + EventListener eventListener, + VirtualNetworkFrameListener frameListener, + VirtualNetworkConfigListener configListener, + PathChecker pathChecker) throws NodeException { + ResultCode rc = node_init( + nodeId, + getListener, + putListener, + sender, + eventListener, + frameListener, + configListener, + pathChecker); + if(rc != ResultCode.RESULT_OK) { throw new NodeException(rc.toString()); } - } + return rc; + } + + public boolean isInited() { + return node_isInited(nodeId); + } /** * Close this Node. @@ -124,15 +101,12 @@ public Node(long now, *

The Node object can no longer be used once this method is called.

*/ public void close() { - if(nodeId != -1) { - node_delete(nodeId); - nodeId = -1; - } + node_delete(nodeId); } @Override - protected void finalize() { - close(); + public String toString() { + return "Node(" + nodeId + ")"; } /** @@ -166,6 +140,7 @@ public ResultCode processVirtualNetworkFrame( * Process a packet received from the physical wire * * @param now Current clock in milliseconds + * @param localSocket Local socket or -1 * @param remoteAddress Origin of packet * @param packetData Packet data * @param nextBackgroundTaskDeadline Value/result: set to deadline for next call to processBackgroundTasks() @@ -197,7 +172,7 @@ public ResultCode processBackgroundTasks(long now, long[] nextBackgroundTaskDead * Join a network * *

This may generate calls to the port config callback before it returns, - * or these may be deffered if a netconf is not available yet.

+ * or these may be deferred if a netconf is not available yet.

* *

If we are already a member of the network, nothing is done and OK is * returned.

@@ -392,8 +367,8 @@ public VirtualNetworkConfig networkConfig(long nwid) { * * @return List of networks or NULL on failure */ - public VirtualNetworkConfig[] networks() { - return networks(nodeId); + public VirtualNetworkConfig[] networkConfigs() { + return networkConfigs(nodeId); } /** @@ -408,7 +383,17 @@ public Version getVersion() { // // function declarations for JNI // - private native ResultCode node_init(long now); + private native ResultCode node_init( + long nodeId, + DataStoreGetListener dataStoreGetListener, + DataStorePutListener dataStorePutListener, + PacketSender packetSender, + EventListener eventListener, + VirtualNetworkFrameListener virtualNetworkFrameListener, + VirtualNetworkConfigListener virtualNetworkConfigListener, + PathChecker pathChecker); + + private native boolean node_isInited(long nodeId); private native void node_delete(long nodeId); @@ -471,5 +456,5 @@ private native ResultCode deorbit( private native Peer[] peers(long nodeId); - private native VirtualNetworkConfig[] networks(long nodeId); -} \ No newline at end of file + private native VirtualNetworkConfig[] networkConfigs(long nodeId); +} diff --git a/app/src/main/java/com/zerotier/sdk/NodeException.java b/app/src/main/java/com/zerotier/sdk/NodeException.java index 1fdef72..beeb060 100644 --- a/app/src/main/java/com/zerotier/sdk/NodeException.java +++ b/app/src/main/java/com/zerotier/sdk/NodeException.java @@ -27,10 +27,11 @@ package com.zerotier.sdk; -import java.lang.RuntimeException; +public class NodeException extends Exception { -public class NodeException extends RuntimeException { + private static final long serialVersionUID = 6268040509883125819L; + public NodeException(String message) { super(message); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/zerotier/sdk/NodeStatus.java b/app/src/main/java/com/zerotier/sdk/NodeStatus.java index 94376d8..1172650 100644 --- a/app/src/main/java/com/zerotier/sdk/NodeStatus.java +++ b/app/src/main/java/com/zerotier/sdk/NodeStatus.java @@ -27,43 +27,64 @@ package com.zerotier.sdk; -public final class NodeStatus { - private long address; - private String publicIdentity; - private String secretIdentity; - private boolean online; +import com.zerotier.sdk.util.StringUtils; - private NodeStatus() {} +/** + * Current node status + * + * Defined in ZeroTierOne.h as ZT_NodeStatus + */ +public class NodeStatus { + + private final long address; + + private final String publicIdentity; + + private final String secretIdentity; + + private final boolean online; + + public NodeStatus(long address, String publicIdentity, String secretIdentity, boolean online) { + this.address = address; + this.publicIdentity = publicIdentity; + this.secretIdentity = secretIdentity; + this.online = online; + } + + @Override + public String toString() { + return "NodeStatus(" + StringUtils.addressToString(address) + ", " + publicIdentity + ", " + secretIdentity + ", " + online + ")"; + } /** * 40-bit ZeroTier address of this node */ - public final long getAddres() { - return address; - } + public long getAddress() { + return address; + } /** * Public identity in string-serialized form (safe to send to others) * *

This identity will remain valid as long as the node exists.

*/ - public final String getPublicIdentity() { - return publicIdentity; - } + public String getPublicIdentity() { + return publicIdentity; + } /** * Full identity including secret key in string-serialized form * *

This identity will remain valid as long as the node exists.

*/ - public final String getSecretIdentity() { - return secretIdentity; - } + public String getSecretIdentity() { + return secretIdentity; + } /** * True if some kind of connectivity appears available */ - public final boolean isOnline() { - return online; - } -} \ No newline at end of file + public boolean isOnline() { + return online; + } +} diff --git a/app/src/main/java/com/zerotier/sdk/PacketSender.java b/app/src/main/java/com/zerotier/sdk/PacketSender.java index 06ec01b..893824a 100644 --- a/app/src/main/java/com/zerotier/sdk/PacketSender.java +++ b/app/src/main/java/com/zerotier/sdk/PacketSender.java @@ -24,12 +24,14 @@ * redistribute it in a modified binary form, please contact ZeroTier Networks * LLC. Start here: http://www.zerotier.com/ */ + package com.zerotier.sdk; import java.net.InetSocketAddress; public interface PacketSender { + /** * Function to send a ZeroTier packet out over the wire * @@ -40,9 +42,10 @@ public interface PacketSender { * @param localSocket socket file descriptor to send from. Set to -1 if not specified. * @param remoteAddr {@link InetSocketAddress} to send to * @param packetData data to send + * @param ttl TTL is ignored * @return 0 on success, any error code on failure. */ - public int onSendPacketRequested( + int onSendPacketRequested( long localSocket, InetSocketAddress remoteAddr, byte[] packetData, diff --git a/app/src/main/java/com/zerotier/sdk/PathChecker.java b/app/src/main/java/com/zerotier/sdk/PathChecker.java index 6bf31df..cfc97d6 100644 --- a/app/src/main/java/com/zerotier/sdk/PathChecker.java +++ b/app/src/main/java/com/zerotier/sdk/PathChecker.java @@ -8,6 +8,7 @@ import java.net.InetSocketAddress; public interface PathChecker { + /** * Callback to check whether a path should be used for ZeroTier traffic * @@ -28,6 +29,7 @@ public interface PathChecker { * @param ztAddress ZeroTier address or 0 for none/any * @param localSocket Local interface socket. -1 if unspecified * @param remoteAddress remote address + * @return true if the path should be used */ boolean onPathCheck(long ztAddress, long localSocket, InetSocketAddress remoteAddress); diff --git a/app/src/main/java/com/zerotier/sdk/Peer.java b/app/src/main/java/com/zerotier/sdk/Peer.java index eb3d713..e3d5443 100644 --- a/app/src/main/java/com/zerotier/sdk/Peer.java +++ b/app/src/main/java/com/zerotier/sdk/Peer.java @@ -27,68 +27,92 @@ package com.zerotier.sdk; -import java.util.ArrayList; +import com.zerotier.sdk.util.StringUtils; + +import java.util.Arrays; /** - * Peer status result + * Peer status result buffer + * + * Defined in ZeroTierOne.h as ZT_Peer */ -public final class Peer { - private long address; - private int versionMajor; - private int versionMinor; - private int versionRev; - private int latency; - private PeerRole role; - private PeerPhysicalPath[] paths; +public class Peer { + + private final long address; + + private final int versionMajor; + + private final int versionMinor; + + private final int versionRev; - private Peer() {} + private final int latency; + + private final PeerRole role; + + private final PeerPhysicalPath[] paths; + + public Peer(long address, int versionMajor, int versionMinor, int versionRev, int latency, PeerRole role, PeerPhysicalPath[] paths) { + this.address = address; + this.versionMajor = versionMajor; + this.versionMinor = versionMinor; + this.versionRev = versionRev; + this.latency = latency; + this.role = role; + this.paths = paths; + } + + @Override + public String toString() { + return "Peer(" + StringUtils.addressToString(address) + ", " + versionMajor + ", " + versionMinor + ", " + versionRev + ", " + latency + ", " + role + ", " + Arrays.toString(paths) + ")"; + } /** * ZeroTier address (40 bits) */ - public final long address() { + public long getAddress() { return address; } /** * Remote major version or -1 if not known */ - public final int versionMajor() { + public int getVersionMajor() { return versionMajor; } /** * Remote minor version or -1 if not known */ - public final int versionMinor() { + public int getVersionMinor() { return versionMinor; } /** * Remote revision or -1 if not known */ - public final int versionRev() { + public int getVersionRev() { return versionRev; } /** * Last measured latency in milliseconds or zero if unknown */ - public final int latency() { + public int getLatency() { return latency; } /** * What trust hierarchy role does this device have? */ - public final PeerRole role() { + public PeerRole getRole() { return role; } /** * Known network paths to peer */ - public final PeerPhysicalPath[] paths() { + public PeerPhysicalPath[] getPaths() { return paths; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/zerotier/sdk/PeerPhysicalPath.java b/app/src/main/java/com/zerotier/sdk/PeerPhysicalPath.java index 3f9a861..f6d3264 100644 --- a/app/src/main/java/com/zerotier/sdk/PeerPhysicalPath.java +++ b/app/src/main/java/com/zerotier/sdk/PeerPhysicalPath.java @@ -31,48 +31,62 @@ /** * Physical network path to a peer + * + * Defined in ZeroTierOne.h as ZT_PeerPhysicalPath */ -public final class PeerPhysicalPath { - private InetSocketAddress address; - private long lastSend; - private long lastReceive; - private boolean fixed; - private boolean preferred; +public class PeerPhysicalPath { + + private final InetSocketAddress address; + + private final long lastSend; + + private final long lastReceive; + + private final boolean preferred; + + public PeerPhysicalPath(InetSocketAddress address, long lastSend, long lastReceive, boolean preferred) { + this.address = address; + if (lastSend < 0) { + throw new RuntimeException("lastSend < 0: " + lastSend); + } + this.lastSend = lastSend; + if (lastReceive < 0) { + throw new RuntimeException("lastReceive < 0: " + lastReceive); + } + this.lastReceive = lastReceive; + this.preferred = preferred; + } - private PeerPhysicalPath() {} + @Override + public String toString() { + return "PeerPhysicalPath(" + address + ", " + lastSend + ", " + lastReceive + ", " + preferred + ")"; + } /** * Address of endpoint */ - public final InetSocketAddress address() { + public InetSocketAddress getAddress() { return address; } /** * Time of last send in milliseconds or 0 for never */ - public final long lastSend() { + public long getLastSend() { return lastSend; } /** * Time of last receive in milliseconds or 0 for never */ - public final long lastReceive() { + public long getLastReceive() { return lastReceive; } - /** - * Is path fixed? (i.e. not learned, static) - */ - public final boolean isFixed() { - return fixed; - } - /** * Is path preferred? */ - public final boolean isPreferred() { + public boolean isPreferred() { return preferred; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/zerotier/sdk/PeerRole.java b/app/src/main/java/com/zerotier/sdk/PeerRole.java index fce183d..d69a1f1 100644 --- a/app/src/main/java/com/zerotier/sdk/PeerRole.java +++ b/app/src/main/java/com/zerotier/sdk/PeerRole.java @@ -27,19 +27,45 @@ package com.zerotier.sdk; +/** + * What trust hierarchy role does this peer have? + * + * Defined in ZeroTierOne.h as ZT_PeerRole + */ public enum PeerRole { + /** * An ordinary node */ - PEER_ROLE_LEAF, + PEER_ROLE_LEAF(0), /** * moon root */ - PEER_ROLE_MOON, + PEER_ROLE_MOON(1), /** * planetary root */ - PEER_ROLE_PLANET -} \ No newline at end of file + PEER_ROLE_PLANET(2); + + @SuppressWarnings({"FieldCanBeLocal", "unused"}) + private final int id; + + PeerRole(int id) { + this.id = id; + } + + public static PeerRole fromInt(int id) { + switch (id) { + case 0: + return PEER_ROLE_LEAF; + case 1: + return PEER_ROLE_MOON; + case 2: + return PEER_ROLE_PLANET; + default: + throw new RuntimeException("Unhandled value: " + id); + } + } +} diff --git a/app/src/main/java/com/zerotier/sdk/ResultCode.java b/app/src/main/java/com/zerotier/sdk/ResultCode.java index 66f5756..dc8a901 100644 --- a/app/src/main/java/com/zerotier/sdk/ResultCode.java +++ b/app/src/main/java/com/zerotier/sdk/ResultCode.java @@ -34,14 +34,22 @@ * occurs, the node should be considered to not be working correctly. These * indicate serious problems like an inaccessible data store or a compile * problem.

+ * + * Defined in ZeroTierOne.h as ZT_ResultCode */ public enum ResultCode { + /** * Operation completed normally */ - RESULT_OK(0), + RESULT_OK(0), + + /** + * Call produced no error but no action was taken + */ + RESULT_OK_IGNORED(1), - // Fatal errors (> 0, < 1000) + // Fatal errors (>=100, <1000) /** * Ran out of memory */ @@ -68,12 +76,36 @@ public enum ResultCode { RESULT_ERROR_BAD_PARAMETER(1002); - - private final int id; - ResultCode(int id) { this.id = id; } - public int getValue() { return id; } + private final int id; + + ResultCode(int id) { + this.id = id; + } + + public static ResultCode fromInt(int id) { + switch (id) { + case 0: + return RESULT_OK; + case 1: + return RESULT_OK_IGNORED; + case 100: + return RESULT_FATAL_ERROR_OUT_OF_MEMORY; + case 101: + return RESULT_FATAL_ERROR_DATA_STORE_FAILED; + case 102: + return RESULT_FATAL_ERROR_INTERNAL; + case 1000: + return RESULT_ERROR_NETWORK_NOT_FOUND; + case 1001: + return RESULT_ERROR_UNSUPPORTED_OPERATION; + case 1002: + return RESULT_ERROR_BAD_PARAMETER; + default: + throw new RuntimeException("Unhandled value: " + id); + } + } - public boolean isFatal(int id) { - return (id > 100 && id < 1000); + public boolean isFatal() { + return (id >= 100 && id < 1000); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/zerotier/sdk/Version.java b/app/src/main/java/com/zerotier/sdk/Version.java index c93c259..0dbe1d2 100644 --- a/app/src/main/java/com/zerotier/sdk/Version.java +++ b/app/src/main/java/com/zerotier/sdk/Version.java @@ -27,10 +27,27 @@ package com.zerotier.sdk; -public final class Version { - private Version() {} - - public int major = 0; - public int minor = 0; - public int revision = 0; -} \ No newline at end of file +public class Version { + + private final int major; + private final int minor; + private final int revision; + + public Version(int major, int minor, int revision) { + this.major = major; + this.minor = minor; + this.revision = revision; + } + + public int getMajor() { + return major; + } + + public int getMinor() { + return minor; + } + + public int getRevision() { + return revision; + } +} diff --git a/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfig.java b/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfig.java index c7b48d5..bcf6485 100644 --- a/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfig.java +++ b/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfig.java @@ -29,197 +29,302 @@ import android.util.Log; -import java.lang.Comparable; -import java.lang.Override; -import java.lang.String; -import java.util.ArrayList; +import com.zerotier.sdk.util.StringUtils; + import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; -public final class VirtualNetworkConfig implements Comparable { +/** + * Virtual network configuration + * + * Defined in ZeroTierOne.h as ZT_VirtualNetworkConfig + */ +public class VirtualNetworkConfig implements Comparable { + private final static String TAG = "VirtualNetworkConfig"; public static final int MAX_MULTICAST_SUBSCRIPTIONS = 4096; public static final int ZT_MAX_ZT_ASSIGNED_ADDRESSES = 16; - private long nwid; - private long mac; - private String name; - private VirtualNetworkStatus status; - private VirtualNetworkType type; - private int mtu; - private boolean dhcp; - private boolean bridge; - private boolean broadcastEnabled; - private int portError; - private boolean enabled; - private long netconfRevision; - private InetSocketAddress[] assignedAddresses; - private VirtualNetworkRoute[] routes; - private VirtualNetworkDNS dns; - - private VirtualNetworkConfig() { + private final long nwid; - } + private final long mac; - public boolean equals(VirtualNetworkConfig cfg) { - ArrayList aaCurrent = new ArrayList<>(); - ArrayList aaNew = new ArrayList<>(); - for (InetSocketAddress s : assignedAddresses) { - aaCurrent.add(s.toString()); - } - for (InetSocketAddress s : cfg.assignedAddresses) { - aaNew.add(s.toString()); + private final String name; + + private final VirtualNetworkStatus status; + + private final VirtualNetworkType type; + + private final int mtu; + + private final boolean dhcp; + + private final boolean bridge; + + private final boolean broadcastEnabled; + + private final int portError; + + private final long netconfRevision; + + private final InetSocketAddress[] assignedAddresses; + + private final VirtualNetworkRoute[] routes; + + private final VirtualNetworkDNS dns; + + public VirtualNetworkConfig(long nwid, long mac, String name, VirtualNetworkStatus status, VirtualNetworkType type, int mtu, boolean dhcp, boolean bridge, boolean broadcastEnabled, int portError, long netconfRevision, InetSocketAddress[] assignedAddresses, VirtualNetworkRoute[] routes, VirtualNetworkDNS dns) { + this.nwid = nwid; + this.mac = mac; + this.name = name; + this.status = status; + this.type = type; + if (mtu < 0) { + throw new RuntimeException("mtu < 0: " + mtu); } - Collections.sort(aaCurrent); - Collections.sort(aaNew); - boolean aaEqual = aaCurrent.equals(aaNew); - - ArrayList rCurrent = new ArrayList<>(); - ArrayList rNew = new ArrayList<>(); - for (VirtualNetworkRoute r : routes) { - rCurrent.add(r.toString()); + this.mtu = mtu; + this.dhcp = dhcp; + this.bridge = bridge; + this.broadcastEnabled = broadcastEnabled; + this.portError = portError; + if (netconfRevision < 0) { + throw new RuntimeException("netconfRevision < 0: " + netconfRevision); } - for (VirtualNetworkRoute r : cfg.routes) { - rNew.add(r.toString()); + this.netconfRevision = netconfRevision; + this.assignedAddresses = assignedAddresses; + this.routes = routes; + this.dns = dns; + } + + @Override + public String toString() { + return "VirtualNetworkConfig(" + StringUtils.networkIdToString(nwid) + ", " + StringUtils.macAddressToString(mac) + ", " + name + ", " + status + ", " + type + ", " + mtu + ", " + dhcp + ", " + bridge + ", " + broadcastEnabled + ", " + portError + ", " + netconfRevision + ", " + Arrays.toString(assignedAddresses) + ", " + Arrays.toString(routes) + ", " + dns + ")"; + } + + @Override + public boolean equals(Object o) { + + if (!(o instanceof VirtualNetworkConfig)) { + return false; } - Collections.sort(rCurrent); - Collections.sort(rNew); - boolean routesEqual = rCurrent.equals(rNew); + + VirtualNetworkConfig cfg = (VirtualNetworkConfig) o; if (this.nwid != cfg.nwid) { - Log.i(TAG, "nwid Changed. Old: " + Long.toHexString(this.nwid) + " (" + Long.toString(this.nwid) + "), " + - "New: " + Long.toHexString(cfg.nwid) + " (" + Long.toString(cfg.nwid) + ")"); + Log.i(TAG, "NetworkID Changed. Old: " + StringUtils.networkIdToString(this.nwid) + " (" + this.nwid + "), " + + "New: " + StringUtils.networkIdToString(cfg.nwid) + " (" + cfg.nwid + ")"); + + return false; } + if (this.mac != cfg.mac) { - Log.i(TAG, "MAC Changed. Old: " + Long.toHexString(this.mac) + ", New: " + Long.toHexString(cfg.mac)); + Log.i(TAG, "MAC Changed. Old: " + StringUtils.macAddressToString(this.mac) + ", New: " + StringUtils.macAddressToString(cfg.mac)); + + return false; } if (!this.name.equals(cfg.name)) { - Log.i(TAG, "Name Changed. Old: " + this.name + " New: "+ cfg.name); + Log.i(TAG, "Name Changed. Old: " + this.name + ", New: " + cfg.name); + + return false; + } + + if (this.status != cfg.status) { + Log.i(TAG, "Status Changed. Old: " + this.status + ", New: " + cfg.status); + + return false; } - if (!this.type.equals(cfg.type)) { - Log.i(TAG, "TYPE changed. Old " + this.type + ", New: " + cfg.type); + if (this.type != cfg.type) { + Log.i(TAG, "Type changed. Old " + this.type + ", New: " + cfg.type); + + return false; } if (this.mtu != cfg.mtu) { - Log.i(TAG, "MTU Changed. Old: " + this.mtu + ", New: " + cfg.mtu); + Log.i(TAG, "MTU Changed. Old: " + this.mtu + ", New: " + cfg.mtu); + + return false; } if (this.dhcp != cfg.dhcp) { Log.i(TAG, "DHCP Flag Changed. Old: " + this.dhcp + ", New: " + cfg.dhcp); + + return false; } if (this.bridge != cfg.bridge) { Log.i(TAG, "Bridge Flag Changed. Old: " + this.bridge + ", New: " + cfg.bridge); + + return false; } if (this.broadcastEnabled != cfg.broadcastEnabled) { - Log.i(TAG, "Broadcast Flag Changed. Old: "+ this.broadcastEnabled +", New: " + this.broadcastEnabled); + Log.i(TAG, "Broadcast Flag Changed. Old: "+ this.broadcastEnabled + ", New: " + cfg.broadcastEnabled); + + return false; } if (this.portError != cfg.portError) { - Log.i(TAG, "Port Error Changed. Old: " + this.portError + ", New: " + this.portError); + Log.i(TAG, "Port Error Changed. Old: " + this.portError + ", New: " + cfg.portError); + + return false; } - if (this.enabled != cfg.enabled) { - Log.i(TAG, "Enabled Changed. Old: " + this.enabled + ", New: " + this.enabled); + if (this.netconfRevision != cfg.netconfRevision) { + Log.i(TAG, "NetConfRevision Changed. Old: " + this.netconfRevision + ", New: " + cfg.netconfRevision); + + return false; } - if (!aaEqual) { + if (!Arrays.equals(assignedAddresses, cfg.assignedAddresses)) { + + ArrayList aaCurrent = new ArrayList<>(); + ArrayList aaNew = new ArrayList<>(); + for (InetSocketAddress s : assignedAddresses) { + aaCurrent.add(s.toString()); + } + for (InetSocketAddress s : cfg.assignedAddresses) { + aaNew.add(s.toString()); + } + Collections.sort(aaCurrent); + Collections.sort(aaNew); + Log.i(TAG, "Assigned Addresses Changed"); Log.i(TAG, "Old:"); for (String s : aaCurrent) { Log.i(TAG, " " + s); } + Log.i(TAG, ""); Log.i(TAG, "New:"); for (String s : aaNew) { Log.i(TAG, " " +s); } + Log.i(TAG, ""); + + return false; } - if (!routesEqual) { + if (!Arrays.equals(routes, cfg.routes)) { + + ArrayList rCurrent = new ArrayList<>(); + ArrayList rNew = new ArrayList<>(); + for (VirtualNetworkRoute r : routes) { + rCurrent.add(r.toString()); + } + for (VirtualNetworkRoute r : cfg.routes) { + rNew.add(r.toString()); + } + Collections.sort(rCurrent); + Collections.sort(rNew); + Log.i(TAG, "Managed Routes Changed"); Log.i(TAG, "Old:"); for (String s : rCurrent) { Log.i(TAG, " " + s); } + Log.i(TAG, ""); Log.i(TAG, "New:"); for (String s : rNew) { Log.i(TAG, " " + s); } + Log.i(TAG, ""); + + return false; + } + + boolean dnsEquals; + if (this.dns == null) { + //noinspection RedundantIfStatement + if (cfg.dns == null) { + dnsEquals = true; + } else { + dnsEquals = false; + } + } else { + if (cfg.dns == null) { + dnsEquals = false; + } else { + dnsEquals = this.dns.equals(cfg.dns); + } } - boolean dnsEquals = false; - if (this.dns == null || cfg.dns == null) { - dnsEquals = true; - } else if (this.dns != null) { - dnsEquals = this.dns.equals(cfg.dns); + if (!dnsEquals) { + return false; } - return this.nwid == cfg.nwid && - this.mac == cfg.mac && - this.name.equals(cfg.name) && - this.status.equals(cfg.status) && - this.type.equals(cfg.type) && - this.mtu == cfg.mtu && - this.dhcp == cfg.dhcp && - this.bridge == cfg.bridge && - this.broadcastEnabled == cfg.broadcastEnabled && - this.portError == cfg.portError && - this.enabled == cfg.enabled && - dnsEquals && - aaEqual && routesEqual; + return true; } + @Override public int compareTo(VirtualNetworkConfig cfg) { - if(cfg.nwid == this.nwid) { - return 0; - } else { - return this.nwid > cfg.nwid ? 1 : -1; - } + return Long.compare(this.nwid, cfg.nwid); + } + + @Override + public int hashCode() { + + int result = 17; + result = 37 * result + (int) (nwid ^ (nwid >>> 32)); + result = 37 * result + (int) (mac ^ (mac >>> 32)); + result = 37 * result + name.hashCode(); + result = 37 * result + status.hashCode(); + result = 37 * result + type.hashCode(); + result = 37 * result + mtu; + result = 37 * result + (dhcp ? 1 : 0); + result = 37 * result + (bridge ? 1 : 0); + result = 37 * result + (broadcastEnabled ? 1 : 0); + result = 37 * result + portError; + result = 37 * result + (int) (netconfRevision ^ (netconfRevision >>> 32)); + result = 37 * result + Arrays.hashCode(assignedAddresses); + result = 37 * result + Arrays.hashCode(routes); + result = 37 * result + (dns == null ? 0 : dns.hashCode()); + + return result; } /** * 64-bit ZeroTier network ID */ - public final long networkId() { + public long getNwid() { return nwid; } /** - * Ethernet MAC (40 bits) that should be assigned to port + * Ethernet MAC (48 bits) that should be assigned to port */ - public final long macAddress() { + public long getMac() { return mac; } /** * Network name (from network configuration master) */ - public final String name() { + public String getName() { return name; } /** * Network configuration request status */ - public final VirtualNetworkStatus networkStatus() { + public VirtualNetworkStatus getStatus() { return status; } /** * Network type */ - public final VirtualNetworkType networkType() { + public VirtualNetworkType getType() { return type; } /** * Maximum interface MTU */ - public final int mtu() { + public int getMtu() { return mtu; } @@ -230,7 +335,7 @@ public final int mtu() { * for security or other reasons. This is simply a netconf parameter that * means 'DHCP is available on this network.'

*/ - public final boolean isDhcpAvailable() { + public boolean isDhcp() { return dhcp; } @@ -240,21 +345,21 @@ public final boolean isDhcpAvailable() { *

This is informational. If this is false, bridged packets will simply * be dropped and bridging won't work.

*/ - public final boolean isBridgeEnabled() { + public boolean isBridge() { return bridge; } /** * If true, this network supports and allows broadcast (ff:ff:ff:ff:ff:ff) traffic */ - public final boolean broadcastEnabled() { + public boolean isBroadcastEnabled() { return broadcastEnabled; } /** * If the network is in PORT_ERROR state, this is the error most recently returned by the port config callback */ - public final int portError() { + public int getPortError() { return portError; } @@ -263,12 +368,12 @@ public final int portError() { * *

If this is zero, it means we're still waiting for our netconf.

*/ - public final long netconfRevision() { + public long getNetconfRevision() { return netconfRevision; } /** - * ZeroTier-assigned addresses (in {@link java.net.InetSocketAddress} objects) + * ZeroTier-assigned addresses (in {@link InetSocketAddress} objects) * * For IP, the port number of the sockaddr_XX structure contains the number * of bits in the address netmask. Only the IP address and port are used. @@ -277,16 +382,21 @@ public final long netconfRevision() { * This is only used for ZeroTier-managed address assignments sent by the * virtual network's configuration master. */ - public final InetSocketAddress[] assignedAddresses() { + public InetSocketAddress[] getAssignedAddresses() { return assignedAddresses; } /** - * ZeroTier-assigned routes (in {@link com.zerotier.sdk.VirtualNetworkRoute} objects) - * - * @return + * ZeroTier-assigned routes (in {@link VirtualNetworkRoute} objects) */ - public final VirtualNetworkRoute[] routes() { return routes; } + public VirtualNetworkRoute[] getRoutes() { + return routes; + } - public final VirtualNetworkDNS dns() { return dns; } + /** + * Network specific DNS configuration + */ + public VirtualNetworkDNS getDns() { + return dns; + } } diff --git a/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfigListener.java b/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfigListener.java index 15ae301..ce91e79 100644 --- a/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfigListener.java +++ b/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfigListener.java @@ -25,11 +25,11 @@ * LLC. Start here: http://www.zerotier.com/ */ - package com.zerotier.sdk; public interface VirtualNetworkConfigListener { + /** * Callback called to update virtual network port configuration * @@ -40,7 +40,7 @@ public interface VirtualNetworkConfigListener { * This in turn should be used by the underlying implementation to create * and configure tap devices at the OS (or virtual network stack) layer.

* - * This should not call {@link Node#multicastSubscribe} or other network-modifying + * This should not call {@link Node#multicastSubscribe(long, long)} or other network-modifying * methods, as this could cause a deadlock in multithreaded or interrupt * driven environments. * @@ -53,8 +53,8 @@ public interface VirtualNetworkConfigListener { * @param config {@link VirtualNetworkConfig} object with the new configuration * @return 0 on success */ - public int onNetworkConfigurationUpdated( + int onNetworkConfigurationUpdated( long nwid, VirtualNetworkConfigOperation op, VirtualNetworkConfig config); -} \ No newline at end of file +} diff --git a/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfigOperation.java b/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfigOperation.java index b70eb47..a1981bd 100644 --- a/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfigOperation.java +++ b/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfigOperation.java @@ -24,26 +24,55 @@ * redistribute it in a modified binary form, please contact ZeroTier Networks * LLC. Start here: http://www.zerotier.com/ */ + package com.zerotier.sdk; +/** + * Virtual network configuration update type + * + * Defined in ZeroTierOne.h as ZT_VirtualNetworkConfigOperation + */ public enum VirtualNetworkConfigOperation { + /** * Network is coming up (either for the first time or after service restart) */ - VIRTUAL_NETWORK_CONFIG_OPERATION_UP, + VIRTUAL_NETWORK_CONFIG_OPERATION_UP(1), /** * Network configuration has been updated */ - VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE, + VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE(2), /** * Network is going down (not permanently) */ - VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN, + VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN(3), /** * Network is going down permanently (leave/delete) */ - VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY + VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY(4); + + @SuppressWarnings({"FieldCanBeLocal", "unused"}) + private final int id; + + VirtualNetworkConfigOperation(int id) { + this.id = id; + } + + public static VirtualNetworkConfigOperation fromInt(int id) { + switch (id) { + case 1: + return VIRTUAL_NETWORK_CONFIG_OPERATION_UP; + case 2: + return VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE; + case 3: + return VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN; + case 4: + return VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY; + default: + throw new RuntimeException("Unhandled value: " + id); + } + } } diff --git a/app/src/main/java/com/zerotier/sdk/VirtualNetworkDNS.java b/app/src/main/java/com/zerotier/sdk/VirtualNetworkDNS.java index 7046fd4..6e4bb3d 100644 --- a/app/src/main/java/com/zerotier/sdk/VirtualNetworkDNS.java +++ b/app/src/main/java/com/zerotier/sdk/VirtualNetworkDNS.java @@ -8,15 +8,48 @@ import java.net.InetSocketAddress; import java.util.ArrayList; +/** + * DNS configuration to be pushed on a virtual network + * + * Defined in ZeroTierOne.h as ZT_VirtualNetworkDNS + */ public class VirtualNetworkDNS implements Comparable { - private String domain; - private ArrayList servers; - public VirtualNetworkDNS() {} + private final String domain; + private final ArrayList servers; + + public VirtualNetworkDNS(String domain, ArrayList servers) { + this.domain = domain; + this.servers = servers; + } + + @Override + public String toString() { + return "VirtualNetworkDNS(" + domain + ", " + servers + ")"; + } + + @Override + public boolean equals(Object o) { + + if (o == null) { + return false; + } + + if (!(o instanceof VirtualNetworkDNS)) { + return false; + } - public boolean equals(VirtualNetworkDNS o) { - if (o == null) return false; - return domain.equals(o.domain) && servers.equals(o.servers); + VirtualNetworkDNS d = (VirtualNetworkDNS) o; + + if (!domain.equals(d.domain)) { + return false; + } + + if (!servers.equals(d.servers)) { + return false; + } + + return true; } @Override @@ -24,7 +57,21 @@ public int compareTo(VirtualNetworkDNS o) { return domain.compareTo(o.domain); } - public String getSearchDomain() { return domain; } + @Override + public int hashCode() { + + int result = 17; + result = 37 * result + domain.hashCode(); + result = 37 * result + servers.hashCode(); + + return result; + } + + public String getDomain() { + return domain; + } - public ArrayList getServers() { return servers; } + public ArrayList getServers() { + return servers; + } } diff --git a/app/src/main/java/com/zerotier/sdk/VirtualNetworkFrameListener.java b/app/src/main/java/com/zerotier/sdk/VirtualNetworkFrameListener.java index 9ad3228..650c9ce 100644 --- a/app/src/main/java/com/zerotier/sdk/VirtualNetworkFrameListener.java +++ b/app/src/main/java/com/zerotier/sdk/VirtualNetworkFrameListener.java @@ -28,17 +28,18 @@ package com.zerotier.sdk; public interface VirtualNetworkFrameListener { + /** * Function to send a frame out to a virtual network port * * @param nwid ZeroTier One network ID * @param srcMac source MAC address * @param destMac destination MAC address - * @param ethertype - * @param vlanId + * @param etherType EtherType + * @param vlanId VLAN ID * @param frameData data to send */ - public void onVirtualNetworkFrame( + void onVirtualNetworkFrame( long nwid, long srcMac, long destMac, diff --git a/app/src/main/java/com/zerotier/sdk/VirtualNetworkRoute.java b/app/src/main/java/com/zerotier/sdk/VirtualNetworkRoute.java index 8dd700c..afd9ee4 100644 --- a/app/src/main/java/com/zerotier/sdk/VirtualNetworkRoute.java +++ b/app/src/main/java/com/zerotier/sdk/VirtualNetworkRoute.java @@ -29,80 +29,135 @@ import java.net.InetSocketAddress; -public final class VirtualNetworkRoute implements Comparable +/** + * A route to be pushed on a virtual network + * + * Defined in ZeroTierOne.h as ZT_VirtualNetworkRoute + */ +public class VirtualNetworkRoute implements Comparable { - private VirtualNetworkRoute() { - target = null; - via = null; - flags = 0; - metric = 0; - } - /** * Target network / netmask bits (in port field) or NULL or 0.0.0.0/0 for default */ - public InetSocketAddress target; - + private final InetSocketAddress target; + /** * Gateway IP address (port ignored) or NULL (family == 0) for LAN-local (no gateway) */ - public InetSocketAddress via; + private final InetSocketAddress via; /** * Route flags */ - public int flags; + private final int flags; /** * Route metric (not currently used) */ - public int metric; + private final int metric; - @Override + public VirtualNetworkRoute(InetSocketAddress target, InetSocketAddress via, int flags, int metric) { + this.target = target; + this.via = via; + this.flags = flags; + this.metric = metric; + } + + @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(target.toString()); - if (via != null) { - sb.append(via.toString()); - } - return sb.toString(); + return "VirtualNetworkRoute(" + target + ", " + via + ", " + flags + ", " + metric + ")"; } @Override - public int compareTo(VirtualNetworkRoute other) { - return this.toString().compareTo(other.toString()); - } - - public boolean equals(VirtualNetworkRoute other) { - boolean targetEquals = false; - if (target == null && other.target == null) { - targetEquals = true; - } - else if (target == null && other.target != null) { - targetEquals = false; - } - else if (target != null && other.target == null) { - targetEquals = false; + public int compareTo(VirtualNetworkRoute other) { + throw new RuntimeException("Unimplemented"); + } + + @Override + public boolean equals(Object o) { + + if (!(o instanceof VirtualNetworkRoute)) { + return false; } - else { - targetEquals = target.toString().equals(other.target.toString()); + + VirtualNetworkRoute other = (VirtualNetworkRoute) o; + + boolean targetEquals; + if (target == null) { + //noinspection RedundantIfStatement + if (other.target == null) { + targetEquals = true; + } else { + targetEquals = false; + } + } else { + if (other.target == null) { + targetEquals = false; + } else { + targetEquals = target.equals(other.target); + } } + if (!targetEquals) { + return false; + } boolean viaEquals; - if (via == null && other.via == null) { - viaEquals = true; + if (via == null) { + //noinspection RedundantIfStatement + if (other.via == null) { + viaEquals = true; + } else { + viaEquals = false; + } + } else { + if (other.via == null) { + viaEquals = false; + } else { + viaEquals = via.equals(other.via); + } } - else if (via == null && other.via != null) { - viaEquals = false; + + if (!viaEquals) { + return false; } - else if (via != null && other.via == null) { - viaEquals = false; + + if (flags != other.flags) { + return false; } - else { - viaEquals = via.toString().equals(other.via.toString()); + + if (metric != other.metric) { + return false; } - return viaEquals && targetEquals; + return true; + } + + @Override + public int hashCode() { + + int result = 17; + result = 37 * result + (target == null ? 0 : target.hashCode()); + result = 37 * result + (via == null ? 0 : via.hashCode()); + result = 37 * result + flags; + result = 37 * result + metric; + + return result; + } + + public InetSocketAddress getTarget() { + return target; + } + + public InetSocketAddress getVia() { + return via; + } + + public int getFlags() { + return flags; + } + + public int getMetric() { + return metric; } } diff --git a/app/src/main/java/com/zerotier/sdk/VirtualNetworkStatus.java b/app/src/main/java/com/zerotier/sdk/VirtualNetworkStatus.java index 2d00561..8a32ba6 100644 --- a/app/src/main/java/com/zerotier/sdk/VirtualNetworkStatus.java +++ b/app/src/main/java/com/zerotier/sdk/VirtualNetworkStatus.java @@ -24,36 +24,76 @@ * redistribute it in a modified binary form, please contact ZeroTier Networks * LLC. Start here: http://www.zerotier.com/ */ + package com.zerotier.sdk; +/** + * Virtual network status codes + * + * Defined in ZeroTierOne.h as ZT_VirtualNetworkStatus + */ public enum VirtualNetworkStatus { + /** * Waiting for network configuration (also means revision == 0) */ - NETWORK_STATUS_REQUESTING_CONFIGURATION, + NETWORK_STATUS_REQUESTING_CONFIGURATION(0), /** * Configuration received and we are authorized */ - NETWORK_STATUS_OK, + NETWORK_STATUS_OK(1), /** * Netconf master told us 'nope' */ - NETWORK_STATUS_ACCESS_DENIED, + NETWORK_STATUS_ACCESS_DENIED(2), /** * Netconf master exists, but this virtual network does not */ - NETWORK_STATUS_NOT_FOUND, + NETWORK_STATUS_NOT_FOUND(3), /** * Initialization of network failed or other internal error */ - NETWORK_STATUS_PORT_ERROR, + NETWORK_STATUS_PORT_ERROR(4), /** * ZeroTier One version too old */ - NETWORK_STATUS_CLIENT_TOO_OLD + NETWORK_STATUS_CLIENT_TOO_OLD(5), + + /** + * External authentication is required (e.g. SSO) + */ + NETWORK_STATUS_AUTHENTICATION_REQUIRED(6); + + @SuppressWarnings({"FieldCanBeLocal", "unused"}) + private final int id; + + VirtualNetworkStatus(int id) { + this.id = id; + } + + public static VirtualNetworkStatus fromInt(int id) { + switch (id) { + case 0: + return NETWORK_STATUS_REQUESTING_CONFIGURATION; + case 1: + return NETWORK_STATUS_OK; + case 2: + return NETWORK_STATUS_ACCESS_DENIED; + case 3: + return NETWORK_STATUS_NOT_FOUND; + case 4: + return NETWORK_STATUS_PORT_ERROR; + case 5: + return NETWORK_STATUS_CLIENT_TOO_OLD; + case 6: + return NETWORK_STATUS_AUTHENTICATION_REQUIRED; + default: + throw new RuntimeException("Unhandled value: " + id); + } + } } diff --git a/app/src/main/java/com/zerotier/sdk/VirtualNetworkType.java b/app/src/main/java/com/zerotier/sdk/VirtualNetworkType.java index ab1f4e0..44be886 100644 --- a/app/src/main/java/com/zerotier/sdk/VirtualNetworkType.java +++ b/app/src/main/java/com/zerotier/sdk/VirtualNetworkType.java @@ -24,16 +24,41 @@ * redistribute it in a modified binary form, please contact ZeroTier Networks * LLC. Start here: http://www.zerotier.com/ */ + package com.zerotier.sdk; +/** + * Virtual network type codes + * + * Defined in ZeroTierOne.h as ZT_VirtualNetworkType + */ public enum VirtualNetworkType { + /** * Private networks are authorized via certificates of membership */ - NETWORK_TYPE_PRIVATE, + NETWORK_TYPE_PRIVATE(0), /** * Public networks have no access control -- they'll always be AUTHORIZED */ - NETWORK_TYPE_PUBLIC + NETWORK_TYPE_PUBLIC(1); + + @SuppressWarnings({"FieldCanBeLocal", "unused"}) + private final int id; + + VirtualNetworkType(int id) { + this.id = id; + } + + public static VirtualNetworkType fromInt(int id) { + switch (id) { + case 0: + return NETWORK_TYPE_PRIVATE; + case 1: + return NETWORK_TYPE_PUBLIC; + default: + throw new RuntimeException("Unhandled value: " + id); + } + } } diff --git a/app/src/main/java/com/zerotier/sdk/util/StringUtils.java b/app/src/main/java/com/zerotier/sdk/util/StringUtils.java new file mode 100644 index 0000000..c7bcc5d --- /dev/null +++ b/app/src/main/java/com/zerotier/sdk/util/StringUtils.java @@ -0,0 +1,52 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2023 ZeroTier, Inc. https://www.zerotier.com/ + */ + +package com.zerotier.sdk.util; + +public class StringUtils { + + /** + * Convert mac address to string. + * + * @param mac MAC address + * @return string in XX:XX:XX:XX:XX:XX format + */ + public static String macAddressToString(long mac) { + + int[] macChars = new int[6]; + for (int i = 0; i < 6; i++) { + macChars[i] = (int) (mac % 256); + mac >>= 8; + } + + return String.format("%02x:%02x:%02x:%02x:%02x:%02x", macChars[5], macChars[4], macChars[3], macChars[2], macChars[1], macChars[0]); + } + + /** + * Convert long to hex string. + * + * @param networkId long + * @return string with 0 padding + */ + public static String networkIdToString(long networkId) { + return String.format("%016x", networkId); + } + + /** + * Convert node address to string. + * + * Node addresses are 40 bits, so print 10 hex characters. + * + * @param address Node address + * @return formatted string + */ + public static String addressToString(long address) { + return String.format("%010x", address); + } + + public static String etherTypeToString(long etherType) { + return String.format("%04x", etherType); + } +} diff --git a/app/src/main/java/net/kaaass/zerotierfix/events/IsServiceRunningEvent.java b/app/src/main/java/net/kaaass/zerotierfix/events/IsServiceRunningEvent.java deleted file mode 100644 index cdd171f..0000000 --- a/app/src/main/java/net/kaaass/zerotierfix/events/IsServiceRunningEvent.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.kaaass.zerotierfix.events; - -public class IsServiceRunningEvent { - public boolean isRunning = false; - public Type type = Type.REQUEST; - - private IsServiceRunningEvent() { - } - - public static IsServiceRunningEvent NewRequest() { - return new IsServiceRunningEvent(); - } - - public static IsServiceRunningEvent NewReply(boolean z) { - IsServiceRunningEvent isServiceRunningEvent = new IsServiceRunningEvent(); - isServiceRunningEvent.isRunning = z; - isServiceRunningEvent.type = Type.REPLY; - return isServiceRunningEvent; - } - - public enum Type { - REQUEST, - REPLY - } -} diff --git a/app/src/main/java/net/kaaass/zerotierfix/events/IsServiceRunningReplyEvent.java b/app/src/main/java/net/kaaass/zerotierfix/events/IsServiceRunningReplyEvent.java new file mode 100644 index 0000000..8bdf39d --- /dev/null +++ b/app/src/main/java/net/kaaass/zerotierfix/events/IsServiceRunningReplyEvent.java @@ -0,0 +1,11 @@ +package net.kaaass.zerotierfix.events; + +import lombok.Data; + +/** + * 应答服务是否运行事件 + */ +@Data +public class IsServiceRunningReplyEvent { + private final boolean isRunning; +} diff --git a/app/src/main/java/net/kaaass/zerotierfix/events/IsServiceRunningRequestEvent.java b/app/src/main/java/net/kaaass/zerotierfix/events/IsServiceRunningRequestEvent.java new file mode 100644 index 0000000..d92b237 --- /dev/null +++ b/app/src/main/java/net/kaaass/zerotierfix/events/IsServiceRunningRequestEvent.java @@ -0,0 +1,10 @@ +package net.kaaass.zerotierfix.events; + +import lombok.Data; + +/** + * 请求服务是否运行事件 + */ +@Data +public class IsServiceRunningRequestEvent { +} diff --git a/app/src/main/java/net/kaaass/zerotierfix/events/NetworkConfigChangedByUserEvent.java b/app/src/main/java/net/kaaass/zerotierfix/events/NetworkConfigChangedByUserEvent.java new file mode 100644 index 0000000..91e8cc7 --- /dev/null +++ b/app/src/main/java/net/kaaass/zerotierfix/events/NetworkConfigChangedByUserEvent.java @@ -0,0 +1,15 @@ +package net.kaaass.zerotierfix.events; + +import net.kaaass.zerotierfix.model.Network; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * 用户更改 ZT 网络配置事件 + */ +@Data +@AllArgsConstructor +public class NetworkConfigChangedByUserEvent { + private final Network network; +} diff --git a/app/src/main/java/net/kaaass/zerotierfix/events/RequestNetworkInfoEvent.java b/app/src/main/java/net/kaaass/zerotierfix/events/NetworkInfoRequestEvent.java similarity index 68% rename from app/src/main/java/net/kaaass/zerotierfix/events/RequestNetworkInfoEvent.java rename to app/src/main/java/net/kaaass/zerotierfix/events/NetworkInfoRequestEvent.java index 252c68f..d3a925a 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/events/RequestNetworkInfoEvent.java +++ b/app/src/main/java/net/kaaass/zerotierfix/events/NetworkInfoRequestEvent.java @@ -1,9 +1,9 @@ package net.kaaass.zerotierfix.events; -public class RequestNetworkInfoEvent { +public class NetworkInfoRequestEvent { private final long networkId; - public RequestNetworkInfoEvent(long j) { + public NetworkInfoRequestEvent(long j) { this.networkId = j; } diff --git a/app/src/main/java/net/kaaass/zerotierfix/events/NetworkListCheckedChangeEvent.java b/app/src/main/java/net/kaaass/zerotierfix/events/NetworkListCheckedChangeEvent.java new file mode 100644 index 0000000..3f39264 --- /dev/null +++ b/app/src/main/java/net/kaaass/zerotierfix/events/NetworkListCheckedChangeEvent.java @@ -0,0 +1,17 @@ +package net.kaaass.zerotierfix.events; + +import androidx.appcompat.widget.SwitchCompat; + +import net.kaaass.zerotierfix.model.Network; + +import lombok.Data; + +/** + * 网络列表点击按钮事件。用于在后台进程控制 ZT 服务的启停 + */ +@Data +public class NetworkListCheckedChangeEvent { + private final SwitchCompat switchHandle; + private final boolean checked; + private final Network selectedNetwork; +} diff --git a/app/src/main/java/net/kaaass/zerotierfix/events/RequestNetworkListEvent.java b/app/src/main/java/net/kaaass/zerotierfix/events/NetworkListRequestEvent.java similarity index 51% rename from app/src/main/java/net/kaaass/zerotierfix/events/RequestNetworkListEvent.java rename to app/src/main/java/net/kaaass/zerotierfix/events/NetworkListRequestEvent.java index 8b143c5..7267839 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/events/RequestNetworkListEvent.java +++ b/app/src/main/java/net/kaaass/zerotierfix/events/NetworkListRequestEvent.java @@ -1,4 +1,4 @@ package net.kaaass.zerotierfix.events; -public class RequestNetworkListEvent { +public class NetworkListRequestEvent { } diff --git a/app/src/main/java/net/kaaass/zerotierfix/events/NetworkReconfigureEvent.java b/app/src/main/java/net/kaaass/zerotierfix/events/NetworkReconfigureEvent.java index 13e6d4e..c38850c 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/events/NetworkReconfigureEvent.java +++ b/app/src/main/java/net/kaaass/zerotierfix/events/NetworkReconfigureEvent.java @@ -1,4 +1,19 @@ package net.kaaass.zerotierfix.events; +import com.zerotier.sdk.VirtualNetworkConfig; + +import net.kaaass.zerotierfix.model.Network; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * 更新网络配置的结果事件 + */ +@Data +@AllArgsConstructor public class NetworkReconfigureEvent { + private final boolean changed; + private final Network network; + private final VirtualNetworkConfig virtualNetworkConfig; } diff --git a/app/src/main/java/net/kaaass/zerotierfix/events/NodeStatusEvent.java b/app/src/main/java/net/kaaass/zerotierfix/events/NodeStatusEvent.java index 680f9c0..37863f1 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/events/NodeStatusEvent.java +++ b/app/src/main/java/net/kaaass/zerotierfix/events/NodeStatusEvent.java @@ -9,7 +9,7 @@ /** * 节点状态事件 * - * 在遇到 {@link RequestNodeStatusEvent} 或 Zerotier 事件时触发 + * 在遇到 {@link NodeStatusRequestEvent} 或 Zerotier 事件时触发 */ @Data @AllArgsConstructor diff --git a/app/src/main/java/net/kaaass/zerotierfix/events/RequestNodeStatusEvent.java b/app/src/main/java/net/kaaass/zerotierfix/events/NodeStatusRequestEvent.java similarity index 73% rename from app/src/main/java/net/kaaass/zerotierfix/events/RequestNodeStatusEvent.java rename to app/src/main/java/net/kaaass/zerotierfix/events/NodeStatusRequestEvent.java index 1f549bc..b4f36ee 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/events/RequestNodeStatusEvent.java +++ b/app/src/main/java/net/kaaass/zerotierfix/events/NodeStatusRequestEvent.java @@ -6,5 +6,5 @@ * 请求节点状态事件 */ @Data -public class RequestNodeStatusEvent { +public class NodeStatusRequestEvent { } diff --git a/app/src/main/java/net/kaaass/zerotierfix/events/RequestPeerInfoEvent.java b/app/src/main/java/net/kaaass/zerotierfix/events/PeerInfoRequestEvent.java similarity index 77% rename from app/src/main/java/net/kaaass/zerotierfix/events/RequestPeerInfoEvent.java rename to app/src/main/java/net/kaaass/zerotierfix/events/PeerInfoRequestEvent.java index 8bdba56..1ddf984 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/events/RequestPeerInfoEvent.java +++ b/app/src/main/java/net/kaaass/zerotierfix/events/PeerInfoRequestEvent.java @@ -8,5 +8,5 @@ * @author kaaass */ @Data -public class RequestPeerInfoEvent { +public class PeerInfoRequestEvent { } diff --git a/app/src/main/java/net/kaaass/zerotierfix/events/VPNErrorEvent.java b/app/src/main/java/net/kaaass/zerotierfix/events/VPNErrorEvent.java new file mode 100644 index 0000000..a33de5d --- /dev/null +++ b/app/src/main/java/net/kaaass/zerotierfix/events/VPNErrorEvent.java @@ -0,0 +1,13 @@ +package net.kaaass.zerotierfix.events; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * VPN 错误事件 + */ +@Data +@AllArgsConstructor +public class VPNErrorEvent { + private final String message; +} diff --git a/app/src/main/java/net/kaaass/zerotierfix/events/VirtualNetworkConfigChangedEvent.java b/app/src/main/java/net/kaaass/zerotierfix/events/VirtualNetworkConfigChangedEvent.java new file mode 100644 index 0000000..f4d547d --- /dev/null +++ b/app/src/main/java/net/kaaass/zerotierfix/events/VirtualNetworkConfigChangedEvent.java @@ -0,0 +1,15 @@ +package net.kaaass.zerotierfix.events; + +import com.zerotier.sdk.VirtualNetworkConfig; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * ZT 网络配置成功更改事件 + */ +@Data +@AllArgsConstructor +public class VirtualNetworkConfigChangedEvent { + private final VirtualNetworkConfig virtualNetworkConfig; +} diff --git a/app/src/main/java/net/kaaass/zerotierfix/model/Network.java b/app/src/main/java/net/kaaass/zerotierfix/model/Network.java index f264aa3..6690551 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/model/Network.java +++ b/app/src/main/java/net/kaaass/zerotierfix/model/Network.java @@ -19,6 +19,7 @@ public class Network { private String networkName; + @Deprecated private boolean useDefaultRoute; private boolean lastActivated; diff --git a/app/src/main/java/net/kaaass/zerotierfix/model/NetworkConfig.java b/app/src/main/java/net/kaaass/zerotierfix/model/NetworkConfig.java index e88616f..28bf3c0 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/model/NetworkConfig.java +++ b/app/src/main/java/net/kaaass/zerotierfix/model/NetworkConfig.java @@ -1,6 +1,8 @@ package net.kaaass.zerotierfix.model; import net.kaaass.zerotierfix.R; +import net.kaaass.zerotierfix.model.type.NetworkStatus; +import net.kaaass.zerotierfix.model.type.NetworkType; import org.greenrobot.greendao.DaoException; import org.greenrobot.greendao.annotation.Convert; @@ -10,6 +12,7 @@ import org.greenrobot.greendao.converter.PropertyConverter; import java.util.List; + import org.greenrobot.greendao.annotation.Generated; @Entity @@ -43,18 +46,22 @@ public class NetworkConfig { @ToMany(referencedJoinProperty = "networkId") private List dnsServers; - /** Used to resolve relations */ + /** + * Used to resolve relations + */ @Generated(hash = 2040040024) private transient DaoSession daoSession; - /** Used for active entity operations. */ + /** + * Used for active entity operations. + */ @Generated(hash = 1627972760) private transient NetworkConfigDao myDao; @Generated(hash = 1535887363) public NetworkConfig(Long id, NetworkType type, NetworkStatus status, String mac, String mtu, - boolean broadcast, boolean bridging, boolean routeViaZeroTier, boolean useCustomDNS, - int dnsMode) { + boolean broadcast, boolean bridging, boolean routeViaZeroTier, boolean useCustomDNS, + int dnsMode) { this.id = id; this.type = type; this.status = status; @@ -174,7 +181,9 @@ public List getAssignedAddresses() { return assignedAddresses; } - /** Resets a to-many relationship, making the next get call to query for a fresh result. */ + /** + * Resets a to-many relationship, making the next get call to query for a fresh result. + */ @Generated(hash = 1705851723) public synchronized void resetAssignedAddresses() { assignedAddresses = null; @@ -202,7 +211,9 @@ public List getDnsServers() { return dnsServers; } - /** Resets a to-many relationship, making the next get call to query for a fresh result. */ + /** + * Resets a to-many relationship, making the next get call to query for a fresh result. + */ @Generated(hash = 980018091) public synchronized void resetDnsServers() { dnsServers = null; @@ -251,130 +262,19 @@ public void __setDaoSession(DaoSession daoSession) { myDao = daoSession != null ? daoSession.getNetworkConfigDao() : null; } - public enum NetworkType { - UNKNOWN(0), - PRIVATE(1), - PUBLIC(2); - - final int id; - - NetworkType(int i) { - this.id = i; - } - - public String toString() { - int i = this.id; - if (i != 1) { - return i != 2 ? "Unknown" : "Public"; - } - return "Private"; - } - - public int toStringId() { - int i = this.id; - if (i != 1) { - return i != 2 ? R.string.network_type_unknown : R.string.network_type_public; - } - return R.string.network_type_private; - } - } - - public enum DNSMode { - NO_DNS(0), - NETWORK_DNS(1), - CUSTOM_DNS(2); - - final int id; - - DNSMode(int i) { - this.id = i; - } - - public String toString() { - int i = this.id; - if (i == 0) { - return "No DNS"; - } - if (i != 1) { - return i != 2 ? "Unknown" : "Custom DNS"; - } - return "Network DNS"; - } - } - - public enum NetworkStatus { - UNKNOWN(0), - OK(1), - ACCESS_DENIED(2), - CLIENT_TOO_OLD(3), - NOT_FOUND(4), - PORT_ERROR(5), - REQUESTING_CONFIGURATION(6); - - final int id; - - NetworkStatus(int i) { - this.id = i; - } - - public String toString() { - switch (this.id) { - case 1: - return "OK"; - case 2: - return "ACCESS DENIED"; - case 3: - return "CLIENT TOO OLD"; - case 4: - return "NETWORK NOT FOUND"; - case 5: - return "PORT ERROR"; - case 6: - return "REQUESTING CONFIGURATION"; - default: - return "UNKNOWN"; - } - } - - public int toStringId() { - switch (this.id) { - case 1: - return R.string.network_status_ok; - case 2: - return R.string.network_status_access_denied; - case 3: - return R.string.network_status_client_too_old; - case 4: - return R.string.network_status_not_found; - case 5: - return R.string.network_status_port_error; - case 6: - return R.string.network_status_requesting_configuration; - default: - return R.string.network_status_unknown; - } - } - } - public static class NetworkTypeConverter implements PropertyConverter { public NetworkType convertToEntityProperty(Integer num) { if (num == null) { return null; } - NetworkType[] values = NetworkType.values(); - for (NetworkType networkType : values) { - if (networkType.id == num) { - return networkType; - } - } - return NetworkType.PRIVATE; + return NetworkType.fromInt(num); } public Integer convertToDatabaseValue(NetworkType networkType) { if (networkType == null) { return null; } - return networkType.id; + return networkType.toInt(); } } @@ -383,20 +283,14 @@ public NetworkStatus convertToEntityProperty(Integer num) { if (num == null) { return null; } - NetworkStatus[] values = NetworkStatus.values(); - for (NetworkStatus networkStatus : values) { - if (networkStatus.id == num) { - return networkStatus; - } - } - return NetworkStatus.UNKNOWN; + return NetworkStatus.fromInt(num); } public Integer convertToDatabaseValue(NetworkStatus networkStatus) { if (networkStatus == null) { return null; } - return networkStatus.id; + return networkStatus.toInt(); } } } diff --git a/app/src/main/java/net/kaaass/zerotierfix/model/type/DNSMode.java b/app/src/main/java/net/kaaass/zerotierfix/model/type/DNSMode.java new file mode 100644 index 0000000..9d30337 --- /dev/null +++ b/app/src/main/java/net/kaaass/zerotierfix/model/type/DNSMode.java @@ -0,0 +1,30 @@ +package net.kaaass.zerotierfix.model.type; + +public enum DNSMode { + NO_DNS(0), + NETWORK_DNS(1), + CUSTOM_DNS(2); + + private final int id; + + DNSMode(int i) { + this.id = i; + } + + public static DNSMode fromInt(int i) { + if (i != 0) { + if (i == 1) { + return NETWORK_DNS; + } + if (i == 2) { + return CUSTOM_DNS; + } + throw new RuntimeException("Unhandled value: " + i); + } + return NO_DNS; + } + + public int toInt() { + return this.id; + } +} diff --git a/app/src/main/java/net/kaaass/zerotierfix/model/type/NetworkStatus.java b/app/src/main/java/net/kaaass/zerotierfix/model/type/NetworkStatus.java new file mode 100644 index 0000000..cb532fa --- /dev/null +++ b/app/src/main/java/net/kaaass/zerotierfix/model/type/NetworkStatus.java @@ -0,0 +1,82 @@ +package net.kaaass.zerotierfix.model.type; + +import com.zerotier.sdk.VirtualNetworkStatus; + +import net.kaaass.zerotierfix.R; + +public enum NetworkStatus { + REQUESTING_CONFIGURATION(0), OK(1), ACCESS_DENIED(2), NOT_FOUND(3), PORT_ERROR(4), CLIENT_TOO_OLD(5), AUTHENTICATION_REQUIRED(6); + + private final int id; + + NetworkStatus(int i) { + this.id = i; + } + + public static NetworkStatus fromInt(int i) { + switch (i) { + case 0: + return REQUESTING_CONFIGURATION; + case 1: + return OK; + case 2: + return ACCESS_DENIED; + case 3: + return NOT_FOUND; + case 4: + return PORT_ERROR; + case 5: + return CLIENT_TOO_OLD; + case 6: + return AUTHENTICATION_REQUIRED; + default: + throw new RuntimeException("Unhandled value: " + i); + } + } + + public static NetworkStatus fromVirtualNetworkStatus(VirtualNetworkStatus virtualNetworkStatus) { + switch (virtualNetworkStatus) { + case NETWORK_STATUS_REQUESTING_CONFIGURATION: + return REQUESTING_CONFIGURATION; + case NETWORK_STATUS_OK: + return OK; + case NETWORK_STATUS_ACCESS_DENIED: + return ACCESS_DENIED; + case NETWORK_STATUS_NOT_FOUND: + return NOT_FOUND; + case NETWORK_STATUS_PORT_ERROR: + return PORT_ERROR; + case NETWORK_STATUS_CLIENT_TOO_OLD: + return CLIENT_TOO_OLD; + case NETWORK_STATUS_AUTHENTICATION_REQUIRED: + return AUTHENTICATION_REQUIRED; + default: + throw new RuntimeException("Unhandled status: " + virtualNetworkStatus); + } + } + + public int toStringId() { + switch (this) { + case REQUESTING_CONFIGURATION: + return R.string.network_status_requesting_configuration; + case OK: + return R.string.network_status_ok; + case ACCESS_DENIED: + return R.string.network_status_access_denied; + case NOT_FOUND: + return R.string.network_status_not_found; + case PORT_ERROR: + return R.string.network_status_port_error; + case CLIENT_TOO_OLD: + return R.string.network_status_client_too_old; + case AUTHENTICATION_REQUIRED: + return R.string.network_status_authentication_required; + default: + return R.string.network_status_unknown; + } + } + + public int toInt() { + return this.id; + } +} diff --git a/app/src/main/java/net/kaaass/zerotierfix/model/type/NetworkType.java b/app/src/main/java/net/kaaass/zerotierfix/model/type/NetworkType.java new file mode 100644 index 0000000..9bf5cb7 --- /dev/null +++ b/app/src/main/java/net/kaaass/zerotierfix/model/type/NetworkType.java @@ -0,0 +1,53 @@ +package net.kaaass.zerotierfix.model.type; + +import com.zerotier.sdk.VirtualNetworkType; + +import net.kaaass.zerotierfix.R; + +public enum NetworkType { + UNKNOWN(0), + PRIVATE(1), + PUBLIC(2); + + private final int id; + + NetworkType(int i) { + this.id = i; + } + + public static NetworkType fromInt(int i) { + if (i != 0) { + if (i == 1) { + return PUBLIC; + } + if (i == 2) { + return PUBLIC; + } + throw new RuntimeException("Unhandled value: " + i); + } + return PRIVATE; + } + + public static NetworkType fromVirtualNetworkType(VirtualNetworkType virtualNetworkType) { + switch (virtualNetworkType) { + case NETWORK_TYPE_PRIVATE: + return PRIVATE; + case NETWORK_TYPE_PUBLIC: + return PUBLIC; + default: + throw new RuntimeException("Unhandled type: " + virtualNetworkType); + } + } + + public int toStringId() { + int i = this.id; + if (i != 1) { + return i != 2 ? R.string.network_type_unknown : R.string.network_type_public; + } + return R.string.network_type_private; + } + + public int toInt() { + return this.id; + } +} diff --git a/app/src/main/java/net/kaaass/zerotierfix/service/ARPEntry.java b/app/src/main/java/net/kaaass/zerotierfix/service/ARPEntry.java new file mode 100644 index 0000000..a0f63e7 --- /dev/null +++ b/app/src/main/java/net/kaaass/zerotierfix/service/ARPEntry.java @@ -0,0 +1,28 @@ +package net.kaaass.zerotierfix.service; + +import java.net.InetAddress; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.Setter; + +/** + * ARP 表项。记录 MAC 与 IPv4 地址的对应关系及记录时间 + */ +@Data +public class ARPEntry { + private final long mac; + private final InetAddress address; + @Setter(AccessLevel.NONE) + private long time; + + ARPEntry(long mac, InetAddress inetAddress) { + this.mac = mac; + this.address = inetAddress; + updateTime(); + } + + public void updateTime() { + this.time = System.currentTimeMillis(); + } +} diff --git a/app/src/main/java/net/kaaass/zerotierfix/service/ARPReplyData.java b/app/src/main/java/net/kaaass/zerotierfix/service/ARPReplyData.java new file mode 100644 index 0000000..9de3b33 --- /dev/null +++ b/app/src/main/java/net/kaaass/zerotierfix/service/ARPReplyData.java @@ -0,0 +1,14 @@ +package net.kaaass.zerotierfix.service; + +import java.net.InetAddress; + +import lombok.Data; + +/** + * ARP 应答报文的所需数据。由于报文内容总是当前节点的 IP 与 MAC,因此仅记录应答报文目标的信息。 + */ +@Data +public class ARPReplyData { + private final long destMac; + private final InetAddress destAddress; +} diff --git a/app/src/main/java/net/kaaass/zerotierfix/service/ARPTable.java b/app/src/main/java/net/kaaass/zerotierfix/service/ARPTable.java index b95ebf1..d048f40 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/service/ARPTable.java +++ b/app/src/main/java/net/kaaass/zerotierfix/service/ARPTable.java @@ -12,9 +12,9 @@ public class ARPTable { private static final long ENTRY_TIMEOUT = 120000; private static final int REPLY = 2; private static final int REQUEST = 1; - private final HashMap entriesMap = new HashMap<>(); + private final HashMap entriesMap = new HashMap<>(); private final HashMap inetAddressToMacAddress = new HashMap<>(); - private final HashMap ipEntriesMap = new HashMap<>(); + private final HashMap ipEntriesMap = new HashMap<>(); private final HashMap macAddressToInetAdddress = new HashMap<>(); private final Thread timeoutThread = new Thread("ARP Timeout Thread") { /* class com.zerotier.one.service.ARPTable.AnonymousClass1 */ @@ -22,14 +22,14 @@ public class ARPTable { public void run() { while (!isInterrupted()) { try { - for (ArpEntry arpEntry : new HashMap<>(ARPTable.this.entriesMap).values()) { - if (arpEntry.time + ARPTable.ENTRY_TIMEOUT < System.currentTimeMillis()) { + for (ARPEntry arpEntry : new HashMap<>(ARPTable.this.entriesMap).values()) { + if (arpEntry.getTime() + ARPTable.ENTRY_TIMEOUT < System.currentTimeMillis()) { Log.d(ARPTable.TAG, "Removing " + arpEntry.getAddress().toString() + " from ARP cache"); synchronized (ARPTable.this.macAddressToInetAdddress) { - ARPTable.this.macAddressToInetAdddress.remove(arpEntry.mac); + ARPTable.this.macAddressToInetAdddress.remove(arpEntry.getMac()); } synchronized (ARPTable.this.inetAddressToMacAddress) { - ARPTable.this.inetAddressToMacAddress.remove(arpEntry.address); + ARPTable.this.inetAddressToMacAddress.remove(arpEntry.getAddress()); } synchronized (ARPTable.this.entriesMap) { ARPTable.this.entriesMap.remove(arpEntry.getMac()); @@ -61,13 +61,6 @@ public static byte[] longToBytes(long j) { return allocate.array(); } - /* access modifiers changed from: protected */ - public void finalize() throws Throwable { - stop(); - super.finalize(); - } - - /* access modifiers changed from: protected */ public void stop() { try { this.timeoutThread.interrupt(); @@ -84,7 +77,7 @@ public void setAddress(InetAddress inetAddress, long j) { synchronized (this.macAddressToInetAdddress) { this.macAddressToInetAdddress.put(j, inetAddress); } - ArpEntry arpEntry = new ArpEntry(j, inetAddress); + ARPEntry arpEntry = new ARPEntry(j, inetAddress); synchronized (this.entriesMap) { this.entriesMap.put(j, arpEntry); } @@ -95,7 +88,7 @@ public void setAddress(InetAddress inetAddress, long j) { private void updateArpEntryTime(long j) { synchronized (this.entriesMap) { - ArpEntry arpEntry = this.entriesMap.get(j); + ARPEntry arpEntry = this.entriesMap.get(j); if (arpEntry != null) { arpEntry.updateTime(); } @@ -104,7 +97,7 @@ private void updateArpEntryTime(long j) { private void updateArpEntryTime(InetAddress inetAddress) { synchronized (this.ipEntriesMap) { - ArpEntry arpEntry = this.ipEntriesMap.get(inetAddress); + ARPEntry arpEntry = this.ipEntriesMap.get(inetAddress); if (arpEntry != null) { arpEntry.updateTime(); } @@ -118,10 +111,13 @@ public long getMacForAddress(InetAddress inetAddress) { return -1; } Log.d(TAG, "Returning MAC for " + inetAddress.toString()); - long longValue = this.inetAddressToMacAddress.get(inetAddress); - updateArpEntryTime(longValue); - return longValue; + var longValue = this.inetAddressToMacAddress.get(inetAddress); + if (longValue != null) { + updateArpEntryTime(longValue); + return longValue; + } } + return -1; } /* access modifiers changed from: package-private */ @@ -177,82 +173,49 @@ public byte[] getARPPacket(int i, long j, long j2, InetAddress inetAddress, Inet return bArr; } - public ARPReplyData processARPPacket(byte[] bArr) { - InetAddress inetAddress; - InetAddress inetAddress2; + public ARPReplyData processARPPacket(byte[] packetData) { + InetAddress srcAddress; + InetAddress dstAddress; Log.d(TAG, "Processing ARP packet"); - byte[] bArr2 = new byte[8]; - System.arraycopy(bArr, 8, bArr2, 2, 6); - byte[] bArr3 = new byte[4]; - System.arraycopy(bArr, 14, bArr3, 0, 4); - byte[] bArr4 = new byte[8]; - System.arraycopy(bArr, 18, bArr4, 2, 6); - byte[] bArr5 = new byte[4]; - System.arraycopy(bArr, 24, bArr5, 0, 4); + + // 解析包内 IP、MAC 地址 + byte[] rawSrcMac = new byte[8]; + System.arraycopy(packetData, 8, rawSrcMac, 2, 6); + byte[] rawSrcAddress = new byte[4]; + System.arraycopy(packetData, 14, rawSrcAddress, 0, 4); + byte[] rawDstMac = new byte[8]; + System.arraycopy(packetData, 18, rawDstMac, 2, 6); + byte[] rawDstAddress = new byte[4]; + System.arraycopy(packetData, 24, rawDstAddress, 0, 4); try { - inetAddress = InetAddress.getByAddress(bArr3); + srcAddress = InetAddress.getByAddress(rawSrcAddress); } catch (Exception unused) { - inetAddress = null; + srcAddress = null; } try { - inetAddress2 = InetAddress.getByAddress(bArr5); - } catch (Exception unused2) { - inetAddress2 = null; - } - long j = ByteBuffer.wrap(bArr2).getLong(); - long j2 = ByteBuffer.wrap(bArr4).getLong(); - if (!(j == 0 || inetAddress == null)) { - setAddress(inetAddress, j); - } - if (!(j2 == 0 || inetAddress2 == null)) { - setAddress(inetAddress2, j2); - } - if (bArr[7] != 1) { - return null; - } - Log.d(TAG, "Reply needed"); - ARPReplyData aRPReplyData = new ARPReplyData(); - aRPReplyData.destMac = j; - aRPReplyData.destAddress = inetAddress; - return aRPReplyData; - } - - public static class ARPReplyData { - public InetAddress destAddress; - public long destMac; - public InetAddress senderAddress; - public long senderMac; - - public ARPReplyData() { - } - } - - /* access modifiers changed from: private */ - public static class ArpEntry { - private final InetAddress address; - private final long mac; - private long time; - - ArpEntry(long j, InetAddress inetAddress) { - this.mac = j; - this.address = inetAddress; - updateTime(); + dstAddress = InetAddress.getByAddress(rawDstAddress); + } catch (Exception unused) { + dstAddress = null; } + long srcMac = ByteBuffer.wrap(rawSrcMac).getLong(); + long dstMac = ByteBuffer.wrap(rawDstMac).getLong(); - public long getMac() { - return this.mac; + // 更新 ARP 表项 + if (srcMac != 0 && srcAddress != null) { + setAddress(srcAddress, srcMac); } - - public InetAddress getAddress() { - return this.address; + if (dstMac != 0 && dstAddress != null) { + setAddress(dstAddress, dstMac); } - public void updateTime() { - this.time = System.currentTimeMillis(); - } - - public boolean equals(ArpEntry arpEntry) { - return this.mac == arpEntry.mac && this.address.equals(arpEntry.address); + // 处理响应行为 + var packetType = packetData[7]; + if (packetType == REQUEST) { + // ARP 请求,返回应答数据 + Log.d(TAG, "Reply needed"); + return new ARPReplyData(srcMac, srcAddress); + } else { + return null; } } } diff --git a/app/src/main/java/net/kaaass/zerotierfix/service/NDPEntry.java b/app/src/main/java/net/kaaass/zerotierfix/service/NDPEntry.java new file mode 100644 index 0000000..868473e --- /dev/null +++ b/app/src/main/java/net/kaaass/zerotierfix/service/NDPEntry.java @@ -0,0 +1,28 @@ +package net.kaaass.zerotierfix.service; + +import java.net.InetAddress; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.Setter; + +/** + * NDP 表项。记录 MAC 与 IPv6 地址的对应关系及记录时间 + */ +@Data +public class NDPEntry { + private final long mac; + private final InetAddress address; + @Setter(AccessLevel.NONE) + private long time; + + NDPEntry(long j, InetAddress inetAddress) { + this.mac = j; + this.address = inetAddress; + updateTime(); + } + + public void updateTime() { + this.time = System.currentTimeMillis(); + } +} diff --git a/app/src/main/java/net/kaaass/zerotierfix/service/NDPTable.java b/app/src/main/java/net/kaaass/zerotierfix/service/NDPTable.java index 6605190..f5daf92 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/service/NDPTable.java +++ b/app/src/main/java/net/kaaass/zerotierfix/service/NDPTable.java @@ -19,16 +19,17 @@ public class NDPTable { private final Thread timeoutThread = new Thread("NDP Timeout Thread") { /* class com.zerotier.one.service.NDPTable.AnonymousClass1 */ + @Override public void run() { while (!isInterrupted()) { try { for (NDPEntry nDPEntry : new HashMap<>(NDPTable.this.entriesMap).values()) { - if (nDPEntry.time + NDPTable.ENTRY_TIMEOUT < System.currentTimeMillis()) { + if (nDPEntry.getTime() + NDPTable.ENTRY_TIMEOUT < System.currentTimeMillis()) { synchronized (NDPTable.this.macAddressToInetAddress) { - NDPTable.this.macAddressToInetAddress.remove(nDPEntry.mac); + NDPTable.this.macAddressToInetAddress.remove(nDPEntry.getMac()); } synchronized (NDPTable.this.inetAddressToMacAddress) { - NDPTable.this.inetAddressToMacAddress.remove(nDPEntry.address); + NDPTable.this.inetAddressToMacAddress.remove(nDPEntry.getAddress()); } synchronized (NDPTable.this.entriesMap) { NDPTable.this.entriesMap.remove(nDPEntry.getMac()); @@ -51,12 +52,6 @@ public NDPTable() { this.timeoutThread.start(); } - /* access modifiers changed from: protected */ - public void finalize() throws Throwable { - stop(); - super.finalize(); - } - /* access modifiers changed from: protected */ public void stop() { try { @@ -169,33 +164,4 @@ public byte[] getNeighborSolicitationPacket(InetAddress inetAddress, InetAddress return bArr; } - /* access modifiers changed from: private */ - public class NDPEntry { - private final InetAddress address; - private final long mac; - private long time; - - NDPEntry(long j, InetAddress inetAddress) { - this.mac = j; - this.address = inetAddress; - updateTime(); - } - - public long getMac() { - return this.mac; - } - - public InetAddress getAddress() { - return this.address; - } - - /* access modifiers changed from: package-private */ - public void updateTime() { - this.time = System.currentTimeMillis(); - } - - public boolean equals(NDPEntry nDPEntry) { - return this.mac == nDPEntry.mac && this.address.equals(nDPEntry.address); - } - } } diff --git a/app/src/main/java/net/kaaass/zerotierfix/service/NetworkStateReceiver.java b/app/src/main/java/net/kaaass/zerotierfix/service/NetworkStateReceiver.java deleted file mode 100644 index 877dd19..0000000 --- a/app/src/main/java/net/kaaass/zerotierfix/service/NetworkStateReceiver.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.kaaass.zerotierfix.service; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; - -// TODO: clear up -public class NetworkStateReceiver extends BroadcastReceiver { - private static final String TAG = "NetworkStateReceiver"; - - public void onReceive(Context context, Intent intent) { - NetworkInfo activeNetworkInfo = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo(); - if (activeNetworkInfo != null) { - activeNetworkInfo.isConnectedOrConnecting(); - } - } -} diff --git a/app/src/main/java/net/kaaass/zerotierfix/service/Route.java b/app/src/main/java/net/kaaass/zerotierfix/service/Route.java index 448f1c6..f4e8f62 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/service/Route.java +++ b/app/src/main/java/net/kaaass/zerotierfix/service/Route.java @@ -4,32 +4,18 @@ import java.net.InetAddress; -// TODO: clear up -public class Route { - InetAddress address; - InetAddress gateway = null; - int prefix; - - public Route(InetAddress inetAddress, int i) { - this.address = inetAddress; - this.prefix = i; - } - - /* access modifiers changed from: package-private */ - public InetAddress getGateway() { - return this.gateway; - } +import lombok.Data; - /* access modifiers changed from: package-private */ - public void setGateway(InetAddress inetAddress) { - this.gateway = inetAddress; - } +/** + * 路由记录数据类 + */ +@Data +public class Route { + private final InetAddress address; + private final int prefix; + private InetAddress gateway = null; public boolean belongsToRoute(InetAddress inetAddress) { return this.address.equals(InetAddressUtils.addressToRoute(inetAddress, this.prefix)); } - - public boolean equals(Route route) { - return this.address.equals(route.address) && this.prefix == route.prefix && this.gateway.equals(route.gateway); - } } diff --git a/app/src/main/java/net/kaaass/zerotierfix/service/StartupReceiver.java b/app/src/main/java/net/kaaass/zerotierfix/service/StartupReceiver.java index d2095a9..798686b 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/service/StartupReceiver.java +++ b/app/src/main/java/net/kaaass/zerotierfix/service/StartupReceiver.java @@ -6,13 +6,16 @@ import android.preference.PreferenceManager; import android.util.Log; +import net.kaaass.zerotierfix.util.Constants; + // TODO: clear up public class StartupReceiver extends BroadcastReceiver { private static final String TAG = "StartupReceiver"; public void onReceive(Context context, Intent intent) { Log.i(TAG, "Received: " + intent.getAction() + ". Starting ZeroTier One service."); - if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("general_start_zerotier_on_boot", true)) { + var pref = PreferenceManager.getDefaultSharedPreferences(context); + if (pref.getBoolean(Constants.PREF_GENERAL_START_ZEROTIER_ON_BOOT, true)) { Log.i(TAG, "Preferences set to start ZeroTier on boot"); } else { Log.i(TAG, "Preferences set to not start ZeroTier on boot"); diff --git a/app/src/main/java/net/kaaass/zerotierfix/service/TunTapAdapter.java b/app/src/main/java/net/kaaass/zerotierfix/service/TunTapAdapter.java index b819b52..20c3861 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/service/TunTapAdapter.java +++ b/app/src/main/java/net/kaaass/zerotierfix/service/TunTapAdapter.java @@ -8,6 +8,7 @@ import com.zerotier.sdk.ResultCode; import com.zerotier.sdk.VirtualNetworkConfig; import com.zerotier.sdk.VirtualNetworkFrameListener; +import com.zerotier.sdk.util.StringUtils; import net.kaaass.zerotierfix.util.IPPacketUtils; import net.kaaass.zerotierfix.util.InetAddressUtils; @@ -23,25 +24,24 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.HashMap; +import java.util.Objects; // TODO: clear up public class TunTapAdapter implements VirtualNetworkFrameListener { - public static final long BROADCAST_MAC = 281474976710655L; public static final String TAG = "TunTapAdapter"; private static final int ARP_PACKET = 2054; private static final int IPV4_PACKET = 2048; private static final int IPV6_PACKET = 34525; private final HashMap routeMap = new HashMap<>(); + private final long networkId; + private final ZeroTierOneService ztService; private ARPTable arpTable = new ARPTable(); - private VirtualNetworkConfig cfg; private FileInputStream in; private NDPTable ndpTable = new NDPTable(); - private final long networkId; private Node node; private FileOutputStream out; private Thread receiveThread; private ParcelFileDescriptor vpnSocket; - private final ZeroTierOneService ztService; public TunTapAdapter(ZeroTierOneService zeroTierOneService, long j) { this.ztService = zeroTierOneService; @@ -63,21 +63,22 @@ public static long multicastAddressToMAC(InetAddress inetAddress) { private void addMulticastRoutes() { } - public void setNetworkConfig(VirtualNetworkConfig virtualNetworkConfig) { - this.cfg = virtualNetworkConfig; - } - - public void setNode(Node node2) { - this.node = node2; + public void setNode(Node node) { + this.node = node; try { - node2.multicastSubscribe(this.networkId, multicastAddressToMAC(InetAddress.getByName("224.224.224.224"))); + var multicastAddress = InetAddress.getByName("224.224.224.224"); + var result = node + .multicastSubscribe(this.networkId, multicastAddressToMAC(multicastAddress)); + if (result != ResultCode.RESULT_OK) { + Log.e(TAG, "Error when calling multicastSubscribe: " + result); + } } catch (UnknownHostException e) { - Log.e(TAG, "", e); + Log.e(TAG, e.toString(), e); } } - public void setVpnSocket(ParcelFileDescriptor parcelFileDescriptor) { - this.vpnSocket = parcelFileDescriptor; + public void setVpnSocket(ParcelFileDescriptor vpnSocket) { + this.vpnSocket = vpnSocket; } public void setFileStreams(FileInputStream fileInputStream, FileOutputStream fileOutputStream) { @@ -85,9 +86,9 @@ public void setFileStreams(FileInputStream fileInputStream, FileOutputStream fil this.out = fileOutputStream; } - public void addRouteAndNetwork(Route route, long j) { + public void addRouteAndNetwork(Route route, long networkId) { synchronized (this.routeMap) { - this.routeMap.put(route, Long.valueOf(j)); + this.routeMap.put(route, networkId); } } @@ -99,63 +100,60 @@ public void clearRouteMap() { } private boolean isIPv4Multicast(InetAddress inetAddress) { - byte[] address = inetAddress.getAddress(); - return address[0] >= -32 && address[0] <= -17; + return (inetAddress.getAddress()[0] & 0xF0) == 224; } private boolean isIPv6Multicast(InetAddress inetAddress) { - byte[] address = inetAddress.getAddress(); - return address[0] == -1 && address[1] >= 0 && address[1] <= -2; + return (inetAddress.getAddress()[0] & 0xFF) == 0xFF; } public void startThreads() { this.receiveThread = new Thread("Tunnel Receive Thread") { - /* class com.zerotier.one.service.TunTapAdapter.AnonymousClass1 */ + @Override public void run() { + // 创建 ARP、NDP 表 if (TunTapAdapter.this.ndpTable == null) { TunTapAdapter.this.ndpTable = new NDPTable(); } if (TunTapAdapter.this.arpTable == null) { TunTapAdapter.this.arpTable = new ARPTable(); } + // 转发 TUN 消息至 Zerotier try { Log.d(TunTapAdapter.TAG, "TUN Receive Thread Started"); - ByteBuffer allocate = ByteBuffer.allocate(32767); - allocate.order(ByteOrder.LITTLE_ENDIAN); + var buffer = ByteBuffer.allocate(32767); + buffer.order(ByteOrder.LITTLE_ENDIAN); while (!isInterrupted()) { try { - boolean z = true; - int read = TunTapAdapter.this.in.read(allocate.array()); - if (read > 0) { - Log.d(TunTapAdapter.TAG, "Sending packet to ZeroTier. " + read + " bytes."); - byte[] bArr = new byte[read]; - System.arraycopy(allocate.array(), 0, bArr, 0, read); - byte iPVersion = IPPacketUtils.getIPVersion(bArr); + boolean noDataBeenRead = true; + int readCount = TunTapAdapter.this.in.read(buffer.array()); + if (readCount > 0) { + Log.d(TunTapAdapter.TAG, "Sending packet to ZeroTier. " + readCount + " bytes."); + var readData = new byte[readCount]; + System.arraycopy(buffer.array(), 0, readData, 0, readCount); + byte iPVersion = IPPacketUtils.getIPVersion(readData); if (iPVersion == 4) { - TunTapAdapter.this.handleIPv4Packet(bArr); + TunTapAdapter.this.handleIPv4Packet(readData); } else if (iPVersion == 6) { - TunTapAdapter.this.handleIPv6Packet(bArr); + TunTapAdapter.this.handleIPv6Packet(readData); } else { Log.e(TunTapAdapter.TAG, "Unknown IP version"); } - allocate.clear(); - z = false; + buffer.clear(); + noDataBeenRead = false; } - if (z) { + if (noDataBeenRead) { Thread.sleep(10); } - } catch (InterruptedException e) { - Log.e(TunTapAdapter.TAG, "Tun/Tap Interrupted", e); - throw e; - } catch (Exception e2) { - Log.e(TunTapAdapter.TAG, "Error in TUN Receive", e2); + } catch (IOException e) { + Log.e(TunTapAdapter.TAG, "Error in TUN Receive: " + e.getMessage(), e); } } - } catch (Exception e3) { - Log.e(TunTapAdapter.TAG, "Exception ending Tun/Tap", e3); + } catch (InterruptedException ignored) { } Log.d(TunTapAdapter.TAG, "TUN Receive Thread ended"); + // 关闭 ARP、NDP 表 TunTapAdapter.this.ndpTable.stop(); TunTapAdapter.this.ndpTable = null; TunTapAdapter.this.arpTable.stop(); @@ -165,209 +163,193 @@ public void run() { this.receiveThread.start(); } - /* access modifiers changed from: private */ - /* access modifiers changed from: public */ - private void handleIPv4Packet(byte[] bArr) { - boolean z; - InetAddress inetAddress; - int i; - long j; - InetAddress destIP = IPPacketUtils.getDestIP(bArr); - InetAddress sourceIP = IPPacketUtils.getSourceIP(bArr); - if (this.cfg == null) { + private void handleIPv4Packet(byte[] packetData) { + boolean isMulticast; + long destMac; + var destIP = IPPacketUtils.getDestIP(packetData); + var sourceIP = IPPacketUtils.getSourceIP(packetData); + var virtualNetworkConfig = this.ztService.getVirtualNetworkConfig(this.networkId); + + if (virtualNetworkConfig == null) { Log.e(TAG, "TunTapAdapter has no network config yet"); return; + } else if (destIP == null) { + Log.e(TAG, "destAddress is null"); + return; + } else if (sourceIP == null) { + Log.e(TAG, "sourceAddress is null"); + return; } if (isIPv4Multicast(destIP)) { - this.node.multicastSubscribe(this.networkId, multicastAddressToMAC(destIP)); - z = true; + var result = this.node.multicastSubscribe(this.networkId, multicastAddressToMAC(destIP)); + if (result != ResultCode.RESULT_OK) { + Log.e(TAG, "Error when calling multicastSubscribe: " + result); + } + isMulticast = true; } else { - z = false; + isMulticast = false; } - Route routeForDestination = routeForDestination(destIP); - InetAddress gateway = routeForDestination != null ? routeForDestination.getGateway() : null; - InetSocketAddress[] assignedAddresses = this.cfg.assignedAddresses(); - int length = assignedAddresses.length; - int i2 = 0; - while (true) { - if (i2 >= length) { - inetAddress = null; - i = 0; + var route = routeForDestination(destIP); + var gateway = route != null ? route.getGateway() : null; + + // 查找当前节点的 v4 地址 + var ztAddresses = virtualNetworkConfig.getAssignedAddresses(); + InetAddress localV4Address = null; + int cidr = 0; + + for (var address : ztAddresses) { + if (address.getAddress() instanceof Inet4Address) { + localV4Address = address.getAddress(); + cidr = address.getPort(); break; } - InetSocketAddress inetSocketAddress = assignedAddresses[i2]; - if (inetSocketAddress.getAddress() instanceof Inet4Address) { - i = inetSocketAddress.getPort(); - inetAddress = inetSocketAddress.getAddress(); - break; - } - i2++; } - if (gateway != null && !InetAddressUtils.addressToRouteNo0Route(destIP, i).equals(InetAddressUtils.addressToRouteNo0Route(sourceIP, i))) { + + var destRoute = InetAddressUtils.addressToRouteNo0Route(destIP, cidr); + var sourceRoute = InetAddressUtils.addressToRouteNo0Route(sourceIP, cidr); + if (gateway != null && !Objects.equals(destRoute, sourceRoute)) { destIP = gateway; } - if (inetAddress == null) { + if (localV4Address == null) { Log.e(TAG, "Couldn't determine local address"); return; } - long macAddress = this.cfg.macAddress(); - long[] jArr = new long[1]; - if (z || this.arpTable.hasMacForAddress(destIP)) { + + long localMac = virtualNetworkConfig.getMac(); + long[] nextDeadline = new long[1]; + if (isMulticast || this.arpTable.hasMacForAddress(destIP)) { + // 已确定目标 MAC,直接发送 if (isIPv4Multicast(destIP)) { - j = multicastAddressToMAC(destIP); + destMac = multicastAddressToMAC(destIP); } else { - j = this.arpTable.getMacForAddress(destIP); + destMac = this.arpTable.getMacForAddress(destIP); } - ResultCode processVirtualNetworkFrame = this.node.processVirtualNetworkFrame(System.currentTimeMillis(), this.networkId, macAddress, j, 2048, 0, bArr, jArr); - if (processVirtualNetworkFrame != ResultCode.RESULT_OK) { - Log.e(TAG, "Error calling processVirtualNetworkFrame: " + processVirtualNetworkFrame.toString()); + var result = this.node.processVirtualNetworkFrame(System.currentTimeMillis(), this.networkId, localMac, destMac, IPV4_PACKET, 0, packetData, nextDeadline); + if (result != ResultCode.RESULT_OK) { + Log.e(TAG, "Error calling processVirtualNetworkFrame: " + result.toString()); return; } Log.d(TAG, "Packet sent to ZT"); - this.ztService.setNextBackgroundTaskDeadline(jArr[0]); - return; - } - Log.d(TAG, "Unknown dest MAC address. Need to look it up. " + destIP); - ResultCode processVirtualNetworkFrame2 = this.node.processVirtualNetworkFrame(System.currentTimeMillis(), this.networkId, macAddress, BROADCAST_MAC, ARP_PACKET, 0, this.arpTable.getRequestPacket(macAddress, inetAddress, destIP), jArr); - if (processVirtualNetworkFrame2 != ResultCode.RESULT_OK) { - Log.e(TAG, "Error sending ARP packet: " + processVirtualNetworkFrame2.toString()); - return; + this.ztService.setNextBackgroundTaskDeadline(nextDeadline[0]); + } else { + // 目标 MAC 未知,进行 ARP 查询 + Log.d(TAG, "Unknown dest MAC address. Need to look it up. " + destIP); + destMac = InetAddressUtils.BROADCAST_MAC_ADDRESS; + packetData = this.arpTable.getRequestPacket(localMac, localV4Address, destIP); + var result = this.node.processVirtualNetworkFrame(System.currentTimeMillis(), this.networkId, localMac, destMac, ARP_PACKET, 0, packetData, nextDeadline); + if (result != ResultCode.RESULT_OK) { + Log.e(TAG, "Error sending ARP packet: " + result.toString()); + return; + } + Log.d(TAG, "ARP Request Sent!"); + this.ztService.setNextBackgroundTaskDeadline(nextDeadline[0]); } - Log.d(TAG, "ARP Request Sent!"); - this.ztService.setNextBackgroundTaskDeadline(jArr[0]); } - /* access modifiers changed from: private */ - /* access modifiers changed from: public */ - /* JADX WARNING: Removed duplicated region for block: B:49:0x00d7 */ - /* JADX WARNING: Removed duplicated region for block: B:55:0x011e */ - /* JADX WARNING: Removed duplicated region for block: B:65:? A[RETURN, SYNTHETIC] */ - /* Code decompiled incorrectly, please refer to instructions dump. */ - // Decomp by fernflower - private void handleIPv6Packet(byte[] var1) { - InetAddress var2 = IPPacketUtils.getDestIP(var1); - InetAddress var3 = IPPacketUtils.getSourceIP(var1); - if (this.cfg == null) { - Log.e("TunTapAdapter", "TunTapAdapter has no network config yet"); - } else { - if (this.isIPv6Multicast(var2)) { - this.node.multicastSubscribe(this.networkId, multicastAddressToMAC(var2)); - } + private void handleIPv6Packet(byte[] packetData) { + var destIP = IPPacketUtils.getDestIP(packetData); + var sourceIP = IPPacketUtils.getSourceIP(packetData); + var virtualNetworkConfig = this.ztService.getVirtualNetworkConfig(this.networkId); - Route var4 = this.routeForDestination(var2); - InetAddress var15; - if (var4 != null) { - var15 = var4.getGateway(); - } else { - var15 = null; + if (virtualNetworkConfig == null) { + Log.e(TAG, "TunTapAdapter has no network config yet"); + return; + } else if (destIP == null) { + Log.e(TAG, "destAddress is null"); + return; + } else if (sourceIP == null) { + Log.e(TAG, "sourceAddress is null"); + return; + } + if (this.isIPv6Multicast(destIP)) { + var result = this.node.multicastSubscribe(this.networkId, multicastAddressToMAC(destIP)); + if (result != ResultCode.RESULT_OK) { + Log.e(TAG, "Error when calling multicastSubscribe: " + result); } + } + var route = routeForDestination(destIP); + var gateway = route != null ? route.getGateway() : null; + + // 查找当前节点的 v6 地址 + var ztAddresses = virtualNetworkConfig.getAssignedAddresses(); + InetAddress localV4Address = null; + int cidr = 0; + + for (var address : ztAddresses) { + if (address.getAddress() instanceof Inet6Address) { + localV4Address = address.getAddress(); + cidr = address.getPort(); + break; + } + } - InetSocketAddress[] var5 = this.cfg.assignedAddresses(); - int var6 = var5.length; - int var7 = 0; - - InetAddress var19; - while (true) { - if (var7 >= var6) { - var19 = null; - var7 = 0; - break; - } + var destRoute = InetAddressUtils.addressToRouteNo0Route(destIP, cidr); + var sourceRoute = InetAddressUtils.addressToRouteNo0Route(sourceIP, cidr); + if (gateway != null && !Objects.equals(destRoute, sourceRoute)) { + destIP = gateway; + } + if (localV4Address == null) { + Log.e(TAG, "Couldn't determine local address"); + return; + } - InetSocketAddress var8 = var5[var7]; - if (var8.getAddress() instanceof Inet6Address) { - var7 = var8.getPort(); - var19 = var8.getAddress(); - break; - } + long localMac = virtualNetworkConfig.getMac(); + long[] nextDeadline = new long[1]; - ++var7; + // 确定目标 MAC 地址 + long destMac; + boolean sendNSPacket = false; + if (this.isNeighborSolicitation(packetData)) { + // 收到本地 NS 报文,根据 NDP 表记录确定是否广播查询 + if (this.ndpTable.hasMacForAddress(destIP)) { + destMac = this.ndpTable.getMacForAddress(destIP); + } else { + destMac = InetAddressUtils.ipv6ToMulticastAddress(destIP); } - - InetAddress var16 = var2; - if (var15 != null) { - var16 = var2; - if (!InetAddressUtils.addressToRouteNo0Route(var2, var7).equals(InetAddressUtils.addressToRouteNo0Route(var3, var7))) { - var16 = var15; - } + } else if (this.isIPv6Multicast(destIP)) { + // 多播报文 + destMac = multicastAddressToMAC(destIP); + } else if (this.isNeighborAdvertisement(packetData)) { + // 收到本地 NA 报文 + if (this.ndpTable.hasMacForAddress(destIP)) { + destMac = this.ndpTable.getMacForAddress(destIP); + } else { + // 目标 MAC 未知,不发送数据包 + destMac = 0L; } - - if (var19 == null) { - Log.e("TunTapAdapter", "Couldn't determine local address"); + sendNSPacket = true; + } else if (this.ndpTable.hasMacForAddress(destIP)) { + // 目标地址 MAC 已知 + destMac = this.ndpTable.getMacForAddress(destIP); + } else { + destMac = 0L; + } + // 发送数据包 + if (destMac != 0L) { + var result = this.node.processVirtualNetworkFrame(System.currentTimeMillis(), this.networkId, localMac, destMac, IPV6_PACKET, 0, packetData, nextDeadline); + if (result != ResultCode.RESULT_OK) { + Log.e(TAG, "Error calling processVirtualNetworkFrame: " + result.toString()); } else { - long var9; - long var11; - long[] var17; - boolean var18; - boolean var20; - label75: - { - var9 = this.cfg.macAddress(); - var18 = true; - var17 = new long[1]; - if (this.isNeighborSolicitation(var1)) { - if (this.ndpTable.hasMacForAddress(var16)) { - var11 = this.ndpTable.getMacForAddress(var16); - } else { - var11 = InetAddressUtils.ipv6ToMulticastAddress(var16); - } - } else if (this.isIPv6Multicast(var16)) { - var11 = multicastAddressToMAC(var16); - } else { - if (this.isNeighborAdvertisement(var1)) { - if (this.ndpTable.hasMacForAddress(var16)) { - var11 = this.ndpTable.getMacForAddress(var16); - } else { - var11 = 0L; - } - - var20 = true; - break label75; - } - - if (this.ndpTable.hasMacForAddress(var16)) { - var11 = this.ndpTable.getMacForAddress(var16); - } else { - var11 = 0L; - } - } - - var20 = false; - } - - long var21; - int var13 = (var21 = var11) == 0L ? 0 : (var21 < 0L ? -1 : 1); - ResultCode var14; - if (var13 != 0) { - var14 = this.node.processVirtualNetworkFrame(System.currentTimeMillis(), this.networkId, var9, var11, '\u86dd', 0, var1, var17); - if (var14 != ResultCode.RESULT_OK) { - Log.e("TunTapAdapter", "Error calling processVirtualNetworkFrame: " + var14.toString()); - } else { - Log.d("TunTapAdapter", "Packet sent to ZT"); - this.ztService.setNextBackgroundTaskDeadline(var17[0]); - } - - var18 = var20; - } - - if (var18) { - if (var13 == 0) { - var11 = InetAddressUtils.ipv6ToMulticastAddress(var16); - } - - Log.d("TunTapAdapter", "Sending Neighbor Solicitation"); - var1 = this.ndpTable.getNeighborSolicitationPacket(var3, var16, var9); - var14 = this.node.processVirtualNetworkFrame(System.currentTimeMillis(), this.networkId, var9, var11, '\u86dd', 0, var1, var17); - if (var14 != ResultCode.RESULT_OK) { - Log.e("TunTapAdapter", "Error calling processVirtualNetworkFrame: " + var14.toString()); - } else { - Log.d("TunTapAdapter", "Neighbor Solicitation sent to ZT"); - this.ztService.setNextBackgroundTaskDeadline(var17[0]); - } - } - + Log.d(TAG, "Packet sent to ZT"); + this.ztService.setNextBackgroundTaskDeadline(nextDeadline[0]); + } + } + // 发送 NS 请求 + if (sendNSPacket) { + if (destMac == 0L) { + destMac = InetAddressUtils.ipv6ToMulticastAddress(destIP); + } + Log.d(TAG, "Sending Neighbor Solicitation"); + packetData = this.ndpTable.getNeighborSolicitationPacket(sourceIP, destIP, localMac); + var result = this.node.processVirtualNetworkFrame(System.currentTimeMillis(), this.networkId, localMac, destMac, IPV6_PACKET, 0, packetData, nextDeadline); + if (result != ResultCode.RESULT_OK) { + Log.e(TAG, "Error calling processVirtualNetworkFrame: " + result.toString()); + } else { + Log.d(TAG, "Neighbor Solicitation sent to ZT"); + this.ztService.setNextBackgroundTaskDeadline(nextDeadline[0]); } } + } public void interrupt() { @@ -376,111 +358,132 @@ public void interrupt() { this.in.close(); this.out.close(); } catch (IOException e) { - Log.e(TAG, "Error stopping in/out", e); + Log.e(TAG, "Error stopping in/out: " + e.getMessage(), e); } this.receiveThread.interrupt(); try { this.receiveThread.join(); - } catch (InterruptedException unused) { + } catch (InterruptedException ignored) { } } } - private boolean isNeighborSolicitation(byte[] bArr) { - return bArr[6] == 58 && bArr[40] == -121; + public void join() throws InterruptedException { + this.receiveThread.join(); } - private boolean isNeighborAdvertisement(byte[] bArr) { - return bArr[6] == 58 && bArr[40] == -120; + private boolean isNeighborSolicitation(byte[] packetData) { + return packetData[6] == 58 && packetData[40] == -121; + } + + private boolean isNeighborAdvertisement(byte[] packetData) { + return packetData[6] == 58 && packetData[40] == -120; } public boolean isRunning() { - Thread thread = this.receiveThread; + var thread = this.receiveThread; if (thread == null) { return false; } return thread.isAlive(); } - @Override // com.zerotier.sdk.VirtualNetworkFrameListener - public void onVirtualNetworkFrame(long j, long j2, long j3, long j4, long j5, byte[] bArr) { - Log.d(TAG, "Got Virtual Network Frame. Network ID: " + Long.toHexString(j) + " Source MAC: " + Long.toHexString(j2) + " Dest MAC: " + Long.toHexString(j3) + " Ether type: " + j4 + " VLAN ID: " + j5 + " Frame Length: " + bArr.length); + /** + * 响应并处理 ZT 网络发送至本节点的以太网帧 + */ + @Override + public void onVirtualNetworkFrame(long networkId, long srcMac, long destMac, long etherType, + long vlanId, byte[] frameData) { + Log.d(TAG, "Got Virtual Network Frame. " + + " Network ID: " + StringUtils.networkIdToString(networkId) + + " Source MAC: " + StringUtils.macAddressToString(srcMac) + + " Dest MAC: " + StringUtils.macAddressToString(destMac) + + " Ether type: " + StringUtils.etherTypeToString(etherType) + + " VLAN ID: " + vlanId + " Frame Length: " + frameData.length); if (this.vpnSocket == null) { Log.e(TAG, "vpnSocket is null!"); } else if (this.in == null || this.out == null) { Log.e(TAG, "no in/out streams"); - } else if (j4 == 2054) { + } else if (etherType == ARP_PACKET) { + // 收到 ARP 包。更新 ARP 表,若需要则进行应答 Log.d(TAG, "Got ARP Packet"); - ARPTable.ARPReplyData processARPPacket = this.arpTable.processARPPacket(bArr); - if (processARPPacket != null && processARPPacket.destMac != 0 && processARPPacket.destAddress != null) { - long[] jArr = new long[1]; - VirtualNetworkConfig networkConfig = this.node.networkConfig(j); - InetAddress inetAddress = null; - InetSocketAddress[] assignedAddresses = networkConfig.assignedAddresses(); - int length = assignedAddresses.length; - int i = 0; - while (true) { - if (i >= length) { - break; - } - InetSocketAddress inetSocketAddress = assignedAddresses[i]; - if (inetSocketAddress.getAddress() instanceof Inet4Address) { - inetAddress = inetSocketAddress.getAddress(); + var arpReply = this.arpTable.processARPPacket(frameData); + if (arpReply != null && arpReply.getDestMac() != 0 && arpReply.getDestAddress() != null) { + // 获取本地 V4 地址 + var networkConfig = this.node.networkConfig(networkId); + InetAddress localV4Address = null; + for (var address : networkConfig.getAssignedAddresses()) { + if (address.getAddress() instanceof Inet4Address) { + localV4Address = address.getAddress(); break; } - i++; } - if (inetAddress != null) { - ResultCode processVirtualNetworkFrame = this.node.processVirtualNetworkFrame(System.currentTimeMillis(), j, networkConfig.macAddress(), j2, ARP_PACKET, 0, this.arpTable.getReplyPacket(networkConfig.macAddress(), inetAddress, processARPPacket.destMac, processARPPacket.destAddress), jArr); - if (processVirtualNetworkFrame != ResultCode.RESULT_OK) { - Log.e(TAG, "Error sending ARP packet: " + processVirtualNetworkFrame.toString()); + // 构造并返回 ARP 应答 + if (localV4Address != null) { + var nextDeadline = new long[1]; + var packetData = this.arpTable.getReplyPacket(networkConfig.getMac(), + localV4Address, arpReply.getDestMac(), arpReply.getDestAddress()); + var result = this.node + .processVirtualNetworkFrame(System.currentTimeMillis(), networkId, + networkConfig.getMac(), srcMac, ARP_PACKET, 0, + packetData, nextDeadline); + if (result != ResultCode.RESULT_OK) { + Log.e(TAG, "Error sending ARP packet: " + result.toString()); return; } Log.d(TAG, "ARP Reply Sent!"); - this.ztService.setNextBackgroundTaskDeadline(jArr[0]); + this.ztService.setNextBackgroundTaskDeadline(nextDeadline[0]); } } - } else if (j4 == PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH) { - Log.d(TAG, "Got IPv4 packet. Length: " + bArr.length + " Bytes"); + } else if (etherType == IPV4_PACKET) { + // 收到 IPv4 包。根据需要发送至 TUN + Log.d(TAG, "Got IPv4 packet. Length: " + frameData.length + " Bytes"); try { - InetAddress sourceIP = IPPacketUtils.getSourceIP(bArr); + var sourceIP = IPPacketUtils.getSourceIP(frameData); if (sourceIP != null) { if (isIPv4Multicast(sourceIP)) { - this.node.multicastSubscribe(this.networkId, multicastAddressToMAC(sourceIP)); + var result = this.node.multicastSubscribe(this.networkId, multicastAddressToMAC(sourceIP)); + if (result != ResultCode.RESULT_OK) { + Log.e(TAG, "Error when calling multicastSubscribe: " + result); + } } else { - this.arpTable.setAddress(sourceIP, j2); + this.arpTable.setAddress(sourceIP, srcMac); } } - this.out.write(bArr); + this.out.write(frameData); } catch (Exception e) { - Log.e(TAG, "Error writing data to vpn socket", e); + Log.e(TAG, "Error writing data to vpn socket: " + e.getMessage(), e); } - } else if (j4 == 34525) { - Log.d(TAG, "Got IPv6 packet. Length: " + bArr.length + " Bytes"); + } else if (etherType == IPV6_PACKET) { + // 收到 IPv6 包。根据需要发送至 TUN,并更新 NDP 表 + Log.d(TAG, "Got IPv6 packet. Length: " + frameData.length + " Bytes"); try { - InetAddress sourceIP2 = IPPacketUtils.getSourceIP(bArr); - if (sourceIP2 != null) { - if (isIPv6Multicast(sourceIP2)) { - this.node.multicastSubscribe(this.networkId, multicastAddressToMAC(sourceIP2)); + var sourceIP = IPPacketUtils.getSourceIP(frameData); + if (sourceIP != null) { + if (isIPv6Multicast(sourceIP)) { + var result = this.node.multicastSubscribe(this.networkId, multicastAddressToMAC(sourceIP)); + if (result != ResultCode.RESULT_OK) { + Log.e(TAG, "Error when calling multicastSubscribe: " + result); + } } else { - this.ndpTable.setAddress(sourceIP2, j2); + this.ndpTable.setAddress(sourceIP, srcMac); } } - this.out.write(bArr); + this.out.write(frameData); } catch (Exception e) { - Log.e(TAG, "Error writing data to vpn socket", e); + Log.e(TAG, "Error writing data to vpn socket: " + e.getMessage(), e); } - } else if (bArr.length >= 14) { - Log.d(TAG, "Unknown Packet Type Received: 0x" + String.format("%02X%02X", bArr[12], bArr[13])); + } else if (frameData.length >= 14) { + Log.d(TAG, "Unknown Packet Type Received: 0x" + String.format("%02X%02X", frameData[12], frameData[13])); } else { - Log.d(TAG, "Unknown Packet Received. Packet Length: " + bArr.length); + Log.d(TAG, "Unknown Packet Received. Packet Length: " + frameData.length); } } - private Route routeForDestination(InetAddress inetAddress) { + private Route routeForDestination(InetAddress destAddress) { synchronized (this.routeMap) { - for (Route route : this.routeMap.keySet()) { - if (route.belongsToRoute(inetAddress)) { + for (var route : this.routeMap.keySet()) { + if (route.belongsToRoute(destAddress)) { return route; } } @@ -488,10 +491,10 @@ private Route routeForDestination(InetAddress inetAddress) { } } - private long networkIdForDestination(InetAddress inetAddress) { + private long networkIdForDestination(InetAddress destAddress) { synchronized (this.routeMap) { for (Route route : this.routeMap.keySet()) { - if (route.belongsToRoute(inetAddress)) { + if (route.belongsToRoute(destAddress)) { return this.routeMap.get(route); } } diff --git a/app/src/main/java/net/kaaass/zerotierfix/service/UdpCom.java b/app/src/main/java/net/kaaass/zerotierfix/service/UdpCom.java index 2af486c..2919f1c 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/service/UdpCom.java +++ b/app/src/main/java/net/kaaass/zerotierfix/service/UdpCom.java @@ -9,14 +9,15 @@ import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; +import java.net.SocketException; import java.net.SocketTimeoutException; // TODO: clear up public class UdpCom implements PacketSender, Runnable { private static final String TAG = "UdpCom"; - Node node; - DatagramSocket svrSocket; - ZeroTierOneService ztService; + private Node node; + private final DatagramSocket svrSocket; + private final ZeroTierOneService ztService; UdpCom(ZeroTierOneService zeroTierOneService, DatagramSocket datagramSocket) { this.svrSocket = datagramSocket; @@ -59,7 +60,7 @@ public void run() { Log.d(TAG, "Got " + datagramPacket.getLength() + " Bytes From: " + datagramPacket.getAddress().toString() + ":" + datagramPacket.getPort()); ResultCode processWirePacket = this.node.processWirePacket(System.currentTimeMillis(), -1, new InetSocketAddress(datagramPacket.getAddress(), datagramPacket.getPort()), bArr2, jArr); if (processWirePacket != ResultCode.RESULT_OK) { - Log.e(TAG, "procesWirePacket returned: " + processWirePacket.toString()); + Log.e(TAG, "processWirePacket returned: " + processWirePacket.toString()); this.ztService.shutdown(); } this.ztService.setNextBackgroundTaskDeadline(jArr[0]); diff --git a/app/src/main/java/net/kaaass/zerotierfix/service/ZeroTierOneService.java b/app/src/main/java/net/kaaass/zerotierfix/service/ZeroTierOneService.java index 8692251..cf0856b 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/service/ZeroTierOneService.java +++ b/app/src/main/java/net/kaaass/zerotierfix/service/ZeroTierOneService.java @@ -1,14 +1,10 @@ package net.kaaass.zerotierfix.service; -import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; import android.net.VpnService; import android.os.Binder; import android.os.Build; @@ -19,25 +15,26 @@ import android.widget.Toast; import androidx.core.app.NotificationCompat; +import androidx.core.content.ContextCompat; 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 com.zerotier.sdk.VirtualNetworkRoute; +import com.zerotier.sdk.VirtualNetworkStatus; import net.kaaass.zerotierfix.ZerotierFixApplication; import net.kaaass.zerotierfix.R; import net.kaaass.zerotierfix.events.AfterJoinNetworkEvent; -import net.kaaass.zerotierfix.events.DefaultRouteChangedEvent; import net.kaaass.zerotierfix.events.ErrorEvent; -import net.kaaass.zerotierfix.events.IsServiceRunningEvent; +import net.kaaass.zerotierfix.events.IsServiceRunningReplyEvent; +import net.kaaass.zerotierfix.events.IsServiceRunningRequestEvent; import net.kaaass.zerotierfix.events.ManualDisconnectEvent; +import net.kaaass.zerotierfix.events.NetworkConfigChangedByUserEvent; import net.kaaass.zerotierfix.events.NetworkInfoReplyEvent; import net.kaaass.zerotierfix.events.NetworkListReplyEvent; import net.kaaass.zerotierfix.events.NetworkReconfigureEvent; @@ -46,29 +43,28 @@ import net.kaaass.zerotierfix.events.NodeStatusEvent; import net.kaaass.zerotierfix.events.OrbitMoonEvent; import net.kaaass.zerotierfix.events.PeerInfoReplyEvent; -import net.kaaass.zerotierfix.events.RequestNetworkInfoEvent; -import net.kaaass.zerotierfix.events.RequestNetworkListEvent; -import net.kaaass.zerotierfix.events.RequestNodeStatusEvent; -import net.kaaass.zerotierfix.events.RequestPeerInfoEvent; +import net.kaaass.zerotierfix.events.NetworkInfoRequestEvent; +import net.kaaass.zerotierfix.events.NetworkListRequestEvent; +import net.kaaass.zerotierfix.events.NodeStatusRequestEvent; +import net.kaaass.zerotierfix.events.PeerInfoRequestEvent; import net.kaaass.zerotierfix.events.StopEvent; +import net.kaaass.zerotierfix.events.VPNErrorEvent; +import net.kaaass.zerotierfix.events.VirtualNetworkConfigChangedEvent; import net.kaaass.zerotierfix.model.AppNode; -import net.kaaass.zerotierfix.model.AppNodeDao; -import net.kaaass.zerotierfix.model.DaoSession; -import net.kaaass.zerotierfix.model.DnsServer; -import net.kaaass.zerotierfix.model.DnsServerDao; import net.kaaass.zerotierfix.model.MoonOrbit; import net.kaaass.zerotierfix.model.Network; -import net.kaaass.zerotierfix.model.NetworkConfig; -import net.kaaass.zerotierfix.model.NetworkConfigDao; import net.kaaass.zerotierfix.model.NetworkDao; +import net.kaaass.zerotierfix.model.type.DNSMode; import net.kaaass.zerotierfix.ui.NetworkListActivity; import net.kaaass.zerotierfix.util.Constants; +import net.kaaass.zerotierfix.util.DatabaseUtils; import net.kaaass.zerotierfix.util.InetAddressUtils; +import net.kaaass.zerotierfix.util.NetworkInfoUtils; +import net.kaaass.zerotierfix.util.StringUtils; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; -import org.greenrobot.greendao.query.WhereCondition; import java.io.BufferedReader; import java.io.FileInputStream; @@ -77,11 +73,15 @@ import java.io.FileReader; import java.io.IOException; import java.net.DatagramSocket; +import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.ReentrantReadWriteLock; // TODO: clear up public class ZeroTierOneService extends VpnService implements Runnable, EventListener, VirtualNetworkConfigListener { @@ -92,11 +92,10 @@ public class ZeroTierOneService extends VpnService implements Runnable, EventLis private static final String[] DISALLOWED_APPS = {"com.android.vending"}; private static final String TAG = "ZT1_Service"; private static final int ZT_NOTIFICATION_TAG = 5919812; - private final Object configLock = new Object(); private final IBinder mBinder = new ZeroTierBinder(); private final DataStore dataStore = new DataStore(this); private final EventBus eventBus = EventBus.getDefault(); - private final long lastMulticastGroupCheck = 0; + private final Map virtualNetworkConfigMap = new HashMap(); FileInputStream in; FileOutputStream out; DatagramSocket svrSocket; @@ -104,7 +103,6 @@ public class ZeroTierOneService extends VpnService implements Runnable, EventLis private int bindCount = 0; private boolean disableIPv6 = false; private int mStartID = -1; - private VirtualNetworkConfig networkConfigs; private long networkId = 0; private long nextBackgroundTaskDeadline = 0; private Node node; @@ -112,12 +110,13 @@ public class ZeroTierOneService extends VpnService implements Runnable, EventLis private TunTapAdapter tunTapAdapter; private UdpCom udpCom; private Thread udpThread; - private boolean useDefaultRoute = false; - private Thread v4multicastScanner = new Thread() { + private Thread v4MulticastScanner = new Thread() { /* class com.zerotier.one.service.ZeroTierOneService.AnonymousClass1 */ ArrayList subscriptions = new ArrayList<>(); + @Override public void run() { + Log.d(ZeroTierOneService.TAG, "IPv4 Multicast Scanner Thread Started."); while (!isInterrupted()) { try { ArrayList arrayList = new ArrayList<>(); @@ -138,53 +137,64 @@ public void run() { } } } - } catch (FileNotFoundException unused) { - Log.e(ZeroTierOneService.TAG, "File Not Found: /proc/net/igmp"); - } catch (IOException unused2) { - Log.e(ZeroTierOneService.TAG, "Error parsing /proc/net/igmp"); + } catch (FileNotFoundException e) { + Log.e(ZeroTierOneService.TAG, "File Not Found: /proc/net/igmp", e); + } catch (IOException e) { + Log.e(ZeroTierOneService.TAG, "Error parsing /proc/net/igmp", e); } + ArrayList arrayList2 = new ArrayList<>(this.subscriptions); ArrayList arrayList3 = new ArrayList<>(arrayList); arrayList3.removeAll(arrayList2); for (String str : arrayList3) { try { - byte[] hexStringToByteArray = ZeroTierOneService.this.hexStringToByteArray(str); + byte[] hexStringToByteArray = StringUtils.hexStringToBytes(str); for (int i = 0; i < hexStringToByteArray.length / 2; i++) { byte b = hexStringToByteArray[i]; hexStringToByteArray[i] = hexStringToByteArray[(hexStringToByteArray.length - i) - 1]; hexStringToByteArray[(hexStringToByteArray.length - i) - 1] = b; } - ZeroTierOneService.this.node.multicastSubscribe(ZeroTierOneService.this.networkId, TunTapAdapter.multicastAddressToMAC(InetAddress.getByAddress(hexStringToByteArray))); - } catch (Exception ignored) { + ResultCode multicastSubscribe = ZeroTierOneService.this.node.multicastSubscribe(ZeroTierOneService.this.networkId, TunTapAdapter.multicastAddressToMAC(InetAddress.getByAddress(hexStringToByteArray))); + if (multicastSubscribe != ResultCode.RESULT_OK) { + Log.e(ZeroTierOneService.TAG, "Error when calling multicastSubscribe: " + multicastSubscribe); + } + } catch (Exception e) { + Log.e(ZeroTierOneService.TAG, e.toString(), e); } } arrayList2.removeAll(new ArrayList<>(arrayList)); for (String str2 : arrayList2) { try { - byte[] hexStringToByteArray2 = ZeroTierOneService.this.hexStringToByteArray(str2); + byte[] hexStringToByteArray2 = StringUtils.hexStringToBytes(str2); for (int i2 = 0; i2 < hexStringToByteArray2.length / 2; i2++) { byte b2 = hexStringToByteArray2[i2]; hexStringToByteArray2[i2] = hexStringToByteArray2[(hexStringToByteArray2.length - i2) - 1]; hexStringToByteArray2[(hexStringToByteArray2.length - i2) - 1] = b2; } - ZeroTierOneService.this.node.multicastUnsubscribe(ZeroTierOneService.this.networkId, TunTapAdapter.multicastAddressToMAC(InetAddress.getByAddress(hexStringToByteArray2))); - } catch (Exception ignored) { + ResultCode multicastUnsubscribe = ZeroTierOneService.this.node.multicastUnsubscribe(ZeroTierOneService.this.networkId, TunTapAdapter.multicastAddressToMAC(InetAddress.getByAddress(hexStringToByteArray2))); + if (multicastUnsubscribe != ResultCode.RESULT_OK) { + Log.e(ZeroTierOneService.TAG, "Error when calling multicastUnsubscribe: " + multicastUnsubscribe); + } + } catch (Exception e) { + Log.e(ZeroTierOneService.TAG, e.toString(), e); } } this.subscriptions = arrayList; Thread.sleep(1000); - } catch (InterruptedException unused5) { - Log.d(ZeroTierOneService.TAG, "V4 Multicast Scanner Thread Interrupted"); - return; + } catch (InterruptedException e) { + Log.d(ZeroTierOneService.TAG, "V4 Multicast Scanner Thread Interrupted", e); } } + Log.d(ZeroTierOneService.TAG, "IPv4 Multicast Scanner Thread Ended."); } }; private Thread v6MulticastScanner = new Thread() { /* class com.zerotier.one.service.ZeroTierOneService.AnonymousClass2 */ ArrayList subscriptions = new ArrayList<>(); + @Override public void run() { + Log.d(ZeroTierOneService.TAG, "IPv6 Multicast Scanner Thread Started."); while (!isInterrupted()) { try { ArrayList arrayList = new ArrayList<>(); @@ -200,38 +210,70 @@ public void run() { arrayList.add(split[2]); } } - } catch (FileNotFoundException unused) { - Log.e(ZeroTierOneService.TAG, "File not found: /proc/net/igmp6"); - } catch (IOException unused2) { - Log.e(ZeroTierOneService.TAG, "Error parsing /proc/net/igmp6"); + } catch (FileNotFoundException e) { + Log.e(ZeroTierOneService.TAG, "File not found: /proc/net/igmp6", e); + } catch (IOException e) { + Log.e(ZeroTierOneService.TAG, "Error parsing /proc/net/igmp6", e); } - ArrayList arrayList2 = new ArrayList(this.subscriptions); - ArrayList arrayList3 = new ArrayList(arrayList); + ArrayList arrayList2 = new ArrayList<>(this.subscriptions); + ArrayList arrayList3 = new ArrayList<>(arrayList); arrayList3.removeAll(arrayList2); for (String str : arrayList3) { try { - ZeroTierOneService.this.node.multicastSubscribe(ZeroTierOneService.this.networkId, TunTapAdapter.multicastAddressToMAC(InetAddress.getByAddress(ZeroTierOneService.this.hexStringToByteArray(str)))); - } catch (Exception unused3) { + ResultCode multicastSubscribe = ZeroTierOneService.this.node.multicastSubscribe(ZeroTierOneService.this.networkId, TunTapAdapter.multicastAddressToMAC(InetAddress.getByAddress(StringUtils.hexStringToBytes(str)))); + if (multicastSubscribe != ResultCode.RESULT_OK) { + Log.e(ZeroTierOneService.TAG, "Error when calling multicastSubscribe: " + multicastSubscribe); + } + } catch (Exception e) { + Log.e(ZeroTierOneService.TAG, e.toString(), e); } } - arrayList2.removeAll(new ArrayList(arrayList)); + arrayList2.removeAll(new ArrayList<>(arrayList)); for (String str2 : arrayList2) { try { - ZeroTierOneService.this.node.multicastUnsubscribe(ZeroTierOneService.this.networkId, TunTapAdapter.multicastAddressToMAC(InetAddress.getByAddress(ZeroTierOneService.this.hexStringToByteArray(str2)))); - } catch (Exception unused4) { + ResultCode multicastUnsubscribe = ZeroTierOneService.this.node.multicastUnsubscribe(ZeroTierOneService.this.networkId, TunTapAdapter.multicastAddressToMAC(InetAddress.getByAddress(StringUtils.hexStringToBytes(str2)))); + if (multicastUnsubscribe != ResultCode.RESULT_OK) { + Log.e(ZeroTierOneService.TAG, "Error when calling multicastUnsubscribe: " + multicastUnsubscribe); + } + } catch (Exception e) { + Log.e(ZeroTierOneService.TAG, e.toString(), e); } } this.subscriptions = arrayList; Thread.sleep(1000); - } catch (InterruptedException unused5) { - Log.d(ZeroTierOneService.TAG, "V6 Multicast Scanner Thread Interrupted"); - return; + } catch (InterruptedException e) { + Log.d(ZeroTierOneService.TAG, "V6 Multicast Scanner Thread Interrupted", e); } } + Log.d(ZeroTierOneService.TAG, "IPv6 Multicast Scanner Thread Ended."); } }; private Thread vpnThread; + public VirtualNetworkConfig getVirtualNetworkConfig(long j) { + VirtualNetworkConfig virtualNetworkConfig; + synchronized (this.virtualNetworkConfigMap) { + virtualNetworkConfig = this.virtualNetworkConfigMap.get(Long.valueOf(j)); + } + return virtualNetworkConfig; + } + + public VirtualNetworkConfig setVirtualNetworkConfig(long j, VirtualNetworkConfig virtualNetworkConfig) { + VirtualNetworkConfig put; + synchronized (this.virtualNetworkConfigMap) { + put = this.virtualNetworkConfigMap.put(Long.valueOf(j), virtualNetworkConfig); + } + return put; + } + + public VirtualNetworkConfig clearVirtualNetworkConfig(long j) { + VirtualNetworkConfig remove; + synchronized (this.virtualNetworkConfigMap) { + remove = this.virtualNetworkConfigMap.remove(Long.valueOf(j)); + } + return remove; + } + private void logBindCount() { Log.i(TAG, "Bind Count: " + this.bindCount); } @@ -257,14 +299,12 @@ public void setNextBackgroundTaskDeadline(long j) { } } - public void onCreate() { - super.onCreate(); - } - + /** + * 启动 ZT 服务,连接至给定网络或最近连接的网络 + */ @Override public int onStartCommand(Intent intent, int flags, int startId) { long networkId; - long j; Log.d(TAG, "onStartCommand"); if (startId == 3) { Log.i(TAG, "Authorizing VPN"); @@ -272,170 +312,203 @@ public int onStartCommand(Intent intent, int flags, int startId) { } else if (intent == null) { Log.e(TAG, "NULL intent. Cannot start"); return START_NOT_STICKY; + } + this.mStartID = startId; + + // 注册事件总线监听器 + if (!this.eventBus.isRegistered(this)) { + this.eventBus.register(this); + } + + // 确定待启动的网络 ID + if (intent.hasExtra(ZT1_NETWORK_ID)) { + // Intent 中指定了目标网络,直接使用此 ID + networkId = intent.getLongExtra(ZT1_NETWORK_ID, 0); } else { - this.mStartID = startId; - if (!this.eventBus.isRegistered(this)) { - this.eventBus.register(this); - } - if (intent.hasExtra(ZT1_NETWORK_ID)) { - networkId = intent.getLongExtra(ZT1_NETWORK_ID, 0); - this.useDefaultRoute = intent.getBooleanExtra(ZT1_USE_DEFAULT_ROUTE, false); - } else { - DaoSession daoSession = ((ZerotierFixApplication) getApplication()).getDaoSession(); + // 默认启用最近一次启动的网络 + DatabaseUtils.readLock.lock(); + try { + var daoSession = ((ZerotierFixApplication) getApplication()).getDaoSession(); daoSession.clear(); - List list = daoSession.getNetworkDao().queryBuilder().where(NetworkDao.Properties.LastActivated.eq(true), new WhereCondition[0]).list(); - if (list == null || list.isEmpty()) { + var lastActivatedNetworks = daoSession.getNetworkDao().queryBuilder() + .where(NetworkDao.Properties.LastActivated.eq(true)) + .list(); + if (lastActivatedNetworks == null || lastActivatedNetworks.isEmpty()) { Log.e(TAG, "Couldn't find last activated connection"); return START_NOT_STICKY; - } else if (list.size() > 1) { - Log.e(TAG, "Multiple networks marked as last connected: " + list.size()); - for (Network network : list) { + } else if (lastActivatedNetworks.size() > 1) { + Log.e(TAG, "Multiple networks marked as last connected: " + lastActivatedNetworks.size()); + for (Network network : lastActivatedNetworks) { Log.e(TAG, "ID: " + Long.toHexString(network.getNetworkId())); } - return START_NOT_STICKY; + throw new IllegalStateException("Database is inconsistent"); } else { - networkId = list.get(0).getNetworkId(); - this.useDefaultRoute = list.get(0).getUseDefaultRoute(); + networkId = lastActivatedNetworks.get(0).getNetworkId(); Log.i(TAG, "Got Always On request for ZeroTier"); } + } finally { + DatabaseUtils.readLock.unlock(); } - if (networkId == 0) { - Log.e(TAG, "Network ID not provided to service"); - stopSelf(startId); - return START_NOT_STICKY; - } - this.networkId = networkId; - SharedPreferences defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - boolean useCellularData = defaultSharedPreferences.getBoolean(Constants.PREF_NETWORK_USE_CELLULAR_DATA, false); - this.disableIPv6 = defaultSharedPreferences.getBoolean("network_disable_ipv6", false); - ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); - int i3 = 0; - while (activeNetworkInfo == null && i3 < 30) { - try { - Log.i(TAG, "Waiting for network connectivity"); - Thread.sleep(1000); - } catch (InterruptedException ignored) { + } + if (networkId == 0) { + Log.e(TAG, "Network ID not provided to service"); + stopSelf(startId); + return START_NOT_STICKY; + } + this.networkId = networkId; + + // 检查当前的网络环境 + var preferences = PreferenceManager.getDefaultSharedPreferences(this); + boolean useCellularData = preferences.getBoolean(Constants.PREF_NETWORK_USE_CELLULAR_DATA, false); + this.disableIPv6 = preferences.getBoolean(Constants.PREF_NETWORK_DISABLE_IPV6, false); + var currentNetworkInfo = NetworkInfoUtils.getNetworkInfoCurrentConnection(this); + + if (currentNetworkInfo == NetworkInfoUtils.CurrentConnection.CONNECTION_NONE) { + // 未连接网络 + Toast.makeText(this, R.string.toast_no_network, Toast.LENGTH_SHORT).show(); + stopSelf(this.mStartID); + return START_NOT_STICKY; + } else if (currentNetworkInfo == NetworkInfoUtils.CurrentConnection.CONNECTION_MOBILE && + !useCellularData) { + // 使用移动网络,但未在设置中允许移动网络访问 + Toast.makeText(this, R.string.toast_mobile_data, Toast.LENGTH_LONG).show(); + stopSelf(this.mStartID); + return START_NOT_STICKY; + } + + // 启动 ZT 服务 + synchronized (this) { + try { + // 创建本地 ZT 服务 Socket,监听本地端口 + if (this.svrSocket == null) { + this.svrSocket = new DatagramSocket(null); + this.svrSocket.setReuseAddress(true); + this.svrSocket.setSoTimeout(1000); + this.svrSocket.bind(new InetSocketAddress(9994)); } - activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); - i3++; - } - if (activeNetworkInfo == null || !activeNetworkInfo.isConnectedOrConnecting()) { - Toast.makeText(this, R.string.toast_no_network, Toast.LENGTH_SHORT).show(); - return START_NOT_STICKY; - } else if (useCellularData || !(activeNetworkInfo == null || activeNetworkInfo.getType() == 0)) { - synchronized (this) { + if (!protect(this.svrSocket)) { + Log.e(TAG, "Error protecting UDP socket from feedback loop."); + } + + // 创建本地节点 + if (this.node == null) { try { - if (this.svrSocket == null) { - DatagramSocket datagramSocket = new DatagramSocket(null); - this.svrSocket = datagramSocket; - datagramSocket.setReuseAddress(true); - this.svrSocket.setSoTimeout(1000); - this.svrSocket.bind(new InetSocketAddress(9994)); - } - if (!protect(this.svrSocket)) { - Log.e(TAG, "Error protecting UDP socket from feedback loop."); - } - if (this.node == null) { - try { - this.udpCom = new UdpCom(this, this.svrSocket); - this.tunTapAdapter = new TunTapAdapter(this, networkId); - long currentTimeMillis = System.currentTimeMillis(); - DataStore dataStore2 = this.dataStore; - // 创建 ZT 节点 - this.node = new Node(currentTimeMillis, dataStore2, dataStore2, this.udpCom, this, this.tunTapAdapter, this, null); - this.onNodeStatusRequest(null); - // 持久化节点信息 - NodeStatus status = this.node.status(); - long address = status.getAddres(); - AppNodeDao appNodeDao = ((ZerotierFixApplication) getApplication()).getDaoSession().getAppNodeDao(); - List list2 = appNodeDao.queryBuilder().build().forCurrentThread().list(); - if (list2.isEmpty()) { - AppNode appNode = new AppNode(); - appNode.setNodeId(address); - appNode.setNodeIdStr(String.format("%10x", address)); - appNodeDao.insert(appNode); - } else { - AppNode appNode2 = list2.get(0); - appNode2.setNodeId(address); - appNode2.setNodeIdStr(String.format("%10x", address)); - appNodeDao.save(appNode2); - } - this.eventBus.post(new NodeIDEvent(status.getAddres())); - this.udpCom.setNode(this.node); - this.tunTapAdapter.setNode(this.node); - Thread thread = new Thread(this.udpCom, "UDP Communication Thread"); - this.udpThread = thread; - thread.start(); - } catch (NodeException e) { - Log.e(TAG, "Error starting ZT1 Node", e); - return START_NOT_STICKY; + this.udpCom = new UdpCom(this, this.svrSocket); + this.tunTapAdapter = new TunTapAdapter(this, networkId); + + // 创建节点对象并初始化 + var dataStore = this.dataStore; + this.node = new Node(System.currentTimeMillis()); + this.node.init(dataStore, dataStore, this.udpCom, this, this.tunTapAdapter, this, null); + this.onNodeStatusRequest(null); + + // 持久化当前节点信息 + long address = this.node.address(); + DatabaseUtils.writeLock.lock(); + try { + var appNodeDao = ((ZerotierFixApplication) getApplication()) + .getDaoSession().getAppNodeDao(); + var nodesList = appNodeDao.queryBuilder().build() + .forCurrentThread().list(); + if (nodesList.isEmpty()) { + var appNode = new AppNode(); + appNode.setNodeId(address); + appNode.setNodeIdStr(String.format("%10x", address)); + appNodeDao.insert(appNode); + } else { + var appNode = nodesList.get(0); + appNode.setNodeId(address); + appNode.setNodeIdStr(String.format("%10x", address)); + appNodeDao.save(appNode); } + } finally { + DatabaseUtils.writeLock.unlock(); } - if (this.vpnThread == null) { - Thread thread2 = new Thread(this, "ZeroTier Service Thread"); - this.vpnThread = thread2; - thread2.start(); - } - if (!this.udpThread.isAlive()) { - this.udpThread.start(); - } - } catch (Exception e2) { - Log.e(TAG, e2.toString()); + + this.eventBus.post(new NodeIDEvent(address)); + this.udpCom.setNode(this.node); + this.tunTapAdapter.setNode(this.node); + + // 启动 UDP 消息处理线程 + var thread = new Thread(this.udpCom, "UDP Communication Thread"); + this.udpThread = thread; + thread.start(); + } catch (NodeException e) { + Log.e(TAG, "Error starting ZT1 Node: " + e.getMessage(), e); return START_NOT_STICKY; } } - joinNetwork(networkId, this.useDefaultRoute); - return START_STICKY; - } else { - Toast.makeText(this, R.string.toast_mobile_data, Toast.LENGTH_SHORT).show(); - stopSelf(this.mStartID); - Node node3 = this.node; - if (node3 != null) { - node3.close(); + + // 创建并启动 VPN 服务线程 + if (this.vpnThread == null) { + var thread = new Thread(this, "ZeroTier Service Thread"); + this.vpnThread = thread; + thread.start(); + } + + // 启动 UDP 消息处理线程 + if (!this.udpThread.isAlive()) { + this.udpThread.start(); } + } catch (Exception e) { + Log.e(TAG, e.toString(), e); return START_NOT_STICKY; } } + joinNetwork(networkId); + return START_STICKY; } public void stopZeroTier() { - Thread udpThread = this.udpThread; - if (udpThread != null && udpThread.isAlive()) { + if (this.svrSocket != null) { + this.svrSocket.close(); + this.svrSocket = null; + } + if (this.udpThread != null && this.udpThread.isAlive()) { this.udpThread.interrupt(); + try { + this.udpThread.join(); + } catch (InterruptedException ignored) { + } this.udpThread = null; } - TunTapAdapter tunTapAdapter = this.tunTapAdapter; - if (tunTapAdapter != null && tunTapAdapter.isRunning()) { + if (this.tunTapAdapter != null && this.tunTapAdapter.isRunning()) { this.tunTapAdapter.interrupt(); + try { + this.tunTapAdapter.join(); + } catch (InterruptedException ignored) { + } this.tunTapAdapter = null; } - Thread vpnThread = this.vpnThread; - if (vpnThread != null && vpnThread.isAlive()) { + if (this.vpnThread != null && this.vpnThread.isAlive()) { this.vpnThread.interrupt(); + try { + this.vpnThread.join(); + } catch (InterruptedException ignored) { + } this.vpnThread = null; } - DatagramSocket svrSocket = this.svrSocket; - if (svrSocket != null) { - svrSocket.close(); - this.svrSocket = null; - } - Thread v4multicastScanner = this.v4multicastScanner; - if (v4multicastScanner != null) { - v4multicastScanner.interrupt(); - this.v4multicastScanner = null; + if (this.v4MulticastScanner != null) { + this.v4MulticastScanner.interrupt(); + try { + this.v4MulticastScanner.join(); + } catch (InterruptedException ignored) { + } + this.v4MulticastScanner = null; } - Thread v6MulticastScanner = this.v6MulticastScanner; - if (v6MulticastScanner != null) { - v6MulticastScanner.interrupt(); + if (this.v6MulticastScanner != null) { + this.v6MulticastScanner.interrupt(); + try { + this.v6MulticastScanner.join(); + } catch (InterruptedException ignored) { + } this.v6MulticastScanner = null; } - ParcelFileDescriptor vpnSocket = this.vpnSocket; - if (vpnSocket != null) { + if (this.vpnSocket != null) { try { - vpnSocket.close(); + this.vpnSocket.close(); } catch (Exception e) { - Log.e(TAG, "Error closing VPN socket", e); + Log.e(TAG, "Error closing VPN socket: " + e, e); } this.vpnSocket = null; } @@ -447,9 +520,8 @@ public void stopZeroTier() { if (this.eventBus.isRegistered(this)) { this.eventBus.unregister(this); } - NotificationManager notificationManager = this.notificationManager; - if (notificationManager != null) { - notificationManager.cancel(ZT_NOTIFICATION_TAG); + if (this.notificationManager != null) { + this.notificationManager.cancel(ZT_NOTIFICATION_TAG); } if (!stopSelfResult(this.mStartID)) { Log.e(TAG, "stopSelfResult() failed!"); @@ -459,12 +531,11 @@ public void stopZeroTier() { public void onDestroy() { try { stopZeroTier(); - ParcelFileDescriptor parcelFileDescriptor = this.vpnSocket; - if (parcelFileDescriptor != null) { + if (this.vpnSocket != null) { try { - parcelFileDescriptor.close(); + this.vpnSocket.close(); } catch (Exception e) { - Log.e(TAG, "Error closing VPN socket", e); + Log.e(TAG, "Error closing VPN socket: " + e, e); } this.vpnSocket = null; } @@ -473,22 +544,19 @@ public void onDestroy() { this.eventBus.unregister(this); } } catch (Exception e) { - Log.e(TAG, "", e); - } catch (Throwable th) { + Log.e(TAG, e.toString(), e); + } finally { super.onDestroy(); - throw th; } - super.onDestroy(); } public void onRevoke() { stopZeroTier(); - ParcelFileDescriptor parcelFileDescriptor = this.vpnSocket; - if (parcelFileDescriptor != null) { + if (this.vpnSocket != null) { try { - parcelFileDescriptor.close(); + this.vpnSocket.close(); } catch (Exception e) { - Log.e(TAG, "Error closing VPN socket", e); + Log.e(TAG, "Error closing VPN socket: " + e, e); } this.vpnSocket = null; } @@ -501,29 +569,29 @@ public void onRevoke() { public void run() { Log.d(TAG, "ZeroTierOne Service Started"); - Log.d(TAG, "This Node Address: " + Long.toHexString(this.node.address())); + Log.d(TAG, "This Node Address: " + com.zerotier.sdk.util.StringUtils.addressToString(this.node.address())); while (!Thread.interrupted()) { try { - long j = this.nextBackgroundTaskDeadline; - long currentTimeMillis = System.currentTimeMillis(); - int i = (Long.compare(j, currentTimeMillis)); - if (i <= 0) { - long[] jArr = {0}; - ResultCode processBackgroundTasks = this.node.processBackgroundTasks(currentTimeMillis, jArr); + // 在后台任务截止期前循环进行后台任务 + var taskDeadline = this.nextBackgroundTaskDeadline; + long currentTime = System.currentTimeMillis(); + int cmp = Long.compare(taskDeadline, currentTime); + if (cmp <= 0) { + long[] newDeadline = {0}; + var taskResult = this.node.processBackgroundTasks(currentTime, newDeadline); synchronized (this) { - this.nextBackgroundTaskDeadline = jArr[0]; + this.nextBackgroundTaskDeadline = newDeadline[0]; } - if (processBackgroundTasks != ResultCode.RESULT_OK) { - Log.e(TAG, "Error on processBackgroundTasks: " + processBackgroundTasks.toString()); + if (taskResult != ResultCode.RESULT_OK) { + Log.e(TAG, "Error on processBackgroundTasks: " + taskResult.toString()); shutdown(); } } - Thread.sleep(i > 0 ? j - currentTimeMillis : 100); - } catch (InterruptedException e) { - Log.e(TAG, "ZeroTierOne Thread Interrupted", e); + Thread.sleep(cmp > 0 ? taskDeadline - currentTime : 100); + } catch (InterruptedException ignored) { break; } catch (Exception e) { - Log.e(TAG, "", e); + Log.e(TAG, e.toString(), e); } } Log.d(TAG, "ZeroTierOne Service Ended"); @@ -540,31 +608,20 @@ public void onManualDisconnect(ManualDisconnectEvent manualDisconnectEvent) { } @Subscribe(threadMode = ThreadMode.POSTING) - public void onIsServiceRunning(IsServiceRunningEvent isServiceRunningEvent) { - if (isServiceRunningEvent.type == IsServiceRunningEvent.Type.REQUEST) { - this.eventBus.post(IsServiceRunningEvent.NewReply(true)); - } + public void onIsServiceRunningRequest(IsServiceRunningRequestEvent event) { + this.eventBus.post(new IsServiceRunningReplyEvent(true)); } /** - * 加入网络 - * @param networkId - * @param useDefaultRoute + * 加入 ZT 网络 */ - public void joinNetwork(long networkId, boolean useDefaultRoute) { + public void joinNetwork(long networkId) { if (this.node == null) { Log.e(TAG, "Can't join network if ZeroTier isn't running"); return; } - // 如果已经加入网络,则退出 - VirtualNetworkConfig virtualNetworkConfig = this.networkConfigs; - if (virtualNetworkConfig != null) { - leaveNetwork(virtualNetworkConfig.networkId()); - } // 连接到新网络 - this.networkConfigs = null; - this.useDefaultRoute = useDefaultRoute; - ResultCode result = this.node.join(networkId); + var result = this.node.join(networkId); if (result != ResultCode.RESULT_OK) { this.eventBus.post(new ErrorEvent(result)); return; @@ -573,57 +630,60 @@ public void joinNetwork(long networkId, boolean useDefaultRoute) { this.eventBus.post(new AfterJoinNetworkEvent()); } - public void leaveNetwork(long j) { - Node node2 = this.node; - if (node2 == null) { + /** + * 离开 ZT 网络 + */ + public void leaveNetwork(long networkId) { + if (this.node == null) { Log.e(TAG, "Can't leave network if ZeroTier isn't running"); return; } - ResultCode leave = node2.leave(j); - if (leave != ResultCode.RESULT_OK) { - this.eventBus.post(new ErrorEvent(leave)); + var result = this.node.leave(networkId); + if (result != ResultCode.RESULT_OK) { + this.eventBus.post(new ErrorEvent(result)); return; } - VirtualNetworkConfig[] networks = this.node.networks(); - if (networks == null || (networks != null && networks.length == 0)) { - stopZeroTier(); - ParcelFileDescriptor parcelFileDescriptor = this.vpnSocket; - if (parcelFileDescriptor != null) { - try { - parcelFileDescriptor.close(); - } catch (Exception e) { - Log.e(TAG, "Error closing VPN socket", e); - } - this.vpnSocket = null; + var networkConfigs = this.node.networkConfigs(); + if (networkConfigs != null && networkConfigs.length != 0) { + return; + } + stopZeroTier(); + if (this.vpnSocket != null) { + try { + this.vpnSocket.close(); + } catch (Exception e) { + Log.e(TAG, "Error closing VPN socket", e); } - stopSelf(this.mStartID); + this.vpnSocket = null; } + stopSelf(this.mStartID); } @Subscribe(threadMode = ThreadMode.BACKGROUND) - public void onNetworkInfoRequest(RequestNetworkInfoEvent requestNetworkInfoEvent) { + public void onNetworkInfoRequest(NetworkInfoRequestEvent networkInfoRequestEvent) { VirtualNetworkConfig networkConfig; Node node2 = this.node; - if (node2 != null && (networkConfig = node2.networkConfig(requestNetworkInfoEvent.getNetworkId())) != null) { + if (node2 != null && (networkConfig = node2.networkConfig(networkInfoRequestEvent.getNetworkId())) != null) { this.eventBus.post(new NetworkInfoReplyEvent(networkConfig)); } } @Subscribe(threadMode = ThreadMode.BACKGROUND) - public void onNetworkListRequest(RequestNetworkListEvent requestNetworkListEvent) { + public void onNetworkListRequest(NetworkListRequestEvent requestNetworkListEvent) { VirtualNetworkConfig[] networks; Node node2 = this.node; - if (node2 != null && (networks = node2.networks()) != null && networks.length > 0) { + if (node2 != null && (networks = node2.networkConfigs()) != null && networks.length > 0) { this.eventBus.post(new NetworkListReplyEvent(networks)); } } /** * 请求节点状态事件回调 + * * @param event 事件 */ @Subscribe(threadMode = ThreadMode.BACKGROUND) - public void onNodeStatusRequest(RequestNodeStatusEvent event) { + public void onNodeStatusRequest(NodeStatusRequestEvent event) { // 返回节点状态 if (this.node != null) { this.eventBus.post(new NodeStatusEvent(this.node.status(), this.node.getVersion())); @@ -631,7 +691,7 @@ public void onNodeStatusRequest(RequestNodeStatusEvent event) { } @Subscribe(threadMode = ThreadMode.BACKGROUND) - public void onRequestPeerInfo(RequestPeerInfoEvent event) { + public void onRequestPeerInfo(PeerInfoRequestEvent event) { if (this.node == null) { this.eventBus.post(new PeerInfoReplyEvent(null)); return; @@ -640,25 +700,37 @@ public void onRequestPeerInfo(RequestPeerInfoEvent event) { } @Subscribe(threadMode = ThreadMode.ASYNC) - public void onNetworkReconfigure(NetworkReconfigureEvent networkReconfigureEvent) { - updateTunnelConfig(); + public void onNetworkReconfigure(NetworkReconfigureEvent event) { + boolean isChanged = event.isChanged(); + var network = event.getNetwork(); + var networkConfig = event.getVirtualNetworkConfig(); + boolean configUpdated = isChanged && updateTunnelConfig(network); + boolean networkIsOk = networkConfig.getStatus() == VirtualNetworkStatus.NETWORK_STATUS_OK; + + if (configUpdated || !networkIsOk) { + this.eventBus.post(new VirtualNetworkConfigChangedEvent(networkConfig)); + } } @Subscribe(threadMode = ThreadMode.ASYNC) - public void onDefaultrouteChanged(DefaultRouteChangedEvent defaultRouteChangedEvent) { - this.useDefaultRoute = defaultRouteChangedEvent.isDefaultRoute(); - updateTunnelConfig(); + public void onNetworkConfigChangedByUser(NetworkConfigChangedByUserEvent event) { + Network network = event.getNetwork(); + if (network.getNetworkId() != this.networkId) { + return; + } + updateTunnelConfig(network); } /** * Zerotier 事件回调 + * * @param event {@link Event} enum */ @Override public void onEvent(Event event) { Log.d(TAG, "Event: " + event.toString()); // 更新节点状态 - if (this.node != null) { + if (this.node.isInited()) { this.eventBus.post(new NodeStatusEvent(this.node.status(), this.node.getVersion())); } } @@ -668,75 +740,68 @@ public void onTrace(String str) { Log.d(TAG, "Trace: " + str); } - @Override // com.zerotier.sdk.VirtualNetworkConfigListener - public int onNetworkConfigurationUpdated(long j, VirtualNetworkConfigOperation virtualNetworkConfigOperation, VirtualNetworkConfig virtualNetworkConfig) { - Log.i(TAG, "Virtual Network Config Operation: " + virtualNetworkConfigOperation.toString()); - int i = AnonymousClass3.$SwitchMap$com$zerotier$sdk$VirtualNetworkConfigOperation[virtualNetworkConfigOperation.ordinal()]; - if (i == 1) { - Log.d(TAG, "Network Type:" + virtualNetworkConfig.networkType().toString() + " Network Status: " + virtualNetworkConfig.networkStatus().toString() + " Network Name: " + virtualNetworkConfig.name() + " "); - this.eventBus.post(new NetworkInfoReplyEvent(virtualNetworkConfig)); - TunTapAdapter tunTapAdapter2 = this.tunTapAdapter; - if (tunTapAdapter2 == null) { - return 0; - } - tunTapAdapter2.setNetworkConfig(virtualNetworkConfig); - return 0; - } else if (i == 2) { - Log.i(TAG, "Network Config Update!"); - VirtualNetworkConfig virtualNetworkConfig2 = this.networkConfigs; - if (virtualNetworkConfig2 == null) { - Log.d(TAG, "Adding new network."); - synchronized (this.configLock) { - this.networkConfigs = virtualNetworkConfig; - } - this.eventBus.post(new NetworkReconfigureEvent()); - this.eventBus.post(new NetworkInfoReplyEvent(virtualNetworkConfig)); - TunTapAdapter tunTapAdapter3 = this.tunTapAdapter; - if (tunTapAdapter3 == null) { - return 0; - } - tunTapAdapter3.setNetworkConfig(virtualNetworkConfig); - return 0; - } - if (!virtualNetworkConfig2.equals(virtualNetworkConfig)) { - Log.i(TAG, "Network Config Changed. Reconfiguring."); - synchronized (this.configLock) { - this.networkConfigs = virtualNetworkConfig; - } - this.eventBus.post(new NetworkReconfigureEvent()); - } - this.eventBus.post(new NetworkInfoReplyEvent(virtualNetworkConfig)); - TunTapAdapter tunTapAdapter4 = this.tunTapAdapter; - if (tunTapAdapter4 == null) { - return 0; - } - tunTapAdapter4.setNetworkConfig(virtualNetworkConfig); - return 0; - } else if (i == 3 || i == 4) { - Log.d(TAG, "Network Down!"); - synchronized (this.configLock) { - this.networkConfigs = null; + /** + * 当 ZT 网络配置发生更新 + */ + @Override + public int onNetworkConfigurationUpdated(long networkId, VirtualNetworkConfigOperation op, VirtualNetworkConfig config) { + Log.i(TAG, "Virtual Network Config Operation: " + op); + DatabaseUtils.writeLock.lock(); + try { + // 查找网络 ID 对应的配置 + var networkDao = ((ZerotierFixApplication) getApplication()) + .getDaoSession() + .getNetworkDao(); + var matchedNetwork = networkDao.queryBuilder() + .where(NetworkDao.Properties.NetworkId.eq(networkId)) + .list(); + if (matchedNetwork.size() != 1) { + throw new IllegalStateException("Database is inconsistent"); } - this.eventBus.post(new NetworkReconfigureEvent()); - TunTapAdapter tunTapAdapter5 = this.tunTapAdapter; - if (tunTapAdapter5 == null) { - return 0; + var network = matchedNetwork.get(0); + // 根据当前网络状态确定更改配置的行为 + switch (op) { + case VIRTUAL_NETWORK_CONFIG_OPERATION_UP: + Log.d(TAG, "Network Type: " + config.getType() + " Network Status: " + config.getStatus() + " Network Name: " + config.getName() + " "); + setVirtualNetworkConfigAndUpdateDatabase(network, config); + this.eventBus.post(new VirtualNetworkConfigChangedEvent(config)); + break; + case VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE: + Log.i(TAG, "Network Config Update!"); + boolean isChanged = setVirtualNetworkConfigAndUpdateDatabase(network, config); + this.eventBus.post(new NetworkReconfigureEvent(isChanged, network, config)); + break; + case VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN: + case VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY: + Log.d(TAG, "Network Down!"); + clearVirtualNetworkConfig(networkId); + break; } - tunTapAdapter5.setNetworkConfig(null); - return 0; - } else { - Log.e(TAG, "Unknown Network Config Operation!"); return 0; + } finally { + DatabaseUtils.writeLock.unlock(); } } - /* access modifiers changed from: protected */ - public void shutdown() { + private boolean setVirtualNetworkConfigAndUpdateDatabase(Network network, VirtualNetworkConfig virtualNetworkConfig) { + if ((DatabaseUtils.writeLock instanceof ReentrantReadWriteLock.WriteLock) && !((ReentrantReadWriteLock.WriteLock) DatabaseUtils.writeLock).isHeldByCurrentThread()) { + throw new IllegalStateException("DatabaseUtils.writeLock not held"); + } + VirtualNetworkConfig virtualNetworkConfig2 = getVirtualNetworkConfig(network.getNetworkId()); + setVirtualNetworkConfig(network.getNetworkId(), virtualNetworkConfig); + var networkName = virtualNetworkConfig.getName(); + if (networkName != null && !networkName.isEmpty()) { + network.setNetworkName(networkName); + } + network.update(); + return !virtualNetworkConfig.equals(virtualNetworkConfig2); + } + + protected void shutdown() { stopZeroTier(); - ParcelFileDescriptor parcelFileDescriptor = this.vpnSocket; - if (parcelFileDescriptor != null) { + if (this.vpnSocket != null) { try { - parcelFileDescriptor.close(); + this.vpnSocket.close(); } catch (Exception e) { Log.e(TAG, "Error closing VPN socket", e); } @@ -745,207 +810,239 @@ public void shutdown() { stopSelf(this.mStartID); } - /* JADX WARNING: Code restructure failed: missing block: B:45:0x0175, code lost: - if (r9 > r6) goto L_0x0179; - */ - /* Code decompiled incorrectly, please refer to instructions dump. */ - // Decomp by jd-gui - private void updateTunnelConfig() { - try { - synchronized (this.configLock) { - if (this.networkConfigs == null) - return; - if (this.tunTapAdapter.isRunning()) - this.tunTapAdapter.interrupt(); - this.tunTapAdapter.clearRouteMap(); - ParcelFileDescriptor parcelFileDescriptor2 = this.vpnSocket; - if (parcelFileDescriptor2 != null) { - try { - parcelFileDescriptor2.close(); - this.in.close(); - this.out.close(); - } catch (Exception e) { - Log.e("ZT1_Service", "Error closing VPN socket", e); - } - this.vpnSocket = null; - this.in = null; - this.out = null; - } - Log.i("ZT1_Service", "Configuring VpnService.Builder"); - VpnService.Builder builder1 = new VpnService.Builder(); - long networkId = this.networkConfigs.networkId(); - InetSocketAddress[] assignedAddresses = this.networkConfigs.assignedAddresses(); - long l2 = 0L; - int i = 0; - int j; - for (j = 0; j < assignedAddresses.length; j++) { - InetSocketAddress curAddress = assignedAddresses[j]; - boolean bool = curAddress.getAddress() instanceof java.net.Inet6Address; - Log.d("ZT1_Service", "Adding VPN Address: " + curAddress.getAddress() + " Mac: " + Long.toHexString(this.networkConfigs.macAddress())); - byte[] rawAddress = curAddress.getAddress().getAddress(); - long numAddress = l2; - try { - if (rawAddress.length == 4) { - numAddress = ByteBuffer.wrap(rawAddress).getInt(); - } - int port = curAddress.getPort(); - InetAddress curInetAddress = curAddress.getAddress(); - if (!this.disableIPv6 || !(curInetAddress instanceof java.net.Inet6Address)) { - InetAddress curRouteAddress = InetAddressUtils.addressToRoute(curInetAddress, port); - if (curRouteAddress == null) { - Log.e("ZT1_Service", "NULL route calculated!"); - } else { - ResultCode resultCode = null; - if (rawAddress.length == 4) { - resultCode = this.node.multicastSubscribe(networkId, 0xffffffffffffL, numAddress); - } else { - l2 = ByteBuffer.wrap(new byte[]{0, 0, 51, 51, -1, rawAddress[13], rawAddress[14], rawAddress[15]}).getLong(); - resultCode = this.node.multicastSubscribe(networkId, l2, numAddress); - } - if (resultCode != ResultCode.RESULT_OK) { - Log.e("ZT1_Service", "Error joining multicast group"); - } else { - Log.d("ZT1_Service", "Joined multicast group"); - } - builder1.addAddress(curInetAddress, port); - builder1.addRoute(curRouteAddress, port); - Route route = new Route(curRouteAddress, port); - this.tunTapAdapter.addRouteAndNetwork(route, networkId); - int m = this.networkConfigs.mtu(); - port = i; - i = Math.max(m, port); - } - } - l2 = numAddress; - } catch (Exception e) { - Log.e("ZT1_Service", "Exception calculating multicast ADI", e); - } + private boolean updateTunnelConfig(Network network) { + long networkId = network.getNetworkId(); + var networkConfig = network.getNetworkConfig(); + var virtualNetworkConfig = getVirtualNetworkConfig(networkId); + if (virtualNetworkConfig == null) { + return false; + } + + // 重启 TUN TAP + if (this.tunTapAdapter.isRunning()) { + this.tunTapAdapter.interrupt(); + try { + this.tunTapAdapter.join(); + } catch (InterruptedException ignored) { + } + } + this.tunTapAdapter.clearRouteMap(); + + // 重启 VPN Socket + if (this.vpnSocket != null) { + try { + this.vpnSocket.close(); + this.in.close(); + this.out.close(); + } catch (Exception e) { + Log.e(TAG, "Error closing VPN socket: " + e, e); + } + this.vpnSocket = null; + this.in = null; + this.out = null; + } + + // 配置 VPN + Log.i(TAG, "Configuring VpnService.Builder"); + var builder = new VpnService.Builder(); + var assignedAddresses = virtualNetworkConfig.getAssignedAddresses(); + Log.i(TAG, "address length: " + assignedAddresses.length); + boolean isRouteViaZeroTier = networkConfig.getRouteViaZeroTier(); + + // 遍历 ZT 网络中当前设备的 IP 地址,组播配置 + for (var vpnAddress : assignedAddresses) { + Log.d(TAG, "Adding VPN Address: " + vpnAddress.getAddress() + + " Mac: " + com.zerotier.sdk.util.StringUtils.macAddressToString(virtualNetworkConfig.getMac())); + byte[] rawAddress = vpnAddress.getAddress().getAddress(); + + if (!this.disableIPv6 || !(vpnAddress.getAddress() instanceof Inet6Address)) { + var address = vpnAddress.getAddress(); + var port = vpnAddress.getPort(); + var route = InetAddressUtils.addressToRoute(address, port); + if (route == null) { + Log.e(TAG, "NULL route calculated!"); + continue; } - InetAddress inetAddress1 = InetAddress.getByName("0.0.0.0"); - InetAddress inetAddress2 = InetAddress.getByName("::"); - if ((this.networkConfigs.routes()).length > 0) { - VirtualNetworkRoute[] arrayOfVirtualNetworkRoute = this.networkConfigs.routes(); - for (j = 0; j < arrayOfVirtualNetworkRoute.length; j++) { - InetSocketAddress inetSocketAddress2 = (arrayOfVirtualNetworkRoute[j]).target; - InetSocketAddress inetSocketAddress1 = (arrayOfVirtualNetworkRoute[j]).via; - int k = inetSocketAddress2.getPort(); - InetAddress inetAddress4 = inetSocketAddress2.getAddress(); - InetAddress inetAddress3 = InetAddressUtils.addressToRoute(inetAddress4, k); - if ((!this.disableIPv6 || (!(inetAddress4 instanceof java.net.Inet6Address) && !(inetAddress3 instanceof java.net.Inet6Address))) && inetAddress3 != null && (this.useDefaultRoute || (!inetAddress3.equals(inetAddress1) && !inetAddress3.equals(inetAddress2)))) { - builder1.addRoute(inetAddress3, k); - Route route = new Route(inetAddress3, k); - if (inetSocketAddress1 != null) - route.setGateway(inetSocketAddress1.getAddress()); - this.tunTapAdapter.addRouteAndNetwork(route, networkId); - } - } + + // 计算 VPN 地址相关的组播 MAC 与 ADI + long multicastGroup; + long multicastAdi; + if (rawAddress.length == 4) { + // IPv4 + multicastGroup = InetAddressUtils.BROADCAST_MAC_ADDRESS; + multicastAdi = ByteBuffer.wrap(rawAddress).getInt(); + } else { + // IPv6 + multicastGroup = ByteBuffer.wrap(new byte[]{ + 0, 0, 0x33, 0x33, (byte) 0xFF, rawAddress[13], rawAddress[14], rawAddress[15]}) + .getLong(); + multicastAdi = 0; } - builder1.addRoute(InetAddress.getByName("224.0.0.0"), 4); - List list = ((ZerotierFixApplication) getApplication()).getDaoSession() - .getNetworkConfigDao().queryBuilder() - .where(NetworkConfigDao.Properties.Id.eq(networkId), new WhereCondition[0]) - .build().forCurrentThread().list(); - if (list.isEmpty()) { - Log.e("ZT1_Service", "network config list empty?!?"); - } else if (list.size() > 1) { - Log.e("ZT1_Service", "network config list has more than 1?!?"); + + // 订阅组播并添加至 TUN TAP 路由 + var result = this.node.multicastSubscribe(networkId, multicastGroup, multicastAdi); + if (result != ResultCode.RESULT_OK) { + Log.e(TAG, "Error joining multicast group"); } else { - j = list.get(0).getDnsMode(); - if (j != 1) { - if (j == 2) { - for (DnsServer dnsServer : ((ZerotierFixApplication) getApplication()).getDaoSession().getDnsServerDao().queryBuilder().where(DnsServerDao.Properties.NetworkId.eq(Long.valueOf(networkId)), new WhereCondition[0]).build().forCurrentThread().list()) { - String str = dnsServer.getNameserver(); - try { - InetAddress inetAddress = InetAddress.getByName(str); - if (inetAddress instanceof java.net.Inet4Address) { - builder1.addDnsServer(inetAddress); - continue; - } - if (inetAddress instanceof java.net.Inet6Address && !this.disableIPv6) - builder1.addDnsServer(inetAddress); - } catch (Exception e) { - Log.e("ZT1_Service", "Exception parsing DNS server", e); - } - } - } - } else if (this.networkConfigs.dns() != null) { - builder1.addSearchDomain(this.networkConfigs.dns().getSearchDomain()); - for (InetSocketAddress inetSocketAddress : this.networkConfigs.dns().getServers()) { - inetAddress1 = inetSocketAddress.getAddress(); - if (inetAddress1 instanceof java.net.Inet4Address) { - builder1.addDnsServer(inetAddress1); - continue; - } - if (inetAddress1 instanceof java.net.Inet6Address && !this.disableIPv6) - builder1.addDnsServer(inetAddress1); - } - } + Log.d(TAG, "Joined multicast group"); } - builder1.setMtu(i); - builder1.setSession("ZeroTier One"); - if (Build.VERSION.SDK_INT >= 21 && !this.useDefaultRoute) - for (String str : DISALLOWED_APPS) { - try { - builder1.addDisallowedApplication(str); - } catch (Exception exception) { - Log.e("ZT1_Service", "Cannot disallow app", exception); + builder.addAddress(address, port); + builder.addRoute(route, port); + this.tunTapAdapter.addRouteAndNetwork(new Route(route, port), networkId); + } + } + + // 遍历网络的路由规则,将网络负责路由的地址路由至 VPN + try { + var v4Loopback = InetAddress.getByName("0.0.0.0"); + var v6Loopback = InetAddress.getByName("::"); + if (virtualNetworkConfig.getRoutes().length > 0) { + for (var routeConfig : virtualNetworkConfig.getRoutes()) { + var target = routeConfig.getTarget(); + var via = routeConfig.getVia(); + var targetAddress = target.getAddress(); + var targetPort = target.getPort(); + var viaAddress = InetAddressUtils.addressToRoute(targetAddress, targetPort); + + boolean isIPv6Route = (targetAddress instanceof Inet6Address) || (viaAddress instanceof Inet6Address); + boolean isDisabledV6Route = this.disableIPv6 && isIPv6Route; + boolean shouldRouteToZerotier = viaAddress != null && ( + isRouteViaZeroTier + || (!viaAddress.equals(v4Loopback) && !viaAddress.equals(v6Loopback)) + ); + if (!isDisabledV6Route && shouldRouteToZerotier) { + builder.addRoute(viaAddress, targetPort); + Route route = new Route(viaAddress, targetPort); + if (via != null) { + route.setGateway(via.getAddress()); } + this.tunTapAdapter.addRouteAndNetwork(route, networkId); } - ParcelFileDescriptor parcelFileDescriptor1 = builder1.establish(); - this.vpnSocket = parcelFileDescriptor1; - if (parcelFileDescriptor1 == null) { - Log.e("ZT1_Service", "vpnSocket is NULL after builder.establish()!!!!"); - stopZeroTier(); - return; } - this.in = new FileInputStream(this.vpnSocket.getFileDescriptor()); - this.out = new FileOutputStream(this.vpnSocket.getFileDescriptor()); - this.tunTapAdapter.setVpnSocket(this.vpnSocket); - this.tunTapAdapter.setFileStreams(this.in, this.out); - this.tunTapAdapter.startThreads(); - // 状态栏提示 - if (this.notificationManager == null) - this.notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - if (Build.VERSION.SDK_INT >= 26) { - NotificationChannel notificationChannel = new NotificationChannel("ZeroTierOne", "ZeroTier One", NotificationManager.IMPORTANCE_MIN); - notificationChannel.setDescription(getString(R.string.network_connected)); - this.notificationManager.createNotificationChannel(notificationChannel); - } - Intent intent = new Intent(this, NetworkListActivity.class); - PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "ZeroTierOne"); - String contentText = String.format(getString(R.string.connected_to_network), Long.toHexString(this.networkConfigs.networkId())); - Notification notification = builder.setOngoing(true) - .setSmallIcon(R.mipmap.ic_launcher) - .setContentTitle(getString(R.string.network_connected)) - .setContentText(contentText) - .setSubText("") - .setContentIntent(pendingIntent).build(); - this.notificationManager.notify(ZT_NOTIFICATION_TAG, notification); - Log.i("ZT1_Service", "ZeroTier One Connected"); - // 多播处理进程 - Thread thread = this.v4multicastScanner; - if (!((thread != null) && !thread.isAlive()) && Build.VERSION.SDK_INT < 29) - this.v4multicastScanner.start(); - if (!this.disableIPv6) { - thread = this.v6MulticastScanner; - if (thread != null && !thread.isAlive() && Build.VERSION.SDK_INT < 29) - this.v6MulticastScanner.start(); + } + builder.addRoute(InetAddress.getByName("224.0.0.0"), 4); + } catch (Exception e) { + this.eventBus.post(new VPNErrorEvent(e.getLocalizedMessage())); + return false; + } + + if (Build.VERSION.SDK_INT >= 29) { + builder.setMetered(false); + } + addDNSServers(builder, network); + + // 配置 MTU + int mtu = virtualNetworkConfig.getMtu(); + Log.i(TAG, "MTU from Network Config: " + mtu); + if (mtu == 0) { + mtu = 2800; + } + Log.i(TAG, "MTU Set: " + mtu); + builder.setMtu(mtu); + + builder.setSession(Constants.VPN_SESSION_NAME); + + // 设置部分 APP 不经过 VPN + if (!isRouteViaZeroTier && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + for (var app : DISALLOWED_APPS) { + try { + builder.addDisallowedApplication(app); + } catch (Exception e3) { + Log.e(TAG, "Cannot disallow app", e3); } } - } catch (Exception exception) { - Log.e("ZT1_Service", "Exception setting up VPN port", exception); } + + // 建立 VPN 连接 + this.vpnSocket = builder.establish(); + if (this.vpnSocket == null) { + this.eventBus.post(new VPNErrorEvent(getString(R.string.toast_vpn_application_not_prepared))); + return false; + } + this.in = new FileInputStream(this.vpnSocket.getFileDescriptor()); + this.out = new FileOutputStream(this.vpnSocket.getFileDescriptor()); + this.tunTapAdapter.setVpnSocket(this.vpnSocket); + this.tunTapAdapter.setFileStreams(this.in, this.out); + this.tunTapAdapter.startThreads(); + + // 状态栏提示 + if (this.notificationManager == null) { + this.notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + } + if (Build.VERSION.SDK_INT >= 26) { + String channelName = getString(R.string.channel_name); + String description = getString(R.string.channel_description); + var channel = new NotificationChannel( + Constants.CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_HIGH); + channel.setDescription(description); + this.notificationManager.createNotificationChannel(channel); + } + var pendingIntent = + PendingIntent.getActivity(this, 0, + new Intent(this, NetworkListActivity.class), + Build.VERSION.SDK_INT >= 23 + ? PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT + : PendingIntent.FLAG_UPDATE_CURRENT); + var notification = new NotificationCompat.Builder(this, Constants.CHANNEL_ID) + .setPriority(1) + .setOngoing(true) + .setSmallIcon(R.mipmap.ic_launcher) + .setContentTitle(getString(R.string.notification_title_connected)) + .setContentText(getString(R.string.notification_text_connected, network.getNetworkIdStr())) + .setColor(ContextCompat.getColor(getApplicationContext(), R.color.zerotier_orange)) + .setContentIntent(pendingIntent).build(); + this.notificationManager.notify(ZT_NOTIFICATION_TAG, notification); + Log.i(TAG, "ZeroTier One Connected"); + + // 旧版本 Android 多播处理 + if (Build.VERSION.SDK_INT < 29) { + if (this.v4MulticastScanner != null && !this.v4MulticastScanner.isAlive()) { + this.v4MulticastScanner.start(); + } + if (!this.disableIPv6 && this.v6MulticastScanner != null && !this.v6MulticastScanner.isAlive()) { + this.v6MulticastScanner.start(); + } + } + return true; } - /* access modifiers changed from: package-private */ - public byte[] hexStringToByteArray(String str) { - int length = str.length(); - byte[] bArr = new byte[(length / 2)]; - for (int i = 0; i < length; i += 2) { - bArr[i / 2] = (byte) ((Character.digit(str.charAt(i), 16) << 4) + Character.digit(str.charAt(i + 1), 16)); + private void addDNSServers(VpnService.Builder builder, Network network) { + var networkConfig = network.getNetworkConfig(); + var virtualNetworkConfig = getVirtualNetworkConfig(network.getNetworkId()); + var dnsMode = DNSMode.fromInt(networkConfig.getDnsMode()); + + switch (dnsMode) { + case NETWORK_DNS: + if (virtualNetworkConfig.getDns() == null) { + return; + } + builder.addSearchDomain(virtualNetworkConfig.getDns().getDomain()); + for (var inetSocketAddress : virtualNetworkConfig.getDns().getServers()) { + InetAddress address = inetSocketAddress.getAddress(); + if (address instanceof Inet4Address) { + builder.addDnsServer(address); + } else if ((address instanceof Inet6Address) && !this.disableIPv6) { + builder.addDnsServer(address); + } + } + break; + case CUSTOM_DNS: + for (var dnsServer : networkConfig.getDnsServers()) { + try { + InetAddress byName = InetAddress.getByName(dnsServer.getNameserver()); + if (byName instanceof Inet4Address) { + builder.addDnsServer(byName); + } else if ((byName instanceof Inet6Address) && !this.disableIPv6) { + builder.addDnsServer(byName); + } + } catch (Exception e) { + Log.e(TAG, "Exception parsing DNS server: " + e, e); + } + } + break; + default: + break; } - return bArr; } /** @@ -966,8 +1063,9 @@ public void onOrbitMoonEvent(OrbitMoonEvent event) { /** * 当前网络入轨 Moon - * @param moonWorldId - * @param moonSeed + * + * @param moonWorldId Moon 节点地址 + * @param moonSeed Moon 种子节点地址 */ public void orbitNetwork(Long moonWorldId, Long moonSeed) { if (this.node == null) { @@ -982,19 +1080,6 @@ public void orbitNetwork(Long moonWorldId, Long moonSeed) { } } - /* renamed from: com.zerotier.one.service.ZeroTierOneService$3 reason: invalid class name */ - static /* synthetic */ class AnonymousClass3 { - static final /* synthetic */ int[] $SwitchMap$com$zerotier$sdk$VirtualNetworkConfigOperation; - - static { - $SwitchMap$com$zerotier$sdk$VirtualNetworkConfigOperation = new int[VirtualNetworkConfigOperation.values().length]; - $SwitchMap$com$zerotier$sdk$VirtualNetworkConfigOperation[VirtualNetworkConfigOperation.VIRTUAL_NETWORK_CONFIG_OPERATION_UP.ordinal()] = 1; - $SwitchMap$com$zerotier$sdk$VirtualNetworkConfigOperation[VirtualNetworkConfigOperation.VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE.ordinal()] = 2; - $SwitchMap$com$zerotier$sdk$VirtualNetworkConfigOperation[VirtualNetworkConfigOperation.VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN.ordinal()] = 3; - $SwitchMap$com$zerotier$sdk$VirtualNetworkConfigOperation[VirtualNetworkConfigOperation.VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY.ordinal()] = 4; - } - } - public class ZeroTierBinder extends Binder { public ZeroTierBinder() { } diff --git a/app/src/main/java/net/kaaass/zerotierfix/ui/NetworkDetailFragment.java b/app/src/main/java/net/kaaass/zerotierfix/ui/NetworkDetailFragment.java index f6ebc99..34e3445 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/ui/NetworkDetailFragment.java +++ b/app/src/main/java/net/kaaass/zerotierfix/ui/NetworkDetailFragment.java @@ -66,10 +66,10 @@ public View onCreateView(LayoutInflater layoutInflater, ViewGroup viewGroup, Bun if (network != null) { ((TextView) inflate.findViewById(R.id.network_detail_network_id)).setText(network.getNetworkIdStr()); TextView textView = inflate.findViewById(R.id.network_detail_network_name); - if (network.getNetworkName() != null) { + if (network.getNetworkName() != null && !network.getNetworkName().isEmpty()) { textView.setText(network.getNetworkName()); } else { - textView.setText(EnvironmentCompat.MEDIA_UNKNOWN); + textView.setText(getString(R.string.empty_network_name)); } NetworkConfig networkConfig = network.getNetworkConfig(); CheckBox checkBox = inflate.findViewById(R.id.network_default_route); diff --git a/app/src/main/java/net/kaaass/zerotierfix/ui/NetworkListFragment.java b/app/src/main/java/net/kaaass/zerotierfix/ui/NetworkListFragment.java index 05fd8c5..cefc1fa 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/ui/NetworkListFragment.java +++ b/app/src/main/java/net/kaaass/zerotierfix/ui/NetworkListFragment.java @@ -24,6 +24,7 @@ import android.widget.TextView; import android.widget.Toast; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.appcompat.widget.SwitchCompat; import androidx.fragment.app.Fragment; @@ -33,22 +34,25 @@ import com.zerotier.sdk.NodeStatus; import com.zerotier.sdk.Version; import com.zerotier.sdk.VirtualNetworkConfig; -import com.zerotier.sdk.VirtualNetworkStatus; import com.zerotier.sdk.VirtualNetworkType; -import net.kaaass.zerotierfix.ZerotierFixApplication; import net.kaaass.zerotierfix.R; +import net.kaaass.zerotierfix.ZerotierFixApplication; import net.kaaass.zerotierfix.events.AfterJoinNetworkEvent; -import net.kaaass.zerotierfix.events.IsServiceRunningEvent; +import net.kaaass.zerotierfix.events.IsServiceRunningReplyEvent; +import net.kaaass.zerotierfix.events.IsServiceRunningRequestEvent; import net.kaaass.zerotierfix.events.NetworkInfoReplyEvent; +import net.kaaass.zerotierfix.events.NetworkListCheckedChangeEvent; import net.kaaass.zerotierfix.events.NetworkListReplyEvent; import net.kaaass.zerotierfix.events.NodeDestroyedEvent; import net.kaaass.zerotierfix.events.NodeIDEvent; import net.kaaass.zerotierfix.events.NodeStatusEvent; +import net.kaaass.zerotierfix.events.NodeStatusRequestEvent; import net.kaaass.zerotierfix.events.OrbitMoonEvent; -import net.kaaass.zerotierfix.events.RequestNetworkListEvent; -import net.kaaass.zerotierfix.events.RequestNodeStatusEvent; +import net.kaaass.zerotierfix.events.NetworkListRequestEvent; import net.kaaass.zerotierfix.events.StopEvent; +import net.kaaass.zerotierfix.events.VPNErrorEvent; +import net.kaaass.zerotierfix.events.VirtualNetworkConfigChangedEvent; import net.kaaass.zerotierfix.model.AppNode; import net.kaaass.zerotierfix.model.AssignedAddress; import net.kaaass.zerotierfix.model.AssignedAddressDao; @@ -58,6 +62,8 @@ import net.kaaass.zerotierfix.model.NetworkConfig; import net.kaaass.zerotierfix.model.NetworkConfigDao; import net.kaaass.zerotierfix.model.NetworkDao; +import net.kaaass.zerotierfix.model.type.NetworkStatus; +import net.kaaass.zerotierfix.model.type.NetworkType; import net.kaaass.zerotierfix.service.ZeroTierOneService; import net.kaaass.zerotierfix.util.Constants; import net.kaaass.zerotierfix.util.StringUtils; @@ -67,6 +73,7 @@ import org.greenrobot.eventbus.ThreadMode; import org.greenrobot.greendao.query.WhereCondition; +import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -77,14 +84,11 @@ // TODO: clear up public class NetworkListFragment extends Fragment { - public static final int AUTH_VPN = 3; public static final String NETWORK_ID_MESSAGE = "com.zerotier.one.network-id"; - public static final int START_VPN = 2; public static final String TAG = "NetworkListFragment"; private final EventBus eventBus; private final List mNetworks = new ArrayList<>(); boolean mIsBound = false; - private JoinAfterAuth joinAfterAuth; private RecyclerViewAdapter recyclerViewAdapter; private RecyclerView recyclerView; private ZeroTierOneService mBoundService; @@ -106,7 +110,6 @@ public void onServiceDisconnected(ComponentName componentName) { private TextView nodeClientVersionView; private View emptyView = null; - final private RecyclerView.AdapterDataObserver checkIfEmptyObserver = new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { @@ -153,7 +156,7 @@ public synchronized boolean isBound() { /* access modifiers changed from: package-private */ public void doBindService() { if (!isBound()) { - if (getActivity().bindService(new Intent(getActivity(), ZeroTierOneService.class), this.mConnection, Context.BIND_NOT_FOREGROUND | Context.BIND_DEBUG_UNBIND)) { + if (requireActivity().bindService(new Intent(getActivity(), ZeroTierOneService.class), this.mConnection, Context.BIND_NOT_FOREGROUND | Context.BIND_DEBUG_UNBIND)) { setIsBound(true); } } @@ -163,7 +166,7 @@ public void doBindService() { public void doUnbindService() { if (isBound()) { try { - getActivity().unbindService(this.mConnection); + requireActivity().unbindService(this.mConnection); } catch (Exception e) { Log.e(TAG, "", e); } catch (Throwable th) { @@ -174,33 +177,27 @@ public void doUnbindService() { } } - @Override // androidx.fragment.app.Fragment + @Override public void onStart() { super.onStart(); - this.eventBus.register(this); - this.eventBus.post(new RequestNetworkListEvent()); - this.eventBus.post(new RequestNodeStatusEvent()); - this.eventBus.post(IsServiceRunningEvent.NewRequest()); + this.eventBus.post(new NetworkListRequestEvent()); + // 初始化节点及服务状态 + this.eventBus.post(new NodeStatusRequestEvent()); + this.eventBus.post(new IsServiceRunningRequestEvent()); } - @Override // androidx.fragment.app.Fragment + @Override public void onStop() { super.onStop(); doUnbindService(); - this.eventBus.unregister(this); } - @Override // androidx.fragment.app.Fragment + @Override public void onPause() { super.onPause(); this.eventBus.unregister(this); } - @Override // androidx.fragment.app.Fragment - public void onActivityCreated(Bundle bundle) { - super.onActivityCreated(bundle); - } - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_network_list, container, false); @@ -235,20 +232,33 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa return view; } - /* access modifiers changed from: private */ - /* access modifiers changed from: public */ - private void sendStartServiceIntent(long networkId, boolean useDefaultRoute) { - Intent prepare = VpnService.prepare(getActivity()); + /** + * 发送连接至指定网络的 Intent。将请求 VPN 权限后启动 ZT 服务 + * + * @param networkId 网络号 + */ + private void sendStartServiceIntent(long networkId) { + var prepare = VpnService.prepare(getActivity()); if (prepare != null) { - this.joinAfterAuth = new JoinAfterAuth(networkId, useDefaultRoute); - startActivityForResult(prepare, 3); + // 等待 VPN 授权后连接网络 + registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), (activityResult) -> { + var result = activityResult.getResultCode(); + Log.d(TAG, "Returned from AUTH_VPN"); + if (result == -1) { + // 授权 + startService(networkId); + } else if (result == 0) { + // 未授权 + updateNetworkListAndNotify(); + } + }).launch(prepare); return; } Log.d(TAG, "Intent is NULL. Already approved."); - startService(networkId, useDefaultRoute); + startService(networkId); } - @Override // androidx.fragment.app.Fragment + @Override public void onCreate(Bundle bundle) { Log.d(TAG, "NetworkListFragment.onCreate"); super.onCreate(bundle); @@ -256,25 +266,26 @@ public void onCreate(Bundle bundle) { setHasOptionsMenu(true); } - @Override // androidx.fragment.app.Fragment + @Override public void onResume() { super.onResume(); this.nodeStatusView.setText(R.string.status_offline); - this.eventBus.post(IsServiceRunningEvent.NewRequest()); + this.eventBus.register(this); + this.eventBus.post(new IsServiceRunningRequestEvent()); updateNetworkListAndNotify(); - this.eventBus.post(new RequestNetworkListEvent()); - this.eventBus.post(new RequestNodeStatusEvent()); + this.eventBus.post(new NetworkListRequestEvent()); + this.eventBus.post(new NodeStatusRequestEvent()); } - @Override // androidx.fragment.app.Fragment - public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { + @Override + public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater menuInflater) { Log.d(TAG, "NetworkListFragment.onCreateOptionsMenu"); menuInflater.inflate(R.menu.menu_network_list, menu); super.onCreateOptionsMenu(menu, menuInflater); - this.eventBus.post(new RequestNodeStatusEvent()); + this.eventBus.post(new NodeStatusRequestEvent()); } - @Override // androidx.fragment.app.Fragment + @Override public boolean onOptionsItemSelected(MenuItem menuItem) { int menuId = menuItem.getItemId(); if (menuId == R.id.menu_item_settings) { @@ -293,48 +304,28 @@ public boolean onOptionsItemSelected(MenuItem menuItem) { return super.onOptionsItemSelected(menuItem); } - @Override // androidx.fragment.app.Fragment - public void onActivityResult(int i, int i2, Intent intent) { - JoinAfterAuth joinAfterAuth2; - if (i == 2) { - long longExtra = intent.getLongExtra(ZeroTierOneService.ZT1_NETWORK_ID, 0); - boolean booleanExtra = intent.getBooleanExtra(ZeroTierOneService.ZT1_USE_DEFAULT_ROUTE, false); - if (longExtra != 0) { - startService(longExtra, booleanExtra); - } else { - Log.e(TAG, "Network ID not provided. Cannot start network without an ID"); - } - } else if (i == 3) { - Log.d(TAG, "Returned from AUTH_VPN"); - if (i2 == -1 && (joinAfterAuth2 = this.joinAfterAuth) != null) { - startService(joinAfterAuth2.networkId, this.joinAfterAuth.useDefaultRoute); - } - this.joinAfterAuth = null; - } - } - - @Override // androidx.fragment.app.Fragment + @Override public void onDestroy() { super.onDestroy(); doUnbindService(); } private List getNetworkList() { - DaoSession daoSession = ((ZerotierFixApplication) getActivity().getApplication()).getDaoSession(); + DaoSession daoSession = ((ZerotierFixApplication) requireActivity().getApplication()).getDaoSession(); daoSession.clear(); return daoSession.getNetworkDao().queryBuilder().orderAsc(NetworkDao.Properties.NetworkId).build().forCurrentThread().list(); } private void setNodeIdText() { - List list = ((ZerotierFixApplication) getActivity().getApplication()).getDaoSession().getAppNodeDao().queryBuilder().build().forCurrentThread().list(); + List list = ((ZerotierFixApplication) requireActivity().getApplication()).getDaoSession().getAppNodeDao().queryBuilder().build().forCurrentThread().list(); if (!list.isEmpty()) { this.nodeIdView.setText(list.get(0).getNodeIdStr()); } } @Subscribe(threadMode = ThreadMode.MAIN) - public void onIsServicerunning(IsServiceRunningEvent isServiceRunningEvent) { - if (isServiceRunningEvent.type == IsServiceRunningEvent.Type.REPLY && isServiceRunningEvent.isRunning) { + public void onIsServiceRunningReply(IsServiceRunningReplyEvent event) { + if (event.isRunning()) { doBindService(); } } @@ -348,17 +339,17 @@ public void onNetworkListReply(NetworkListReplyEvent networkListReplyEvent) { @Subscribe(threadMode = ThreadMode.MAIN) public void onNetworkInfoReply(NetworkInfoReplyEvent networkInfoReplyEvent) { - NetworkConfig.NetworkStatus networkStatus; + NetworkStatus networkStatus; Log.d(TAG, "Got Network Info"); VirtualNetworkConfig networkInfo = networkInfoReplyEvent.getNetworkInfo(); for (Network network : getNetworkList()) { - if (network.getNetworkId() == networkInfo.networkId()) { + if (network.getNetworkId() == networkInfo.getNwid()) { network.setConnected(true); - if (!networkInfo.name().isEmpty()) { - network.setNetworkName(networkInfo.name()); + if (!networkInfo.getName().isEmpty()) { + network.setNetworkName(networkInfo.getName()); } - NetworkDao networkDao = ((ZerotierFixApplication) getActivity().getApplication()).getDaoSession().getNetworkDao(); - NetworkConfigDao networkConfigDao = ((ZerotierFixApplication) getActivity().getApplication()).getDaoSession().getNetworkConfigDao(); + NetworkDao networkDao = ((ZerotierFixApplication) requireActivity().getApplication()).getDaoSession().getNetworkDao(); + NetworkConfigDao networkConfigDao = ((ZerotierFixApplication) requireActivity().getApplication()).getDaoSession().getNetworkConfigDao(); NetworkConfig networkConfig = network.getNetworkConfig(); if (networkConfig == null) { networkConfig = new NetworkConfig(); @@ -368,40 +359,40 @@ public void onNetworkInfoReply(NetworkInfoReplyEvent networkInfoReplyEvent) { network.setNetworkConfig(networkConfig); network.setNetworkConfigId(network.getNetworkId()); networkDao.save(network); - networkConfig.setBridging(networkInfo.isBridgeEnabled()); - if (networkInfo.networkType() == VirtualNetworkType.NETWORK_TYPE_PRIVATE) { - networkConfig.setType(NetworkConfig.NetworkType.PRIVATE); - } else if (networkInfo.networkType() == VirtualNetworkType.NETWORK_TYPE_PUBLIC) { - networkConfig.setType(NetworkConfig.NetworkType.PUBLIC); + networkConfig.setBridging(networkInfo.isBridge()); + if (networkInfo.getType() == VirtualNetworkType.NETWORK_TYPE_PRIVATE) { + networkConfig.setType(NetworkType.PRIVATE); + } else if (networkInfo.getType() == VirtualNetworkType.NETWORK_TYPE_PUBLIC) { + networkConfig.setType(NetworkType.PUBLIC); } - switch (AnonymousClass2.$SwitchMap$com$zerotier$sdk$VirtualNetworkStatus[networkInfo.networkStatus().ordinal()]) { - case 1: - networkStatus = NetworkConfig.NetworkStatus.OK; + switch (networkInfo.getStatus()) { + case NETWORK_STATUS_OK: + networkStatus = NetworkStatus.OK; break; - case 2: - networkStatus = NetworkConfig.NetworkStatus.ACCESS_DENIED; - Toast.makeText(getActivity(), R.string.toast_not_authorized, Toast.LENGTH_SHORT).show(); + case NETWORK_STATUS_ACCESS_DENIED: + networkStatus = NetworkStatus.ACCESS_DENIED; break; - case 3: - networkStatus = NetworkConfig.NetworkStatus.CLIENT_TOO_OLD; + case NETWORK_STATUS_NOT_FOUND: + networkStatus = NetworkStatus.NOT_FOUND; break; - case 4: - networkStatus = NetworkConfig.NetworkStatus.NOT_FOUND; + case NETWORK_STATUS_PORT_ERROR: + networkStatus = NetworkStatus.PORT_ERROR; break; - case 5: - networkStatus = NetworkConfig.NetworkStatus.PORT_ERROR; + case NETWORK_STATUS_CLIENT_TOO_OLD: + networkStatus = NetworkStatus.CLIENT_TOO_OLD; break; - case 6: - networkStatus = NetworkConfig.NetworkStatus.REQUESTING_CONFIGURATION; + case NETWORK_STATUS_AUTHENTICATION_REQUIRED: + networkStatus = NetworkStatus.AUTHENTICATION_REQUIRED; break; default: - networkStatus = NetworkConfig.NetworkStatus.UNKNOWN; + case NETWORK_STATUS_REQUESTING_CONFIGURATION: + networkStatus = NetworkStatus.REQUESTING_CONFIGURATION; break; } networkConfig.setStatus(networkStatus); - String macAddress = Long.toHexString(networkInfo.macAddress()); + StringBuilder macAddress = new StringBuilder(Long.toHexString(networkInfo.getMac())); while (macAddress.length() < 12) { - macAddress = "0" + macAddress; + macAddress.insert(0, "0"); } String sb = String.valueOf(macAddress.charAt(0)) + macAddress.charAt(1) + @@ -421,16 +412,16 @@ public void onNetworkInfoReply(NetworkInfoReplyEvent networkInfoReplyEvent) { macAddress.charAt(10) + macAddress.charAt(11); networkConfig.setMac(sb); - networkConfig.setMtu(Integer.toString(networkInfo.mtu())); - networkConfig.setBroadcast(networkInfo.broadcastEnabled()); - network.setNetworkConfigId(networkInfo.networkId()); + networkConfig.setMtu(Integer.toString(networkInfo.getMtu())); + networkConfig.setBroadcast(networkInfo.isBroadcastEnabled()); + network.setNetworkConfigId(networkInfo.getNwid()); network.setNetworkConfig(networkConfig); networkDao.save(network); networkConfigDao.save(networkConfig); - ((ZerotierFixApplication) getActivity().getApplication()).getDaoSession().getAssignedAddressDao().queryBuilder().where(AssignedAddressDao.Properties.NetworkId.eq(networkInfo.networkId()), new WhereCondition[0]).buildDelete().forCurrentThread().forCurrentThread().executeDeleteWithoutDetachingEntities(); - ((ZerotierFixApplication) getActivity().getApplication()).getDaoSession().clear(); - AssignedAddressDao assignedAddressDao = ((ZerotierFixApplication) getActivity().getApplication()).getDaoSession().getAssignedAddressDao(); - InetSocketAddress[] assignedAddresses = networkInfo.assignedAddresses(); + ((ZerotierFixApplication) requireActivity().getApplication()).getDaoSession().getAssignedAddressDao().queryBuilder().where(AssignedAddressDao.Properties.NetworkId.eq(networkInfo.getNwid()), new WhereCondition[0]).buildDelete().forCurrentThread().forCurrentThread().executeDeleteWithoutDetachingEntities(); + ((ZerotierFixApplication) requireActivity().getApplication()).getDaoSession().clear(); + AssignedAddressDao assignedAddressDao = ((ZerotierFixApplication) requireActivity().getApplication()).getDaoSession().getAssignedAddressDao(); + InetSocketAddress[] assignedAddresses = networkInfo.getAssignedAddresses(); for (InetSocketAddress inetSocketAddress : assignedAddresses) { InetAddress address = inetSocketAddress.getAddress(); short port = (short) inetSocketAddress.getPort(); @@ -441,8 +432,8 @@ public void onNetworkInfoReply(NetworkInfoReplyEvent networkInfoReplyEvent) { } if (address instanceof Inet6Address) { assignedAddress.setType(AssignedAddress.AddressType.IPV6); - } else if (address instanceof InetAddress) { - assignedAddress.setType(AssignedAddress.AddressType.IPV6); + } else if (address instanceof Inet4Address) { + assignedAddress.setType(AssignedAddress.AddressType.IPV4); } assignedAddress.setAddressBytes(address.getAddress()); assignedAddress.setAddressString(inetAddress); @@ -455,10 +446,50 @@ public void onNetworkInfoReply(NetworkInfoReplyEvent networkInfoReplyEvent) { network.update(); } } - ((ZerotierFixApplication) getActivity().getApplication()).getDaoSession().clear(); + ((ZerotierFixApplication) requireActivity().getApplication()).getDaoSession().clear(); updateNetworkListAndNotify(); } + @Subscribe(threadMode = ThreadMode.MAIN) + public void onVirtualNetworkConfigChanged(VirtualNetworkConfigChangedEvent event) { + Log.d(TAG, "Got Network Info"); + var config = event.getVirtualNetworkConfig(); + + // Toast 提示网络状态 + var status = NetworkStatus.fromVirtualNetworkStatus(config.getStatus()); + var networkId = com.zerotier.sdk.util.StringUtils.networkIdToString(config.getNwid()); + String message = null; + switch (status) { + case OK: + message = getString(R.string.toast_network_status_ok, networkId); + break; + case ACCESS_DENIED: + message = getString(R.string.toast_network_status_access_denied, networkId); + break; + case NOT_FOUND: + message = getString(R.string.toast_network_status_not_found, networkId); + break; + case PORT_ERROR: + message = getString(R.string.toast_network_status_port_error, networkId); + break; + case CLIENT_TOO_OLD: + message = getString(R.string.toast_network_status_client_too_old, networkId); + break; + case AUTHENTICATION_REQUIRED: + message = getString(R.string.toast_network_status_authentication_required, networkId); + break; + case REQUESTING_CONFIGURATION: + default: + break; + } + if (message != null) { + Toast.makeText(requireActivity(), message, Toast.LENGTH_SHORT).show(); + } + + // 触发网络信息更新 + this.onNetworkInfoReply(new NetworkInfoReplyEvent(config)); + } + @Subscribe(threadMode = ThreadMode.MAIN) public void onNodeID(NodeIDEvent nodeIDEvent) { setNodeIdText(); @@ -466,6 +497,7 @@ public void onNodeID(NodeIDEvent nodeIDEvent) { /** * 节点状态事件回调 + * * @param event 事件 */ @Subscribe(threadMode = ThreadMode.MAIN) @@ -476,7 +508,7 @@ public void onNodeStatus(NodeStatusEvent event) { if (status.isOnline()) { this.nodeStatusView.setText(R.string.status_online); if (this.nodeIdView != null) { - this.nodeIdView.setText(Long.toHexString(status.getAddres())); + this.nodeIdView.setText(Long.toHexString(status.getAddress())); } } else { setOfflineState(); @@ -488,10 +520,18 @@ public void onNodeStatus(NodeStatusEvent event) { } @Subscribe(threadMode = ThreadMode.MAIN) - public void onNodeDestroyed(NodeDestroyedEvent nodeDestroyedEvent) { + public void onNodeDestroyed(NodeDestroyedEvent event) { setOfflineState(); } + @Subscribe(threadMode = ThreadMode.MAIN) + public void onVPNError(VPNErrorEvent event) { + var errorMessage = event.getMessage(); + var message = getString(R.string.toast_vpn_error, errorMessage); + Toast.makeText(getContext(), message, Toast.LENGTH_LONG).show(); + updateNetworkListAndNotify(); + } + private void setOfflineState() { TextView textView = this.nodeStatusView; if (textView != null) { @@ -516,7 +556,7 @@ private void updateNetworkList() { if (this.mVNC != null) { for (Network network : networkList) { for (VirtualNetworkConfig virtualNetworkConfig : this.mVNC) { - network.setConnected(network.getNetworkId() == virtualNetworkConfig.networkId()); + network.setConnected(network.getNetworkId() == virtualNetworkConfig.getNwid()); } } } @@ -538,24 +578,28 @@ public void updateNetworkListAndNotify() { } } - private void startService(long networkId, boolean useDefaultRoute) { - Intent intent = new Intent(getActivity(), ZeroTierOneService.class); + /** + * 启动 ZT 服务连接至指定网络 + * + * @param networkId 网络号 + */ + private void startService(long networkId) { + var intent = new Intent(getActivity(), ZeroTierOneService.class); intent.putExtra(ZeroTierOneService.ZT1_NETWORK_ID, networkId); - intent.putExtra(ZeroTierOneService.ZT1_USE_DEFAULT_ROUTE, useDefaultRoute); doBindService(); - getActivity().startService(intent); + requireActivity().startService(intent); } - /* access modifiers changed from: private */ - /* access modifiers changed from: public */ + /** + * 停止 ZT 服务 + */ private void stopService() { - ZeroTierOneService zeroTierOneService = this.mBoundService; - if (zeroTierOneService != null) { - zeroTierOneService.stopZeroTier(); + if (this.mBoundService != null) { + this.mBoundService.stopZeroTier(); } - Intent intent = new Intent(getActivity(), ZeroTierOneService.class); + var intent = new Intent(requireActivity(), ZeroTierOneService.class); this.eventBus.post(new StopEvent()); - if (!getActivity().stopService(intent)) { + if (!requireActivity().stopService(intent)) { Log.e(TAG, "stopService() returned false"); } doUnbindService(); @@ -566,12 +610,13 @@ private void stopService() { * 获得 Moon 入轨配置列表 */ private List getMoonOrbitList() { - DaoSession daoSession = ((ZerotierFixApplication) getActivity().getApplication()).getDaoSession(); + DaoSession daoSession = ((ZerotierFixApplication) requireActivity().getApplication()).getDaoSession(); return daoSession.getMoonOrbitDao().loadAll(); } /** * 加入网络后事件回调 + * * @param event 事件 */ @Subscribe @@ -582,29 +627,67 @@ public void onAfterJoinNetworkEvent(AfterJoinNetworkEvent event) { this.eventBus.post(new OrbitMoonEvent(moonOrbits)); } - /* renamed from: com.zerotier.one.ui.NetworkListFragment$2 reason: invalid class name */ - static /* synthetic */ class AnonymousClass2 { - static final /* synthetic */ int[] $SwitchMap$com$zerotier$sdk$VirtualNetworkStatus; - - static { - $SwitchMap$com$zerotier$sdk$VirtualNetworkStatus = new int[VirtualNetworkStatus.values().length]; - $SwitchMap$com$zerotier$sdk$VirtualNetworkStatus[com.zerotier.sdk.VirtualNetworkStatus.NETWORK_STATUS_OK.ordinal()] = 1; - $SwitchMap$com$zerotier$sdk$VirtualNetworkStatus[com.zerotier.sdk.VirtualNetworkStatus.NETWORK_STATUS_ACCESS_DENIED.ordinal()] = 2; - $SwitchMap$com$zerotier$sdk$VirtualNetworkStatus[com.zerotier.sdk.VirtualNetworkStatus.NETWORK_STATUS_CLIENT_TOO_OLD.ordinal()] = 3; - $SwitchMap$com$zerotier$sdk$VirtualNetworkStatus[com.zerotier.sdk.VirtualNetworkStatus.NETWORK_STATUS_NOT_FOUND.ordinal()] = 4; - $SwitchMap$com$zerotier$sdk$VirtualNetworkStatus[com.zerotier.sdk.VirtualNetworkStatus.NETWORK_STATUS_PORT_ERROR.ordinal()] = 5; - $SwitchMap$com$zerotier$sdk$VirtualNetworkStatus[com.zerotier.sdk.VirtualNetworkStatus.NETWORK_STATUS_REQUESTING_CONFIGURATION.ordinal()] = 6; - } - } - - /* access modifiers changed from: private */ - public static class JoinAfterAuth { - long networkId; - boolean useDefaultRoute; - - JoinAfterAuth(long j, boolean z) { - this.networkId = j; - this.useDefaultRoute = z; + @Subscribe(threadMode = ThreadMode.BACKGROUND) + public void onNetworkListCheckedChangeEvent(NetworkListCheckedChangeEvent event) { + var switchHandle = event.getSwitchHandle(); + var checked = event.isChecked(); + var selectedNetwork = event.getSelectedNetwork(); + var networkDao = ((ZerotierFixApplication) requireActivity().getApplication()) + .getDaoSession().getNetworkDao(); + if (checked) { + // 启动网络 + var context = requireContext(); + boolean useCellularData = PreferenceManager + .getDefaultSharedPreferences(context) + .getBoolean(Constants.PREF_NETWORK_USE_CELLULAR_DATA, false); + var activeNetworkInfo = ((ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE)) + .getActiveNetworkInfo(); + if (activeNetworkInfo == null || !activeNetworkInfo.isConnectedOrConnecting()) { + // 设备无网络 + requireActivity().runOnUiThread(() -> { + Toast.makeText(NetworkListFragment.this.getContext(), R.string.toast_no_network, Toast.LENGTH_SHORT).show(); + switchHandle.setChecked(false); + }); + } else if (useCellularData || !(activeNetworkInfo.getType() == 0)) { + // 可以连接至网络 + // 先关闭所有现有网络连接 + for (var network : this.mNetworks) { + if (network.getConnected()) { + network.setConnected(false); + } + network.setLastActivated(false); + network.update(); + } + this.stopService(); + // 连接目标网络 + if (!this.isBound()) { + this.sendStartServiceIntent(selectedNetwork.getNetworkId()); + } else { + this.mBoundService.joinNetwork(selectedNetwork.getNetworkId()); + } + Log.d(TAG, "Joining Network: " + selectedNetwork.getNetworkIdStr()); + selectedNetwork.setConnected(true); + selectedNetwork.setLastActivated(true); + networkDao.save(selectedNetwork); + } else { + // 移动数据且未确认 + requireActivity().runOnUiThread(() -> { + Toast.makeText(this.getContext(), R.string.toast_mobile_data, Toast.LENGTH_SHORT).show(); + switchHandle.setChecked(false); + }); + } + } else { + // 关闭网络 + Log.d(TAG, "Leaving Leaving Network: " + selectedNetwork.getNetworkIdStr()); + if (this.isBound() && this.mBoundService != null) { + this.mBoundService.leaveNetwork(selectedNetwork.getNetworkId()); + this.doUnbindService(); + } + this.stopService(); + selectedNetwork.setConnected(false); + networkDao.save(selectedNetwork); + this.mVNC = null; } } @@ -635,10 +718,10 @@ public void onBindViewHolder(final RecyclerViewAdapter.ViewHolder holder, int po // 设置文本信息 holder.mNetworkId.setText(network.getNetworkIdStr()); String networkName = network.getNetworkName(); - if (networkName != null) { + if (networkName != null && !networkName.isEmpty()) { holder.mNetworkName.setText(networkName); } else { - holder.mNetworkName.setText(R.string.network_status_unknown); + holder.mNetworkName.setText(R.string.empty_network_name); } // 设置点击事件 holder.mView.setOnClickListener(holder::onClick); @@ -666,20 +749,19 @@ public class ViewHolder extends RecyclerView.ViewHolder { public ViewHolder(View view) { super(view); mView = view; - mNetworkId = (TextView) view.findViewById(R.id.network_list_network_id); - mNetworkName = (TextView) view.findViewById(R.id.network_list_network_name); - mSwitch = (SwitchCompat) view.findViewById(R.id.network_start_network_switch); + mNetworkId = view.findViewById(R.id.network_list_network_id); + mNetworkName = view.findViewById(R.id.network_list_network_name); + mSwitch = view.findViewById(R.id.network_start_network_switch); } /** * 单击列表项打开网络详细页面 */ - public boolean onClick(View view) { + public void onClick(View view) { Log.d(NetworkListFragment.TAG, "ConvertView OnClickListener"); Intent intent = new Intent(NetworkListFragment.this.getActivity(), NetworkDetailActivity.class); intent.putExtra(NetworkListFragment.NETWORK_ID_MESSAGE, this.mItem.getNetworkId()); NetworkListFragment.this.startActivity(intent); - return true; } /** @@ -693,7 +775,7 @@ public boolean onLongClick(View view) { popupMenu.setOnMenuItemClickListener(menuItem -> { if (menuItem.getItemId() == R.id.menu_item_delete_network) { // 删除对应网络 - DaoSession daoSession = ((ZerotierFixApplication) NetworkListFragment.this.getActivity().getApplication()).getDaoSession(); + DaoSession daoSession = ((ZerotierFixApplication) NetworkListFragment.this.requireActivity().getApplication()).getDaoSession(); AssignedAddressDao assignedAddressDao = daoSession.getAssignedAddressDao(); NetworkConfigDao networkConfigDao = daoSession.getNetworkConfigDao(); NetworkDao networkDao = daoSession.getNetworkDao(); @@ -719,7 +801,7 @@ public boolean onLongClick(View view) { return true; } else if (menuItem.getItemId() == R.id.menu_item_copy_network_id) { // 复制网络 ID - ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); + ClipboardManager clipboard = (ClipboardManager) requireContext().getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText(getString(R.string.network_id), this.mItem.getNetworkIdStr()); clipboard.setPrimaryClip(clip); Toast.makeText(getContext(), R.string.text_copied, Toast.LENGTH_SHORT).show(); @@ -734,58 +816,11 @@ public boolean onLongClick(View view) { * 点击开启网络开关 */ public void onSwitchCheckedChanged(CompoundButton buttonView, boolean isChecked) { - NetworkDao networkDao = ((ZerotierFixApplication) NetworkListFragment.this.getActivity().getApplication()).getDaoSession().getNetworkDao(); - if (isChecked) { - // 启动网络 - Context context = NetworkListFragment.this.getContext(); - boolean useCellularData = PreferenceManager - .getDefaultSharedPreferences(context) - .getBoolean(Constants.PREF_NETWORK_USE_CELLULAR_DATA, false); - NetworkInfo activeNetworkInfo = ((ConnectivityManager) context - .getSystemService(Context.CONNECTIVITY_SERVICE)) - .getActiveNetworkInfo(); - if (activeNetworkInfo == null || !activeNetworkInfo.isConnectedOrConnecting()) { - // 设备无网络 - Toast.makeText(NetworkListFragment.this.getContext(), R.string.toast_no_network, Toast.LENGTH_SHORT).show(); - this.mSwitch.setChecked(false); - } else if (useCellularData || !(activeNetworkInfo == null || activeNetworkInfo.getType() == 0)) { - // 可以连接至网络 - // 先关闭所有现有网络连接 - for (Network network : NetworkListFragment.this.mNetworks) { - if (network.getConnected()) { - network.setConnected(false); - } - network.setLastActivated(false); - network.update(); - } - NetworkListFragment.this.stopService(); - // 连接目标网络 - if (!NetworkListFragment.this.isBound()) { - NetworkListFragment.this.sendStartServiceIntent(this.mItem.getNetworkId(), this.mItem.getUseDefaultRoute()); - } else { - NetworkListFragment.this.mBoundService.joinNetwork(this.mItem.getNetworkId(), this.mItem.getUseDefaultRoute()); - } - Log.d(NetworkListFragment.TAG, "Joining Network: " + this.mItem.getNetworkIdStr()); - this.mItem.setConnected(true); - this.mItem.setLastActivated(true); - networkDao.save(this.mItem); - } else { - // 移动数据且未确认 - Toast.makeText(NetworkListFragment.this.getContext(), R.string.toast_mobile_data, Toast.LENGTH_SHORT).show(); - this.mSwitch.setChecked(false); - } - } else { - // 关闭网络 - Log.d(NetworkListFragment.TAG, "Leaving Leaving Network: " + this.mItem.getNetworkIdStr()); - if (!(!NetworkListFragment.this.isBound() || NetworkListFragment.this.mBoundService == null || this.mItem == null)) { - NetworkListFragment.this.mBoundService.leaveNetwork(this.mItem.getNetworkId()); - NetworkListFragment.this.doUnbindService(); - } - NetworkListFragment.this.stopService(); - this.mItem.setConnected(false); - networkDao.save(this.mItem); - NetworkListFragment.this.mVNC = null; - } + NetworkListFragment.this.eventBus.post(new NetworkListCheckedChangeEvent( + this.mSwitch, + isChecked, + this.mItem + )); } } } diff --git a/app/src/main/java/net/kaaass/zerotierfix/ui/PeerListFragment.java b/app/src/main/java/net/kaaass/zerotierfix/ui/PeerListFragment.java index e8109a6..6dd1619 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/ui/PeerListFragment.java +++ b/app/src/main/java/net/kaaass/zerotierfix/ui/PeerListFragment.java @@ -22,7 +22,7 @@ import net.kaaass.zerotierfix.R; import net.kaaass.zerotierfix.events.PeerInfoReplyEvent; -import net.kaaass.zerotierfix.events.RequestPeerInfoEvent; +import net.kaaass.zerotierfix.events.PeerInfoRequestEvent; import net.kaaass.zerotierfix.util.StringUtils; import org.greenrobot.eventbus.EventBus; @@ -130,7 +130,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, this.swipeRefreshLayout.setOnRefreshListener(this::onRefresh); // 更新入轨数据 - this.eventBus.post(new RequestPeerInfoEvent()); + this.eventBus.post(new PeerInfoRequestEvent()); return view; } @@ -139,7 +139,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, * 刷新列表 */ public void onRefresh() { - this.eventBus.post(new RequestPeerInfoEvent()); + this.eventBus.post(new PeerInfoRequestEvent()); // 超时自动重置刷新状态 new Thread(() -> { try { @@ -193,20 +193,20 @@ public RecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int v public void onBindViewHolder(final RecyclerViewAdapter.ViewHolder holder, int position) { Peer peer = mValues.get(position); holder.mItem = peer; - holder.mAddress.setText(Long.toHexString(peer.address())); - holder.mRole.setText(peerRoleToString(peer.role())); + holder.mAddress.setText(Long.toHexString(peer.getAddress())); + holder.mRole.setText(peerRoleToString(peer.getRole())); // 客户端版本 String clientVersion = getString(R.string.unknown_version); - if (peer.versionMajor() > 0) { + if (peer.getVersionMajor() > 0) { clientVersion = StringUtils.peerVersionString(peer); } holder.mVersion.setText(clientVersion); // 延迟 - holder.mLatency.setText(String.format(getString(R.string.peer_lat), peer.latency())); + holder.mLatency.setText(String.format(getString(R.string.peer_lat), peer.getLatency())); // 当前路径 PeerPhysicalPath preferred = null; - if (peer.paths() != null) { - for (PeerPhysicalPath path : peer.paths()) { + if (peer.getPaths() != null) { + for (PeerPhysicalPath path : peer.getPaths()) { if (path.isPreferred()) { preferred = path; break; @@ -215,7 +215,7 @@ public void onBindViewHolder(final RecyclerViewAdapter.ViewHolder holder, int po } String strPreferred = getString(R.string.peer_relay); if (preferred != null) { - strPreferred = preferred.address().toString(); + strPreferred = preferred.getAddress().toString(); } holder.mPath.setText(strPreferred); } diff --git a/app/src/main/java/net/kaaass/zerotierfix/util/Constants.java b/app/src/main/java/net/kaaass/zerotierfix/util/Constants.java index 5242995..f1d353b 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/util/Constants.java +++ b/app/src/main/java/net/kaaass/zerotierfix/util/Constants.java @@ -13,9 +13,17 @@ public class Constants { public static final String PREF_SET_PLANET_FILE = "set_planet_file"; + public static final String PREF_NETWORK_DISABLE_IPV6 = "network_disable_ipv6"; + + public static final String PREF_GENERAL_START_ZEROTIER_ON_BOOT = "general_start_zerotier_on_boot"; + public static final String FILE_CUSTOM_PLANET = "planet.custom"; public static final String FILE_TEMP = "temp"; public static final String FILE_PLANET = "planet"; + + public static final String CHANNEL_ID = "ZerotierFix"; + + public static final String VPN_SESSION_NAME = "ZerotierFix"; } diff --git a/app/src/main/java/net/kaaass/zerotierfix/util/DatabaseUtils.java b/app/src/main/java/net/kaaass/zerotierfix/util/DatabaseUtils.java new file mode 100644 index 0000000..0f630ec --- /dev/null +++ b/app/src/main/java/net/kaaass/zerotierfix/util/DatabaseUtils.java @@ -0,0 +1,21 @@ +package net.kaaass.zerotierfix.util; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * 数据库访问工具类 + */ +public class DatabaseUtils { + public static final Lock readLock; + public static final ReadWriteLock readWriteLock; + public static final Lock writeLock; + + static { + var reentrantReadWriteLock = new ReentrantReadWriteLock(); + readWriteLock = reentrantReadWriteLock; + writeLock = reentrantReadWriteLock.writeLock(); + readLock = reentrantReadWriteLock.readLock(); + } +} diff --git a/app/src/main/java/net/kaaass/zerotierfix/util/InetAddressUtils.java b/app/src/main/java/net/kaaass/zerotierfix/util/InetAddressUtils.java index f7d5226..cf687a1 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/util/InetAddressUtils.java +++ b/app/src/main/java/net/kaaass/zerotierfix/util/InetAddressUtils.java @@ -12,29 +12,45 @@ public class InetAddressUtils { public static final String TAG = "InetAddressUtils"; - public static byte[] addressToNetmask(InetAddress inetAddress, int i) { - int length = inetAddress.getAddress().length; - int i2 = length * 8; - byte[] bArr = new byte[length]; - for (int i3 = 0; i3 < length; i3++) { - bArr[i3] = -1; + public static final long BROADCAST_MAC_ADDRESS = 0xffffffffffffL; + + /** + * 获得地址指定 CIDR 的子网掩码 + */ + public static byte[] addressToNetmask(InetAddress address, int cidr) { + int length = address.getAddress().length; + int subnetLength = length * 8 - cidr; + byte[] fullMasked = new byte[length]; + for (int i = 0; i < length; i++) { + fullMasked[i] = -1; } if (length == 4) { - int i4 = i2 - i; - return ByteBuffer.allocate(4).putInt((ByteBuffer.wrap(bArr).getInt() >> i4) << i4).array(); - } - int i5 = i2 - i; - byte[] byteArray = new BigInteger(bArr).shiftRight(i5).shiftLeft(i5).toByteArray(); - if (byteArray.length == length) { - return byteArray; - } - byte[] bArr2 = new byte[length]; - int abs = Math.abs(length - byteArray.length); - for (int i6 = 0; i6 < abs; i6++) { - bArr2[i6] = byteArray[0]; + // IPv4 地址 + return ByteBuffer.allocate(4) + .putInt((ByteBuffer.wrap(fullMasked).getInt() >> subnetLength) << subnetLength) + .array(); + } else { + // IPv6 地址 + if (cidr == 0) { + // 若 CIDR 为 0 则返回空子网掩码 + return new byte[length]; + } + byte[] shiftedAddress = new BigInteger(fullMasked) + .shiftRight(subnetLength) + .shiftLeft(subnetLength) + .toByteArray(); + if (shiftedAddress.length == length) { + return shiftedAddress; + } + // 高位为 0 时需要在前补 0 + byte[] netmask = new byte[length]; + int offset = Math.abs(length - shiftedAddress.length); + for (int i = 0; i < offset; i++) { + netmask[i] = shiftedAddress[0]; + } + System.arraycopy(shiftedAddress, 0, netmask, offset, shiftedAddress.length); + return netmask; } - System.arraycopy(byteArray, 0, bArr2, abs, byteArray.length); - return bArr2; } public static InetAddress addressToRoute(InetAddress inetAddress, int i) { @@ -60,16 +76,19 @@ public static InetAddress addressToRoute(InetAddress inetAddress, int i) { return addressToRouteNo0Route(inetAddress, i); } - public static InetAddress addressToRouteNo0Route(InetAddress inetAddress, int i) { - byte[] addressToNetmask = addressToNetmask(inetAddress, i); - byte[] bArr = new byte[addressToNetmask.length]; - for (int i2 = 0; i2 < addressToNetmask.length; i2++) { - bArr[i2] = (byte) (inetAddress.getAddress()[i2] & addressToNetmask[i2]); + /** + * 获得地址对应的网络前缀 + */ + public static InetAddress addressToRouteNo0Route(InetAddress address, int cidr) { + byte[] netmask = addressToNetmask(address, cidr); + byte[] rawAddress = new byte[netmask.length]; + for (int i = 0; i < netmask.length; i++) { + rawAddress[i] = (byte) (address.getAddress()[i] & netmask[i]); } try { - return InetAddress.getByAddress(bArr); + return InetAddress.getByAddress(rawAddress); } catch (UnknownHostException unused) { - Log.e(TAG, "Uknown Host Exception calculating route"); + Log.e(TAG, "Unknown Host Exception calculating route"); return null; } } diff --git a/app/src/main/java/net/kaaass/zerotierfix/util/NetworkInfoUtils.java b/app/src/main/java/net/kaaass/zerotierfix/util/NetworkInfoUtils.java new file mode 100644 index 0000000..ba1e56e --- /dev/null +++ b/app/src/main/java/net/kaaass/zerotierfix/util/NetworkInfoUtils.java @@ -0,0 +1,48 @@ +package net.kaaass.zerotierfix.util; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkCapabilities; +import android.os.Build; + +/** + * 网络连接信息处理工具类 + */ +public class NetworkInfoUtils { + public enum CurrentConnection { + CONNECTION_NONE, + CONNECTION_MOBILE, + CONNECTION_OTHER + } + + public static CurrentConnection getNetworkInfoCurrentConnection(Context context) { + var connectivityManager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivityManager == null) { + return CurrentConnection.CONNECTION_NONE; + } + if (Build.VERSION.SDK_INT >= 23) { + var networkCapabilities = connectivityManager + .getNetworkCapabilities(connectivityManager.getActiveNetwork()); + if (networkCapabilities == null) { + return CurrentConnection.CONNECTION_NONE; + } + if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + return CurrentConnection.CONNECTION_MOBILE; + } + return CurrentConnection.CONNECTION_OTHER; + } else { + var activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); + if (activeNetworkInfo == null) { + return CurrentConnection.CONNECTION_NONE; + } + if (!activeNetworkInfo.isConnectedOrConnecting()) { + return CurrentConnection.CONNECTION_NONE; + } + if (activeNetworkInfo.getType() == 0) { + return CurrentConnection.CONNECTION_MOBILE; + } + return CurrentConnection.CONNECTION_OTHER; + } + } +} diff --git a/app/src/main/java/net/kaaass/zerotierfix/util/StringUtils.java b/app/src/main/java/net/kaaass/zerotierfix/util/StringUtils.java index 4cb673c..6f96060 100644 --- a/app/src/main/java/net/kaaass/zerotierfix/util/StringUtils.java +++ b/app/src/main/java/net/kaaass/zerotierfix/util/StringUtils.java @@ -22,7 +22,7 @@ public class StringUtils { */ public static String toString(Version version) { return String.format(Locale.ROOT, VERSION_FORMAT, - version.major, version.minor, version.revision); + version.getMajor(), version.getMinor(), version.getRevision()); } /** @@ -33,6 +33,26 @@ public static String toString(Version version) { */ public static String peerVersionString(Peer peer) { return String.format(Locale.ROOT, VERSION_FORMAT, - peer.versionMajor(), peer.versionMinor(), peer.versionRev()); + peer.getVersionMajor(), peer.getVersionMinor(), peer.getVersionRev()); + } + + /** + * 将 16 进制字符串转换为字符数组 + * + * @param hex 16 进制字符串 + * @return 字符数组 + */ + public static byte[] hexStringToBytes(String hex) { + int length = hex.length(); + if (length % 2 != 0) { + throw new RuntimeException("String length must be even"); + } + var result = new byte[length / 2]; + for (int i = 0; i < length; i += 2) { + var highDigit = Character.digit(hex.charAt(i), 16); + var lowDigit = Character.digit(hex.charAt(i + 1), 16); + result[i / 2] = (byte) ((highDigit << 4) + lowDigit); + } + return result; } } diff --git a/app/src/main/jniLibs/arm64-v8a/libZeroTierOneJNI.so b/app/src/main/jniLibs/arm64-v8a/libZeroTierOneJNI.so index cca0043..7f696ce 100644 Binary files a/app/src/main/jniLibs/arm64-v8a/libZeroTierOneJNI.so and b/app/src/main/jniLibs/arm64-v8a/libZeroTierOneJNI.so differ diff --git a/app/src/main/jniLibs/armeabi-v7a/libZeroTierOneJNI.so b/app/src/main/jniLibs/armeabi-v7a/libZeroTierOneJNI.so index e5a09ce..294f93f 100644 Binary files a/app/src/main/jniLibs/armeabi-v7a/libZeroTierOneJNI.so and b/app/src/main/jniLibs/armeabi-v7a/libZeroTierOneJNI.so differ diff --git a/app/src/main/jniLibs/x86/libZeroTierOneJNI.so b/app/src/main/jniLibs/x86/libZeroTierOneJNI.so index 7f3054b..fb7fbab 100644 Binary files a/app/src/main/jniLibs/x86/libZeroTierOneJNI.so and b/app/src/main/jniLibs/x86/libZeroTierOneJNI.so differ diff --git a/app/src/main/jniLibs/x86_64/libZeroTierOneJNI.so b/app/src/main/jniLibs/x86_64/libZeroTierOneJNI.so index 1aa122a..ed3f568 100644 Binary files a/app/src/main/jniLibs/x86_64/libZeroTierOneJNI.so and b/app/src/main/jniLibs/x86_64/libZeroTierOneJNI.so differ diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 7b77381..2bae6f4 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -37,6 +37,7 @@ 端口错误 正在请求配置 未知 + 需要授权 网络类型 网络类型: 私有 @@ -61,7 +62,6 @@ 已连接 没有网络连接,无法启动 ZeroTier 设备正在使用移动网络,必须开启“允许移动网络下使用”选项以启动 ZeroTier - 无权使用该网络 结点列表 入轨 启用 @@ -127,4 +127,17 @@ Moon 文件格式错误 Moon 结点的 Zerotier 地址。即 Moon 文件的文件名(不包含扩展名)。 已删除缓存的 Moon 文件。需手动重连使配置生效。 + ZeroTier Fix + 连接状态 + 已连接 + 已连接至网络: %1$s + VPN 服务还未准备好或已经失效。 + 成功连接至网络 %1$s + 无法连接至网络 %1$s: 访问被拒绝 + 无法连接至网络 %1$s: 网络不存在 + 无法连接至网络 %1$s: 端口错误 + 无法连接至网络 %1$s: 客户端版本过低 + 无法连接至网络 %1$s: 需要授权 + VPN 服务发生错误!%1$s + 未知 \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index b62fcd4..6cc5c01 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -22,4 +22,6 @@ #555555 #FAC073 + + #fac073 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 86c22ba..3adf24b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -38,6 +38,7 @@ Port Error Requesting Configuration Unknown + Authentication Required Type Type: Private @@ -62,7 +63,6 @@ ONLINE No Network Connectivity. ZeroTier cannot start. Currently using mobile data. Enable \"Use Cellular Data\" in order to start ZeroTier. - Not Authorized for network Peers Orbit Enabled @@ -129,4 +129,17 @@ Wrong moon file format Zerotier address of Moon node. Also filename of its moon file. Cached moon file deleted. Reconnect for this setting to take effect. + ZeroTier Fix + Connection Status + Connected + Connected to: %1$s + VPN application is not prepared or is revoked. + Successfully connected to network %1$s + Cannot connect to network %1$s: Access denied + Cannot connect to network %1$s: Network does not exist + Cannot connect to network %1$s: Port error + Cannot connect to network %1$s: Client version too low + Cannot connect to network %1$s: Authorization required + A VPN service error has occurred! %1$s + Unknown