Showing preview only (524K chars total). Download the full file or copy to clipboard to get everything.
Repository: Genymobile/gnirehtet
Branch: master
Commit: 1eb2e58bc91e
Files: 119
Total size: 487.8 KB
Directory structure:
gitextract_jsgxj4af/
├── .gitignore
├── DEVELOP.md
├── LICENSE
├── README.md
├── app/
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── genymobile/
│ │ │ └── gnirehtet/
│ │ │ ├── Binary.java
│ │ │ ├── CIDR.java
│ │ │ ├── Forwarder.java
│ │ │ ├── GnirehtetActivity.java
│ │ │ ├── GnirehtetService.java
│ │ │ ├── IPPacketOutputStream.java
│ │ │ ├── InvalidCIDRException.java
│ │ │ ├── Net.java
│ │ │ ├── Notifier.java
│ │ │ ├── PersistentRelayTunnel.java
│ │ │ ├── RelayTunnel.java
│ │ │ ├── RelayTunnelListener.java
│ │ │ ├── RelayTunnelProvider.java
│ │ │ ├── Tunnel.java
│ │ │ └── VpnConfiguration.java
│ │ └── res/
│ │ ├── drawable/
│ │ │ ├── ic_close_24dp.xml
│ │ │ ├── ic_report_problem_24dp.xml
│ │ │ └── ic_usb_24dp.xml
│ │ ├── values/
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ └── values-fr/
│ │ └── strings.xml
│ └── test/
│ └── java/
│ └── com/
│ └── genymobile/
│ └── gnirehtet/
│ └── TestIPPacketOutputSteam.java
├── build.gradle
├── config/
│ ├── android-checkstyle.gradle
│ ├── android-signing.gradle
│ ├── checkstyle/
│ │ └── checkstyle.xml
│ └── java-checkstyle.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── relay-java/
│ ├── build.gradle
│ ├── scripts/
│ │ ├── gnirehtet
│ │ ├── gnirehtet-run.cmd
│ │ └── gnirehtet.cmd
│ └── src/
│ ├── main/
│ │ └── java/
│ │ └── com/
│ │ └── genymobile/
│ │ └── gnirehtet/
│ │ ├── AdbMonitor.java
│ │ ├── CommandLineArguments.java
│ │ ├── Main.java
│ │ └── relay/
│ │ ├── AbstractConnection.java
│ │ ├── Binary.java
│ │ ├── Client.java
│ │ ├── CloseListener.java
│ │ ├── CommandExecutionException.java
│ │ ├── Connection.java
│ │ ├── ConnectionId.java
│ │ ├── DatagramBuffer.java
│ │ ├── IPv4Header.java
│ │ ├── IPv4Packet.java
│ │ ├── IPv4PacketBuffer.java
│ │ ├── Log.java
│ │ ├── Net.java
│ │ ├── PacketSource.java
│ │ ├── Packetizer.java
│ │ ├── Relay.java
│ │ ├── Router.java
│ │ ├── SelectionHandler.java
│ │ ├── StreamBuffer.java
│ │ ├── TCPConnection.java
│ │ ├── TCPHeader.java
│ │ ├── TransportHeader.java
│ │ ├── TunnelServer.java
│ │ ├── UDPConnection.java
│ │ └── UDPHeader.java
│ └── test/
│ └── java/
│ └── com/
│ └── genymobile/
│ └── gnirehtet/
│ ├── AdbMonitorTest.java
│ ├── CommandLineArgumentsTest.java
│ └── relay/
│ ├── DatagramBufferTest.java
│ ├── IPv4HeaderTest.java
│ ├── IPv4PacketBufferTest.java
│ ├── IPv4PacketTest.java
│ ├── InetAddressTest.java
│ ├── PacketizerTest.java
│ ├── StreamBufferTest.java
│ ├── TCPHeaderTest.java
│ └── UDPHeaderTest.java
├── relay-rust/
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── build.gradle
│ ├── scripts/
│ │ └── gnirehtet-run.cmd
│ └── src/
│ ├── adb_monitor.rs
│ ├── cli_args.rs
│ ├── execution_error.rs
│ ├── lib.rs
│ ├── logger.rs
│ ├── main.rs
│ └── relay/
│ ├── binary.rs
│ ├── byte_buffer.rs
│ ├── client.rs
│ ├── close_listener.rs
│ ├── connection.rs
│ ├── datagram.rs
│ ├── datagram_buffer.rs
│ ├── interrupt.rs
│ ├── ipv4_header.rs
│ ├── ipv4_packet.rs
│ ├── ipv4_packet_buffer.rs
│ ├── mod.rs
│ ├── net.rs
│ ├── packet_source.rs
│ ├── packetizer.rs
│ ├── relay.rs
│ ├── router.rs
│ ├── selector.rs
│ ├── stream_buffer.rs
│ ├── tcp_connection.rs
│ ├── tcp_header.rs
│ ├── transport_header.rs
│ ├── tunnel_server.rs
│ ├── udp_connection.rs
│ └── udp_header.rs
├── release
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
build/
.gradle/
.idea/
*.iml
/local.properties
================================================
FILE: DEVELOP.md
================================================
# Gnirehtet for developers
## Getting started
### Requirements
You need the [Android SDK] (_Android Studio_) and the JDK 8 (`openjdk-8-jdk`).
You also need the [Rust] environment to build the Rust version:
```bash
wget https://sh.rustup.rs -O rustup-init
sh rustup-init
```
[Android SDK]: https://developer.android.com/studio/index.html
[Rust]: https://www.rust-lang.org/
### Build
#### Everything
If `gradle` is installed on your computer:
gradle build
Otherwise, you can call the [gradle wrapper]:
./gradlew build
This will build the Android application, the Java and Rust relay servers, both
in debug and release versions.
[gradle wrapper]: https://docs.gradle.org/current/userguide/gradle_wrapper.html
#### Specific parts
Several _gradle_ tasks are exposed in the root project. For instance:
- `debugJava` and `releaseJava` build the Android application and the Java
relay server;
- `debugRust` and `releaseRust` build the Android application and the Rust
relay server.
Even if the Rust build tasks are exposed through `gradle` (which wraps calls to
`cargo`), it is often more convenient to use `cargo` directly.
For instance, to build a release version of the Rust relay server:
cd relay-rust
cargo build --release
It will generate the binary in `target/release/gnirehtet`.
#### Cross-compile the Rust relay server from Linux to Windows
To build `gnirehtet.exe` from Linux, install the cross-compile toolchain (on
Debian):
sudo apt install gcc-mingw-w64-x86-64
rustup target add x86_64-pc-windows-gnu
Add the following lines to `~/.cargo/config`:
[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"
ar = "x86_64-w64-mingw32-gcc-ar"
Then build:
cargo build --release --target=x86_64-pc-windows-gnu
It will generate `target/x86_64-pc-windows-gnu/release/gnirehtet.exe`.
### Android Studio
To import the project in _Android Studio_: File → Import…
From there, you can develop on the Android application and the Java relay
server. You can also execute any _gradle_ tasks, and run the tests with visual
results.
## Overview
The client registers itself as a [VPN], in order to intercept the whole device
network traffic.
It exchanges raw [IPv4 packets] as `byte[]` with the device:
- it receives packets from the Android applications or system;
- it must forge response packets.
The client (executed on the Android device) just maintains a TCP connection to
the relay server, and sends the raw packets to it.
This TCP connection is established over _adb_, after we started a reverse
port redirection:
adb reverse localabstract:gnirehtet tcp:31416
This means that every connection initiated to `localhost:31416` from the device
will be redirected to the port `31416` on the computer, on which the relay
server is listening.
The relay server does all the hard work. It receives the IP packets from every
connected client and opens [standard sockets][berkeley] (which, of course, don't
require _root_) accordingly, then relays data in both directions. This requires
to translate packets between level 3 (on the device side) and level 5 (on the
network side) in the [OSI model].
In a sense, the relay server behaves like a [NAT] (more precisely a
[port-restricted cone NAT][portNAT]), in that it opens connections on behalf of
private peers. However, it differs from a standard NAT in the way it
communicates with the clients (the private peers), by using a very specific
(though simple) protocol, over a TCP connection.
[VPN]: https://developer.android.com/reference/android/net/VpnService.html
[IPv4 packets]: https://en.wikipedia.org/wiki/IPv4#Packet_structure
[OSI model]: https://en.wikipedia.org/wiki/OSI_model
[berkeley]: https://en.wikipedia.org/wiki/Berkeley_sockets
[NAT]: https://en.wikipedia.org/wiki/Network_address_translation
[portNAT]: https://en.wikipedia.org/wiki/Network_address_translation#Methods_of_translation
## Client
The client is an _Android_ project located in [`app/`](app/).
The [`VpnService`] is implemented by [`GnirehtetService`].
We control the application through intents to [`GnirehtetActivity`].
Some configuration options may be passed as extra parameters, converted to a
[`VpnConfiguration`] instance. Currently, the user can configure the DNS servers
to use.
The very first time, Android requests to the user the permission to enable the
VPN. In that case, the API requires to call
[`startActivityForResult`], so we need an [`Activity`]: this is the purpose
of [`AuthorizationActivity`].
[`RelayTunnel`] manages one connection to the relay server.
[`PersistentRelayTunnel`] manages [`RelayTunnel`] instances to handle
reconnections, so that we can stop and start the relay while the client keeps
running.
To send response packets to the system, we must write one packet at a time to
the VPN interface. Since we receive packets from the relay server over a TCP
connection, we have to split writes at packet boundaries: this is the purpose
of [`IPPacketOutputStream`].
[`VpnService`]: https://developer.android.com/reference/android/net/VpnService.html
[`GnirehtetService`]: app/src/main/java/com/genymobile/gnirehtet/GnirehtetService.java
[`GnirehtetActivity`]: app/src/main/java/com/genymobile/gnirehtet/GnirehtetActivity.java
[`VpnConfiguration`]: app/src/main/java/com/genymobile/gnirehtet/VpnConfiguration.java
[`startActivityForResult`]: https://developer.android.com/reference/android/app/Activity.html#startActivityForResult%28android.content.Intent,%20int%29
[`Activity`]: https://developer.android.com/reference/android/app/Activity.html
[`AuthorizationActivity`]: app/src/main/java/com/genymobile/gnirehtet/AuthorizationActivity.java
[`RelayTunnel`]: app/src/main/java/com/genymobile/gnirehtet/RelayTunnel.java
[`PersistentRelayTunnel`]: app/src/main/java/com/genymobile/gnirehtet/PersistentRelayTunnel.java
[`IPPacketOutputStream`]: app/src/main/java/com/genymobile/gnirehtet/IPPacketOutputStream.java
## Relay server
The relay server comes in two flavors:
- the **Java** version is a _Java 8_ project located in
[`relay-java/`](relay-java/);
- the **Rust** version is a _Rust_ project located in
[`relay-rust/`](relay-rust/).
It is implemented using [asynchronous I/O] (through [Java NIO] and [Rust mio]).
As a consequence, it is essentially monothreaded, so there is no need for
synchronization to handle packets.
[asynchronous I/O]: https://en.wikipedia.org/wiki/Asynchronous_I/O
[Java NIO]: https://en.wikipedia.org/wiki/New_I/O_%28Java%29
[Rust mio]: https://docs.rs/mio/0.6.10/mio/
### Selector
There are different _socket channels_ registered to a unique _selector_:
- one for the server socket, listening on port 31416;
- one for each _client_, accepted by the server socket;
- one for each _TCP connection_ to the network;
- one for each _UDP connection_ to the network.
Initially, only the server socket _channel_ is registered.
In **Java**, the _channels_ ([`SelectableChannel`][nio/SelectableChannel]) are
registered to the _selector_ ([`Selector`][nio/Selector]) defined in
[`Relay`][java/Relay], with their [`SelectionHandler`][java/SelectionHandler] as
[attachment][nio/attachment] (for better decoupling). A [`Client`][java/Client]
is created for every accepted _client_.
[nio/Selector]: https://docs.oracle.com/javase/8/docs/api/java/nio/channels/Selector.html
[nio/SelectableChannel]: https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SelectableChannel.html
[java/Relay]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Relay.java
[java/SelectionHandler]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/SelectionHandler.java
[nio/attachment]: https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SelectionKey.html#attachment--
[java/Client]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Client.java
In **Rust**, our own [`Selector`][rust/selector] class wraps the
[`Poll`][mio/Poll] from _mio_ to expose an API accepting event handlers instead
of low-level [tokens][mio/Token]. The _selector_ instance is created in
[`Relay`][rust/relay]. The _channels_ are called _"handles"_ in _mio_; they are
simply the socket instances themselves ([`TcpListener`][mio/TcpListener],
[`TcpStream`][mio/TcpStream] and [`UdpSocket`][mio/UdpSocket]). A
[`Client`][rust/client] is created for every accepted _client_.
[mio/Poll]: https://docs.rs/mio/0.6.10/mio/struct.Poll.html
[mio/Token]: https://docs.rs/mio/0.6.10/mio/struct.Token.html
[mio/TcpListener]: https://docs.rs/mio/0.6.10/mio/net/struct.TcpListener.html
[mio/TcpStream]: https://docs.rs/mio/0.6.10/mio/net/struct.TcpStream.html
[mio/UdpSocket]: https://docs.rs/mio/0.6.10/mio/net/struct.UdpSocket.html
[rust/selector]: relay-rust/src/relay/selector.rs
[rust/relay]: relay-rust/src/relay/relay.rs
[rust/client]: relay-rust/src/relay/client.rs

### Client
Each _client_ manages a TCP socket, used to transmit raw IP packets from and to
the _Gnirehtet_ Android client. Thus, these IP packets are encapsulated into TCP
(they are transmitted as the TCP payload).
When a client connects, the relay server assigns an integer id to it, which it
writes to the TCP socket. The client considers itself connected to the relay
server only once it has received this number. This allows to detect any
end-to-end connection issue immediately. For instance, a TCP _connect_ initiated
by a client succeeds whenever a port redirection is enabled (typically through
`adb reverse`), even if the relay server is not listening. In that case, the
first _read_ will fail.
### Packets
A class representing an _IPv4 packet_
([`IPv4Packet`][java/IPv4Packet] | [`Ipv4Packet`][rust/ipv4-packet]) provides a
structured view to read and write packet data, which is physically stored in the
buffers (the little squares on the schema). Since we handle one packet at a time
with asynchronous I/O, there is no need to copy or synchronize access to the
packets data: the packets just point to the buffer where they are stored.
[java/IPv4Packet]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/IPv4Packet.java
[rust/ipv4-packet]: relay-rust/src/relay/ipv4\_packet.rs
Each packet contains an instance of _IPv4 headers_ and _transport headers_
(which might be _TCP_ or _UDP_ headers).
In **Java**, this is straightforward: [`IPv4Header`][java/IPv4Header],
[`TCPHeader`][java/TCPHeader] and [`UDPHeader`][java/UDPHeader] just share a
slice of the raw packet buffer.
[java/IPv4Header]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/IPv4Header.java
[java/TCPHeader]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/TCPHeader.java
[java/UDPHeader]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/UDPHeader.java
In **Rust**, the borrowing rules prevent to share a mutable reference.
Therefore, _header data_ classes (`*HeaderData`) are used to store the fields,
and lifetime-bound views (`*Header<'a>` and `*HeaderMut<'a>`) reference both
the raw array and the _header data_:
- [`ipv4_header`][rust/ipv4-header]:
- data: `Ipv4HeaderData`
- view: `Ipv4Header<'a>`
- mutable view: `Ipv4HeaderMut<'a>`
- [`tcp_header`][rust/tcp-header]:
- data: `TcpHeaderData`
- view: `TcpHeader<'a>`
- mutable view: `TcpHeaderMut<'a>`
- [`udp_header`][rust/udp-header]:
- data: `UdpHeaderData`
- view: `UdpHeader<'a>`
- mutable view: `UdpHeaderMut<'a>`
In addition, we use [enums][rust-enums] for _transport headers_ to statically
dispatch calls to _UDP_ and _TCP_ header classes:
- [`transport_header`][rust/transport-header]:
- data: `TransportHeaderData`
- view: `TransportHeader<'a>`
- mutable view: `TransportHeaderMut<'a>`
[rust/ipv4-header]: relay-rust/src/relay/ipv4\_header.rs
[rust/tcp-header]: relay-rust/src/relay/tcp\_header.rs
[rust/udp-header]: relay-rust/src/relay/udp\_header.rs
[rust/transport-header]: relay-rust/src/relay/transport\_header.rs
[rust-enums]: https://doc.rust-lang.org/book/first-edition/enums.html
### Router
Each _client_ holds a _router_
([`Router`][java/Router] | [`Router`][rust/router]), responsible for sending the
packets to the right _connection_, identified by these 5 properties available in
the IP and transport headers:
- protocol
- source address
- source port
- destination address
- destination port
These identifiers are stored in a _connection id_
([`ConnectionId`][java/ConnectionId] | [`ConnectionId`][rust/connection]),
used as a key to find or create the associated _connection_.
[java/Router]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Router.java
[java/ConnectionId]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/ConnectionId.java
[rust/Router]: relay-rust/src/relay/router.rs
[rust/connection]: relay-rust/src/relay/connection.rs
### Connections
A _connection_ ([`Connection`][java/Connection] |
[`Connection`][rust/connection]) is either a _TCP connection_
([`TCPConnection`][java/TCPConnection] | [`TcpConnection`][rust/tcp-connection])
or a _UDP connection_ ([`UDPConnection`][java/UDPConnection] |
[`UdpConnection`][rust/udp-connection]) to the requested destination. It
registers its own _channel_ to the _selector_.
[java/Connection]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Connection.java
[java/TCPConnection]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/TCPConnection.java
[java/UDPConnection]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/UDPConnection.java
[rust/connection]: relay-rust/src/relay/connection.rs
[rust/tcp-connection]: relay-rust/src/relay/tcp\_connection.rs
[rust/udp-connection]: relay-rust/src/relay/udp\_connection.rs
The connection is responsible for converting data from level 3 to level 5 for
device-to-network packets, and from level 5 to level 3 for network-to-device
packets. For _UDP connections_, it consists essentially in removing or
adding IP and transport headers. For _TCP connections_, however, it
requires to respond to the client according to the TCP protocol ([RFC 793]),
in such a way as to ensure a correct end-to-end communication.
[RFC 793]: https://tools.ietf.org/html/rfc793
A _packetizer_ ([`Packetizer`][java/Packetizer] |
[`Packetizer`][rust/packetizer]) converts from level 5 to level 3 by appending
correct IP and transport headers.
[java/Packetizer]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Packetizer.java
[rust/packetizer]: relay-rust/src/relay/packetizer.rs
#### UDP connection
When the first packet for a specific UDP connection is received from the device,
a new `UdpConnection` is created. It keeps a copy of the IP and UDP headers
of this first packet, swapping the source and the destination, in order to use
them as headers for all response packets.
The relaying is simple for UDP: each packet received from one side must be sent
to the other side, without any splitting or merging (datagram boundaries must be
preserved for UDP).
Since UDP is not a connected protocol, a UDP connection is never "closed".
Therefore, the _selector_ wakes up once per minute (using a timeout) to clean
expired (in practice, unused for more than 2 minutes) UDP connections.
#### TCP connection
`TcpConnection` also keeps, as a reference, a copy of the IP and TCP headers
of the first packet received.
However, contrary to UDP, TCP must provide reliable delivery. In particular,
lost packets have to be retransmitted. Nonetheless, we can take advantage of the
two TCP we are proxifying, so that we can provide reliability by delegating the
retransmission mechanism to them. In fact, it is sufficient to guarantee that
**we cannot lose packets from network to device**.
Indeed, any packet written to a TCP channel is safe, since it will be managed by
the TCP implementation from the system. Losing a raw IP packet received from the
device is also safe: the device TCP implementation will follow the TCP protocol
to retransmit it. Therefore, **dropping packets from device to network does not
break the connection**.
On the other hand, once we retrieved a packet from a TCP channel from the
network, we are responsible for it. Would it be dropped, there would be no way
to recover the connection.
As far as I know, there are only two possible causes of packet loss for which we
are responsible:
1. When **our buffers are full**, we won't resize them indefinitely, so we have to
drop packets. Typically, this may happen if the data from the network is
received at a higher rate than that they can be sent to the device.
2. When **a raw packet is considered invalid** by the device, it is rejected.
This may happen for example if the checksum is invalid or if the TCP sequence
number is [out-of-the-window][flow control].
[flow control]: https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Flow_control
Therefore, by [contraposition], if we guarantee that we never retrieve a packet
that we won't be able to store, and that we provide a valid checksum and respect
the client TCP window, then **we won't lose any packet without implementing any
retransmission mechanism**.
[contraposition]: https://en.wikipedia.org/wiki/Contraposition
To prevent retrieving a packet while our buffers are full, we indicate that we
are not interested in reading ([`interestOps`][nio/interestOps] |
[`interest`][mio/reregister]) the TCP channel when some pending data remain to
be written to the client buffer. Once some space becomes available, the client
then _pulls_ the available packets from the `TcpConnection`s, which are _packet
sources_ ([`PacketSource`][java/PacketSource] |
[`PacketSource`][rust/packet-source]).
[nio/interestOps]: https://developer.android.com/reference/java/nio/channels/SelectionKey.html#interestOps%28int%29
[mio/reregister]: https://docs.rs/mio/0.6.10/mio/struct.Poll.html#method.reregister
[java/PacketSource]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/PacketSource.java
[rust/packet-source]: relay-rust/src/relay/packet\_source.rs
## Hack
For more details, go read the code!
If you find a bug, or have an awesome idea to implement, please discuss and
contribute ;-)
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (C) 2017 Genymobile
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
# Gnirehtet (v2.5.1)
This project provides **reverse tethering** over `adb` for Android: it
allows devices to use the internet connection of the computer they are plugged
on. It does not require any _root_ access (neither on the device nor on the
computer). It works on _GNU/Linux_, _Windows_ and _Mac OS_.
Currently, it relays [TCP] and [UDP] over [IPv4] traffic, but it does not
support [IPv6] (yet?).
[TCP]: https://en.wikipedia.org/wiki/Transmission_Control_Protocol
[UDP]: https://fr.wikipedia.org/wiki/User_Datagram_Protocol
[IPv4]: https://en.wikipedia.org/wiki/IPv4
[IPv6]: https://en.wikipedia.org/wiki/IPv6
_**This project is not actively maintained anymore, only major blockers (like
build issues) are fixed. It should still work, though.**_
## Flavors
Two implementations of _Gnirehtet_ are available:
- one in **Java**;
- one in **Rust**.
### Which one to choose?
Use the **Rust** implementation. The native binary consumes less CPU and memory,
and does not require a _Java_ runtime environment.
The relay server of _Gnirehtet_ was initially only implemented in Java. As a
benefit, the same "binary" runs on every platform having _Java 8_ runtime
installed. It is still maintained to provide a working alternative in case of
problems with the Rust version.
## Requirements
The Android application requires at least API 21 (Android 5.0).
For the _Java_ version only, _Java 8_ (JRE) is required on your computer. On
Debian-based distros, install the package `openjdk-8-jre`.
### adb
You need a recent version of [adb] (where `adb reverse` is implemented, it
works with 1.0.36).
It is available in the [Android SDK platform tools][platform-tools].
On Debian-based distros, you can alternatively install the package
`android-tools-adb`.
On Windows, if you need `adb` only for this application, just download the
[platform-tools][platform-tools-windows] and extract the following files to the
_gnirehtet_ directory:
- `adb.exe`
- `AdbWinApi.dll`
- `AdbWinUsbApi.dll`
Make sure you [enabled adb debugging][enable-adb] on your device(s).
[adb]: https://developer.android.com/studio/command-line/adb.html
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
[platform-tools]: https://developer.android.com/studio/releases/platform-tools.html
[platform-tools-windows]: https://dl.google.com/android/repository/platform-tools-latest-windows.zip
## Get the app
### Homebrew
If you use [Homebrew](https://brew.sh/), getting up and running is very quick.
To install the Rust version:
```
brew install gnirehtet
```
### Download
Download the [latest release][latest] in the flavor you want.
[latest]: https://github.com/Genymobile/gnirehtet/releases/latest
#### Rust
- **Linux:** [`gnirehtet-rust-linux64-v2.5.1.zip`][direct-rust-linux64]
(SHA-256: _dee55499ca4fef00ce2559c767d2d8130163736d43fdbce753e923e75309c275_)
- **Windows:** [`gnirehtet-rust-win64-v2.5.1.zip`][direct-rust-win64]
(SHA-256: _7f5b1063e7895182aa60def1437e50363c3758144088dcd079037bb7c3c46a1c_)
- **MacOS:** [`gnirehtet-rust-macos64-v2.2.1.zip`][direct-rust-macos64]
_(old release)_
(SHA-256: _902103e6497f995e1e9b92421be212559950cca4a8b557e1f0403769aee06fc8_)
[direct-rust-linux64]: https://github.com/Genymobile/gnirehtet/releases/download/v2.5.1/gnirehtet-rust-linux64-v2.5.1.zip
[direct-rust-win64]: https://github.com/Genymobile/gnirehtet/releases/download/v2.5.1/gnirehtet-rust-win64-v2.5.1.zip
[direct-rust-macos64]: https://github.com/Genymobile/gnirehtet/releases/download/v2.2.1/gnirehtet-rust-macos64-v2.2.1.zip
Then extract it.
The Linux and MacOS archives contain:
- `gnirehtet.apk`
- `gnirehtet`
The Windows archive contains:
- `gnirehtet.apk`
- `gnirehtet.exe`
- `gnirehtet-run.cmd`
#### Java
- **All platforms:** [`gnirehtet-java-v2.5.1.zip`][direct-java]
(SHA-256: _816748078fa6a304600a294a13338a06ac778bcc0e57b62d88328c7968ad2d3a_)
[direct-java]: https://github.com/Genymobile/gnirehtet/releases/download/v2.5.1/gnirehtet-java-v2.5.1.zip
Then extract it. The archive contains:
- `gnirehtet.apk`
- `gnirehtet.jar`
- `gnirehtet`
- `gnirehtet.cmd`
- `gnirehtet-run.cmd`
## Run (simple)
_Note: On Windows, replace `./gnirehtet` by `gnirehtet` in the following
commands._
The application has no UI, and is intended to be controlled from the computer
only.
If you want to activate reverse tethering for exactly one device, just execute:
./gnirehtet run
Reverse tethering remains active until you press _Ctrl+C_.
On Windows, for convenience, you can double-click on `gnirehtet-run.cmd`
instead (it just executes `gnirehtet run`, without requiring to open a
terminal).
The very first start should open a popup to request permission:

A "key" logo appears in the status bar whenever _Gnirehtet_ is active:

Alternatively, you can enable reverse tethering for all connected devices
(present and future) by calling:
./gnirehtet autorun
## Run
You can execute the actions separately (it may be useful if you want to reverse
tether several devices simultaneously).
Start the relay server and keep it open:
./gnirehtet relay
Install the `apk` on your Android device:
./gnirehtet install [serial]
In another terminal, for each client, execute:
./gnirehtet start [serial]
To stop a client:
./gnirehtet stop [serial]
To reset the tunnel (useful to get the connection back when a device is
unplugged and plugged back while gnirehtet is active):
./gnirehtet tunnel [serial]
The _serial_ parameter is required only if `adb devices` outputs more than one
device.
For advanced options, call `./gnirehtet` without arguments to get more details.
## Run manually
The `gnirehtet` program exposes a simple command-line interface that executes
lower-level commands. You can call them manually instead.
To start the relay server:
./gnirehtet relay
To install the apk:
adb install -r gnirehtet.apk
To start a client:
adb reverse localabstract:gnirehtet tcp:31416
adb shell am start -a com.genymobile.gnirehtet.START \
-n com.genymobile.gnirehtet/.GnirehtetActivity
To stop a client:
adb shell am start -a com.genymobile.gnirehtet.STOP \
-n com.genymobile.gnirehtet/.GnirehtetActivity
## Environment variables
`ADB` defines a custom path to the `adb` executable:
```bash
ADB=/path/to/my/adb ./gnirehtet run
```
`GNIREHTET_APK` defines a custom path to `gnirehtet.apk`:
```bash
GNIREHTET_APK=/usr/share/gnirehtet/gnirehtet.apk ./gnirehtet run
```
## Why _gnirehtet_?
rev <<< tethering
(in _Bash_)
## Developers
Read the [developers page].
[developers page]: DEVELOP.md
## Licence
Copyright (C) 2017 Genymobile
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## Articles
- [Introducing “gnirehtet”, a reverse tethering tool for Android][medium-1] ([French version][blog-1])
- [Gnirehtet 2: our reverse tethering tool for Android now available in Rust][medium-2]
- [Gnirehtet rewritten in Rust][blog-2-en] ([French version][blog-2-fr])
[medium-1]: https://medium.com/@rom1v/gnirehtet-reverse-tethering-android-2afacdbdaec7
[blog-1]: https://blog.rom1v.com/2017/03/gnirehtet/
[medium-2]: https://medium.com/genymobile/gnirehtet-2-our-reverse-tethering-tool-for-android-now-available-in-rust-999960483d5a
[blog-2-en]: https://blog.rom1v.com/2017/09/gnirehtet-rewritten-in-rust/
[blog-2-fr]: https://blog.rom1v.com/2017/09/gnirehtet-reecrit-en-rust/
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
archivesBaseName = "gnirehtet" // change apk name
applicationId "com.genymobile.gnirehtet"
minSdkVersion 21
targetSdkVersion 29
versionCode 9
versionName "2.5.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
testImplementation 'junit:junit:4.12'
}
apply from: "$project.rootDir/config/android-checkstyle.gradle"
apply from: "$project.rootDir/config/android-signing.gradle"
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /home/rom/android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.genymobile.gnirehtet">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="false"
android:icon="@null"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name="com.genymobile.gnirehtet.GnirehtetActivity"
android:exported="true"
android:permission="android.permission.WRITE_SECURE_SETTINGS"
android:theme="@style/Theme.Transparent">
<intent-filter>
<action android:name="com.genymobile.gnirehtet.START" />
<action android:name="com.genymobile.gnirehtet.STOP" />
</intent-filter>
</activity>
<service
android:name="com.genymobile.gnirehtet.GnirehtetService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService" />
</intent-filter>
</service>
</application>
</manifest>
================================================
FILE: app/src/main/java/com/genymobile/gnirehtet/Binary.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet;
@SuppressWarnings("checkstyle:MagicNumber")
public final class Binary {
private static final int MAX_STRING_PACKET_SIZE = 20;
private Binary() {
// not instantiable
}
public static int unsigned(byte value) {
return value & 0xff;
}
public static int unsigned(short value) {
return value & 0xffff;
}
public static long unsigned(int value) {
return value & 0xffffffffL;
}
public static String buildPacketString(byte[] data, int len) {
int limit = Math.min(MAX_STRING_PACKET_SIZE, len);
StringBuilder builder = new StringBuilder();
builder.append('[').append(len).append(" bytes] ");
for (int i = 0; i < limit; ++i) {
if (i != 0) {
String sep = i % 4 == 0 ? " " : " ";
builder.append(sep);
}
builder.append(String.format("%02X", data[i] & 0xff));
}
if (limit < len) {
builder.append(" ... +").append(len - limit).append(" bytes");
}
return builder.toString();
}
}
================================================
FILE: app/src/main/java/com/genymobile/gnirehtet/CIDR.java
================================================
/*
* Copyright (C) 2018 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class CIDR implements Parcelable {
private InetAddress address;
private int prefixLength;
public CIDR(InetAddress address, int prefixLength) {
this.address = address;
this.prefixLength = prefixLength;
}
private CIDR(Parcel source) {
try {
address = InetAddress.getByAddress(source.createByteArray());
} catch (UnknownHostException e) {
throw new AssertionError("Invalid address", e);
}
prefixLength = source.readInt();
}
@SuppressWarnings("checkstyle:MagicNumber")
public static CIDR parse(String cidr) throws InvalidCIDRException {
int slashIndex = cidr.indexOf('/');
InetAddress address;
int prefix;
try {
if (slashIndex != -1) {
address = Net.toInetAddress(cidr.substring(0, slashIndex));
prefix = Integer.parseInt(cidr.substring(slashIndex + 1));
} else {
address = Net.toInetAddress(cidr);
prefix = 32;
}
return new CIDR(address, prefix);
} catch (IllegalArgumentException e) {
Log.e("Error", e.getMessage(), e);
throw new InvalidCIDRException(cidr, e);
} catch (Throwable e) {
Log.e("Error", e.getMessage(), e);
throw e;
}
}
public InetAddress getAddress() {
return address;
}
public int getPrefixLength() {
return prefixLength;
}
@Override
public String toString() {
return address.getHostAddress() + "/" + prefixLength;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByteArray(address.getAddress());
dest.writeInt(prefixLength);
}
public static final Creator<CIDR> CREATOR = new Creator<CIDR>() {
@Override
public CIDR createFromParcel(Parcel source) {
return new CIDR(source);
}
@Override
public CIDR[] newArray(int size) {
return new CIDR[size];
}
};
}
================================================
FILE: app/src/main/java/com/genymobile/gnirehtet/Forwarder.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet;
import android.net.VpnService;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Forwarder {
private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(3);
private static final String TAG = Forwarder.class.getSimpleName();
private static final int BUFSIZE = 0x10000;
private static final byte[] DUMMY_ADDRESS = {42, 42, 42, 42};
private static final int DUMMY_PORT = 4242;
private final FileDescriptor vpnFileDescriptor;
private final PersistentRelayTunnel tunnel;
private Future<?> deviceToTunnelFuture;
private Future<?> tunnelToDeviceFuture;
public Forwarder(VpnService vpnService, FileDescriptor vpnFileDescriptor, RelayTunnelListener listener) {
this.vpnFileDescriptor = vpnFileDescriptor;
tunnel = new PersistentRelayTunnel(vpnService, listener);
}
public void forward() {
deviceToTunnelFuture = EXECUTOR_SERVICE.submit(new Runnable() {
@Override
public void run() {
try {
forwardDeviceToTunnel(tunnel);
} catch (InterruptedIOException e) {
Log.d(TAG, "Device to tunnel interrupted");
} catch (IOException e) {
Log.e(TAG, "Device to tunnel exception", e);
}
}
});
tunnelToDeviceFuture = EXECUTOR_SERVICE.submit(new Runnable() {
@Override
public void run() {
try {
forwardTunnelToDevice(tunnel);
} catch (InterruptedIOException e) {
Log.d(TAG, "Device to tunnel interrupted");
} catch (IOException e) {
Log.e(TAG, "Tunnel to device exception", e);
}
}
});
}
public void stop() {
tunnel.close();
tunnelToDeviceFuture.cancel(true);
deviceToTunnelFuture.cancel(true);
wakeUpReadWorkaround();
}
@SuppressWarnings("checkstyle:MagicNumber")
private void forwardDeviceToTunnel(Tunnel tunnel) throws IOException {
Log.d(TAG, "Device to tunnel forwarding started");
FileInputStream vpnInput = new FileInputStream(vpnFileDescriptor);
byte[] buffer = new byte[BUFSIZE];
while (true) {
// blocking read
int r = vpnInput.read(buffer);
if (r == -1) {
Log.d(TAG, "VPN closed");
break;
}
if (r > 0) {
int version = buffer[0] >> 4;
if (version == 4) {
// blocking send
tunnel.send(buffer, r);
} else {
// see <https://github.com/Genymobile/gnirehtet/issues/69>
Log.w(TAG, "Unexpected packet IP version: " + version);
}
} else {
Log.d(TAG, "Empty read");
}
}
Log.d(TAG, "Device to tunnel forwarding stopped");
}
private void forwardTunnelToDevice(Tunnel tunnel) throws IOException {
Log.d(TAG, "Tunnel to device forwarding started");
FileOutputStream vpnOutput = new FileOutputStream(vpnFileDescriptor);
IPPacketOutputStream packetOutputStream = new IPPacketOutputStream(vpnOutput);
byte[] buffer = new byte[BUFSIZE];
while (true) {
// blocking receive
int w = tunnel.receive(buffer);
if (w == -1) {
Log.d(TAG, "Tunnel closed");
break;
}
if (w > 0) {
// blocking write
packetOutputStream.write(buffer, 0, w);
} else {
Log.d(TAG, "Empty write");
}
}
Log.d(TAG, "Tunnel to device forwarding stopped");
}
/**
* Neither vpnInterface.close() nor vpnInputStream.close() wake up a blocking
* vpnInputStream.read().
* <p>
* Therefore, we need to make Android send a packet to the VPN interface (here by sending a UDP
* packet), so that any blocking read will be woken up.
* <p>
* Since the tunnel is closed at this point, it will never reach the network.
*/
private void wakeUpReadWorkaround() {
// network actions may not be called from the main thread
EXECUTOR_SERVICE.execute(new Runnable() {
@Override
public void run() {
try {
DatagramSocket socket = new DatagramSocket();
InetAddress dummyAddr = InetAddress.getByAddress(DUMMY_ADDRESS);
DatagramPacket packet = new DatagramPacket(new byte[0], 0, dummyAddr, DUMMY_PORT);
socket.send(packet);
} catch (IOException e) {
// ignore
}
}
});
}
}
================================================
FILE: app/src/main/java/com/genymobile/gnirehtet/GnirehtetActivity.java
================================================
package com.genymobile.gnirehtet;
import android.app.Activity;
import android.content.Intent;
import android.net.VpnService;
import android.os.Bundle;
import android.util.Log;
/**
* This (invisible) activity receives the {@link #ACTION_GNIREHTET_START START} and
* {@link #ACTION_GNIREHTET_STOP} actions from the command line.
* <p>
* Recent versions of Android refuse to directly start a {@link android.app.Service Service} or a
* {@link android.content.BroadcastReceiver BroadcastReceiver}, so actions are always managed by
* this activity.
*/
public class GnirehtetActivity extends Activity {
private static final String TAG = GnirehtetActivity.class.getSimpleName();
public static final String ACTION_GNIREHTET_START = "com.genymobile.gnirehtet.START";
public static final String ACTION_GNIREHTET_STOP = "com.genymobile.gnirehtet.STOP";
public static final String EXTRA_DNS_SERVERS = "dnsServers";
public static final String EXTRA_ROUTES = "routes";
private static final int VPN_REQUEST_CODE = 0;
private VpnConfiguration requestedConfig;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handleIntent(getIntent());
}
private void handleIntent(Intent intent) {
String action = intent.getAction();
Log.d(TAG, "Received request " + action);
boolean finish = true;
if (ACTION_GNIREHTET_START.equals(action)) {
VpnConfiguration config = createConfig(intent);
finish = startGnirehtet(config);
} else if (ACTION_GNIREHTET_STOP.equals(action)) {
stopGnirehtet();
}
if (finish) {
finish();
}
}
private static VpnConfiguration createConfig(Intent intent) {
String[] dnsServers = intent.getStringArrayExtra(EXTRA_DNS_SERVERS);
if (dnsServers == null) {
dnsServers = new String[0];
}
String[] routes = intent.getStringArrayExtra(EXTRA_ROUTES);
if (routes == null) {
routes = new String[0];
}
return new VpnConfiguration(Net.toInetAddresses(dnsServers), Net.toCIDRs(routes));
}
private boolean startGnirehtet(VpnConfiguration config) {
Intent vpnIntent = VpnService.prepare(this);
if (vpnIntent == null) {
Log.d(TAG, "VPN was already authorized");
// we got the permission, start the service now
GnirehtetService.start(this, config);
return true;
}
Log.w(TAG, "VPN requires the authorization from the user, requesting...");
requestAuthorization(vpnIntent, config);
return false; // do not finish now
}
private void stopGnirehtet() {
GnirehtetService.stop(this);
}
private void requestAuthorization(Intent vpnIntent, VpnConfiguration config) {
this.requestedConfig = config;
startActivityForResult(vpnIntent, VPN_REQUEST_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == VPN_REQUEST_CODE && resultCode == RESULT_OK) {
GnirehtetService.start(this, requestedConfig);
}
requestedConfig = null;
finish();
}
}
================================================
FILE: app/src/main/java/com/genymobile/gnirehtet/GnirehtetService.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.VpnService;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import java.io.IOException;
import java.net.InetAddress;
import java.util.List;
public class GnirehtetService extends VpnService {
public static final boolean VERBOSE = false;
private static final String ACTION_START_VPN = "com.genymobile.gnirehtet.START_VPN";
private static final String ACTION_CLOSE_VPN = "com.genymobile.gnirehtet.CLOSE_VPN";
private static final String EXTRA_VPN_CONFIGURATION = "vpnConfiguration";
private static final String TAG = GnirehtetService.class.getSimpleName();
private static final InetAddress VPN_ADDRESS = Net.toInetAddress(new byte[] {10, 0, 0, 2});
// magic value: higher (like 0x8000 or 0xffff) or lower (like 1500) values show poorer performances
private static final int MTU = 0x4000;
private final Notifier notifier = new Notifier(this);
private final Handler handler = new RelayTunnelConnectionStateHandler(this);
private ParcelFileDescriptor vpnInterface = null;
private Forwarder forwarder;
public static void start(Context context, VpnConfiguration config) {
Intent intent = new Intent(context, GnirehtetService.class);
intent.setAction(ACTION_START_VPN);
intent.putExtra(GnirehtetService.EXTRA_VPN_CONFIGURATION, config);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
}
public static void stop(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(createStopIntent(context));
} else {
context.startService(createStopIntent(context));
}
}
static Intent createStopIntent(Context context) {
Intent intent = new Intent(context, GnirehtetService.class);
intent.setAction(ACTION_CLOSE_VPN);
return intent;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String action = intent.getAction();
Log.d(TAG, "Received request " + action);
if (ACTION_START_VPN.equals(action)) {
if (isRunning()) {
Log.d(TAG, "VPN already running, ignore START request");
} else {
VpnConfiguration config = intent.getParcelableExtra(EXTRA_VPN_CONFIGURATION);
if (config == null) {
config = new VpnConfiguration();
}
startVpn(config);
}
} else if (ACTION_CLOSE_VPN.equals(action)) {
close();
}
return START_NOT_STICKY;
}
private boolean isRunning() {
return vpnInterface != null;
}
private void startVpn(VpnConfiguration config) {
notifier.start();
if (setupVpn(config)) {
startForwarding();
}
}
@SuppressWarnings("checkstyle:MagicNumber")
private boolean setupVpn(VpnConfiguration config) {
Builder builder = new Builder();
builder.addAddress(VPN_ADDRESS, 32);
builder.setSession(getString(R.string.app_name));
CIDR[] routes = config.getRoutes();
if (routes.length == 0) {
// no routes defined, redirect the whole network traffic
builder.addRoute("0.0.0.0", 0);
} else {
for (CIDR route : routes) {
builder.addRoute(route.getAddress(), route.getPrefixLength());
}
}
InetAddress[] dnsServers = config.getDnsServers();
if (dnsServers.length == 0) {
// no DNS server defined, use Google DNS
builder.addDnsServer("8.8.8.8");
} else {
for (InetAddress dnsServer : dnsServers) {
builder.addDnsServer(dnsServer);
}
}
// non-blocking by default, but FileChannel is not selectable, that's stupid!
// so switch to synchronous I/O to avoid polling
builder.setBlocking(true);
builder.setMtu(MTU);
vpnInterface = builder.establish();
if (vpnInterface == null) {
Log.w(TAG, "VPN starting failed, please retry");
// establish() may return null if the application is not prepared or is revoked
return false;
}
setAsUndernlyingNetwork();
return true;
}
@SuppressWarnings("checkstyle:MagicNumber")
private void setAsUndernlyingNetwork() {
if (Build.VERSION.SDK_INT >= 22) {
Network vpnNetwork = findVpnNetwork();
if (vpnNetwork != null) {
// so that applications knows that network is available
setUnderlyingNetworks(new Network[] {vpnNetwork});
}
} else {
Log.w(TAG, "Cannot set underlying network, API version " + Build.VERSION.SDK_INT + " < 22");
}
}
private Network findVpnNetwork() {
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
Network[] networks = cm.getAllNetworks();
for (Network network : networks) {
LinkProperties linkProperties = cm.getLinkProperties(network);
List<LinkAddress> addresses = linkProperties.getLinkAddresses();
for (LinkAddress addr : addresses) {
if (addr.getAddress().equals(VPN_ADDRESS)) {
return network;
}
}
}
return null;
}
private void startForwarding() {
forwarder = new Forwarder(this, vpnInterface.getFileDescriptor(), new RelayTunnelListener(handler));
forwarder.forward();
}
private void close() {
if (!isRunning()) {
// already closed
return;
}
notifier.stop();
try {
forwarder.stop();
forwarder = null;
vpnInterface.close();
vpnInterface = null;
} catch (IOException e) {
Log.w(TAG, "Cannot close VPN file descriptor", e);
}
}
private static final class RelayTunnelConnectionStateHandler extends Handler {
private final GnirehtetService vpnService;
private RelayTunnelConnectionStateHandler(GnirehtetService vpnService) {
this.vpnService = vpnService;
}
@Override
public void handleMessage(Message message) {
if (!vpnService.isRunning()) {
// if the VPN is not running anymore, ignore obsolete events
return;
}
switch (message.what) {
case RelayTunnelListener.MSG_RELAY_TUNNEL_CONNECTED:
Log.d(TAG, "Relay tunnel connected");
vpnService.notifier.setFailure(false);
break;
case RelayTunnelListener.MSG_RELAY_TUNNEL_DISCONNECTED:
Log.d(TAG, "Relay tunnel disconnected");
vpnService.notifier.setFailure(true);
break;
default:
}
}
}
}
================================================
FILE: app/src/main/java/com/genymobile/gnirehtet/IPPacketOutputStream.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet;
import android.util.Log;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
* Wrapper for writing one IP packet at a time to an {@link OutputStream}.
*/
@SuppressWarnings("checkstyle:MagicNumber")
public class IPPacketOutputStream extends OutputStream {
private static final String TAG = IPPacketOutputStream.class.getSimpleName();
private static final int MAX_IP_PACKET_LENGTH = 1 << 16; // packet length is stored on 16 bits
private final OutputStream target;
// must always accept 1 full packet + any partial packet
private final ByteBuffer buffer = ByteBuffer.allocate(2 * MAX_IP_PACKET_LENGTH);
public IPPacketOutputStream(OutputStream target) {
this.target = target;
}
@Override
public void close() throws IOException {
target.close();
}
@Override
public void flush() throws IOException {
target.flush();
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (len > MAX_IP_PACKET_LENGTH) {
throw new IOException("IPPacketOutputStream does not support writing more than one packet at a time");
}
// by design, the buffer must always have enough space for one packet
if (BuildConfig.DEBUG && len > buffer.remaining()) {
Log.e(TAG, len + " must be <= than " + buffer.remaining());
Log.e(TAG, buffer.toString());
throw new AssertionError("Buffer is unexpectedly full");
}
buffer.put(b, off, len);
buffer.flip();
sink();
buffer.compact();
}
@Override
public void write(int b) throws IOException {
if (!buffer.hasRemaining()) {
throw new IOException("IPPacketOutputStream buffer is full");
}
buffer.put((byte) b);
buffer.flip();
sink();
buffer.compact();
}
private void sink() throws IOException {
// sink all packets
while (sinkPacket()) {
// continue
}
}
private boolean sinkPacket() throws IOException {
int version = readPacketVersion(buffer);
if (version == -1) {
// no packet at all
return false;
}
if (version != 4) {
Log.e(TAG, "Unsupported packet received, IP version is:" + version);
Log.d(TAG, "Clearing buffer");
buffer.clear();
return false;
}
int packetLength = readPacketLength(buffer);
if (packetLength == -1 || packetLength > buffer.remaining()) {
// no packet
return false;
}
target.write(buffer.array(), buffer.arrayOffset() + buffer.position(), packetLength);
buffer.position(buffer.position() + packetLength);
return true;
}
/**
* Read the packet IP version, assuming that an IP packets is stored at absolute position 0.
*
* @param buffer the buffer
* @return the IP version, or {@code -1} if not available
*/
public static int readPacketVersion(ByteBuffer buffer) {
if (!buffer.hasRemaining()) {
// buffer is empty
return -1;
}
// version is stored in the 4 first bits
byte versionAndIHL = buffer.get(buffer.position());
return (versionAndIHL & 0xf0) >> 4;
}
/**
* Read the packet length, assuming thatan IP packet is stored at absolute position 0.
*
* @param buffer the buffer
* @return the packet length, or {@code -1} if not available
*/
public static int readPacketLength(ByteBuffer buffer) {
if (buffer.limit() < buffer.position() + 4) {
// buffer does not even contains the length field
return -1;
}
// packet length is 16 bits starting at offset 2
return Binary.unsigned(buffer.getShort(buffer.position() + 2));
}
}
================================================
FILE: app/src/main/java/com/genymobile/gnirehtet/InvalidCIDRException.java
================================================
/*
* Copyright (C) 2018 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet;
public class InvalidCIDRException extends Exception {
private String cidr;
private static String createMessage(String cidr) {
return "Invalid CIDR:" + cidr;
}
public InvalidCIDRException(String cidr, Throwable cause) {
super(createMessage(cidr), cause);
this.cidr = cidr;
}
public InvalidCIDRException(String cidr) {
super(createMessage(cidr));
this.cidr = cidr;
}
public String getCIDR() {
return cidr;
}
}
================================================
FILE: app/src/main/java/com/genymobile/gnirehtet/Net.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
public final class Net {
private Net() {
// not instantiable
}
public static InetAddress[] toInetAddresses(String... addresses) {
InetAddress[] result = new InetAddress[addresses.length];
for (int i = 0; i < result.length; ++i) {
result[i] = toInetAddress(addresses[i]);
}
return result;
}
public static InetAddress toInetAddress(String address) {
try {
return InetAddress.getByName(address);
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e);
}
}
public static InetAddress toInetAddress(byte[] raw) {
try {
return InetAddress.getByAddress(raw);
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e);
}
}
public static CIDR toCIDR(String cidr) {
try {
return CIDR.parse(cidr);
} catch (InvalidCIDRException e) {
throw new IllegalArgumentException(e);
}
}
public static CIDR[] toCIDRs(String... cidrs) {
CIDR[] result = new CIDR[cidrs.length];
for (int i = 0; i < result.length; ++i) {
result[i] = toCIDR(cidrs[i]);
}
return result;
}
@SuppressWarnings("checkstyle:MagicNumber")
public static Inet4Address getLocalhostIPv4() {
byte[] localhost = {127, 0, 0, 1};
return (Inet4Address) toInetAddress(localhost);
}
}
================================================
FILE: app/src/main/java/com/genymobile/gnirehtet/Notifier.java
================================================
package com.genymobile.gnirehtet;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
/**
* Manage the notification necessary for the foreground service (mandatory since Android O).
*/
public class Notifier {
private static final int NOTIFICATION_ID = 42;
private static final String CHANNEL_ID = "Gnirehtet";
private final Service context;
private boolean failure;
public Notifier(Service context) {
this.context = context;
}
private Notification createNotification(boolean failure) {
Notification.Builder notificationBuilder = createNotificationBuilder();
notificationBuilder.setContentTitle(context.getString(R.string.app_name));
if (failure) {
notificationBuilder.setContentText(context.getString(R.string.relay_disconnected));
notificationBuilder.setSmallIcon(R.drawable.ic_report_problem_24dp);
} else {
notificationBuilder.setContentText(context.getString(R.string.relay_connected));
notificationBuilder.setSmallIcon(R.drawable.ic_usb_24dp);
}
notificationBuilder.addAction(createStopAction());
return notificationBuilder.build();
}
@SuppressWarnings("deprecation")
private Notification.Builder createNotificationBuilder() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return new Notification.Builder(context, CHANNEL_ID);
}
return new Notification.Builder(context);
}
@TargetApi(26)
private void createNotificationChannel() {
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, context.getString(R.string.app_name), NotificationManager
.IMPORTANCE_DEFAULT);
getNotificationManager().createNotificationChannel(channel);
}
@TargetApi(26)
private void deleteNotificationChannel() {
getNotificationManager().deleteNotificationChannel(CHANNEL_ID);
}
public void start() {
failure = false; // reset failure flag
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel();
}
context.startForeground(NOTIFICATION_ID, createNotification(false));
}
public void stop() {
context.stopForeground(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
deleteNotificationChannel();
}
}
public void setFailure(boolean failure) {
if (this.failure != failure) {
this.failure = failure;
Notification notification = createNotification(failure);
getNotificationManager().notify(NOTIFICATION_ID, notification);
}
}
private Notification.Action createStopAction() {
Intent stopIntent = GnirehtetService.createStopIntent(context);
PendingIntent stopPendingIntent = PendingIntent.getService(context, 0, stopIntent, PendingIntent.FLAG_ONE_SHOT);
// the non-deprecated constructor is not available in API 21
@SuppressWarnings("deprecation")
Notification.Action.Builder actionBuilder = new Notification.Action.Builder(R.drawable.ic_close_24dp, context.getString(R.string.stop_vpn),
stopPendingIntent);
return actionBuilder.build();
}
private NotificationManager getNotificationManager() {
return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
}
}
================================================
FILE: app/src/main/java/com/genymobile/gnirehtet/PersistentRelayTunnel.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet;
import android.net.VpnService;
import android.util.Log;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Expose a {@link Tunnel} that automatically handles {@link RelayTunnel} reconnections.
*/
public class PersistentRelayTunnel implements Tunnel {
private static final String TAG = PersistentRelayTunnel.class.getSimpleName();
private final RelayTunnelProvider provider;
private final AtomicBoolean stopped = new AtomicBoolean();
public PersistentRelayTunnel(VpnService vpnService, RelayTunnelListener listener) {
provider = new RelayTunnelProvider(vpnService, listener);
}
@Override
public void send(byte[] packet, int len) throws IOException {
while (!stopped.get()) {
Tunnel tunnel = null;
try {
tunnel = provider.getCurrentTunnel();
tunnel.send(packet, len);
return;
} catch (IOException | InterruptedException e) {
Log.e(TAG, "Cannot send to tunnel", e);
if (tunnel != null) {
provider.invalidateTunnel(tunnel);
}
}
}
throw new InterruptedIOException("Persistent tunnel stopped");
}
@Override
public int receive(byte[] packet) throws IOException {
while (!stopped.get()) {
Tunnel tunnel = null;
try {
tunnel = provider.getCurrentTunnel();
int r = tunnel.receive(packet);
if (r == -1) {
Log.d(TAG, "Tunnel read EOF");
provider.invalidateTunnel(tunnel);
continue;
}
return r;
} catch (IOException | InterruptedException e) {
Log.e(TAG, "Cannot receive from tunnel", e);
if (tunnel != null) {
provider.invalidateTunnel(tunnel);
}
}
}
throw new InterruptedIOException("Persistent tunnel stopped");
}
@Override
public void close() {
stopped.set(true);
provider.invalidateTunnel();
}
}
================================================
FILE: app/src/main/java/com/genymobile/gnirehtet/RelayTunnel.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.net.VpnService;
import android.util.Log;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
public final class RelayTunnel implements Tunnel {
private static final String TAG = RelayTunnel.class.getSimpleName();
private static final String LOCAL_ABSTRACT_NAME = "gnirehtet";
private final LocalSocket localSocket = new LocalSocket();
private RelayTunnel() {
// exposed through open() static method
}
@SuppressWarnings("unused")
public static RelayTunnel open(VpnService vpnService) throws IOException {
Log.d(TAG, "Opening a new relay tunnel...");
// since we use a local socket, we don't need to protect the socket from the vpnService anymore
// but this is an implementation detail, so keep the method signature
return new RelayTunnel();
}
public void connect() throws IOException {
localSocket.connect(new LocalSocketAddress(LOCAL_ABSTRACT_NAME));
readClientId(localSocket.getInputStream());
}
/**
* The relay server is accessible through an "adb reverse" port redirection.
* <p>
* If the port redirection is enabled but the relay server is not started, then the call to
* channel.connect() will succeed, but the first read() will return -1.
* <p>
* As a consequence, the connection state of the relay server would be invalid temporarily (we
* would switch to CONNECTED state then switch back to DISCONNECTED).
* <p>
* To avoid this problem, we must actually read from the server, so that an error occurs
* immediately if the relay server is not accessible.
* <p>
* Therefore, the relay server immediately sends the client id: consume it and log it.
*
* @param inputStream the input stream to receive data from the relay server
* @throws IOException if an I/O error occurs
*/
private static void readClientId(InputStream inputStream) throws IOException {
Log.d(TAG, "Requesting client id");
int clientId = new DataInputStream(inputStream).readInt();
Log.d(TAG, "Connected to the relay server as #" + Binary.unsigned(clientId));
}
@Override
public void send(byte[] packet, int len) throws IOException {
if (GnirehtetService.VERBOSE) {
Log.v(TAG, "Sending packet: " + Binary.buildPacketString(packet, len));
}
localSocket.getOutputStream().write(packet, 0, len);
}
@Override
public int receive(byte[] packet) throws IOException {
int r = localSocket.getInputStream().read(packet);
if (GnirehtetService.VERBOSE) {
Log.v(TAG, "Receiving packet: " + Binary.buildPacketString(packet, r));
}
return r;
}
@Override
public void close() {
try {
if (localSocket.getFileDescriptor() != null) {
// close the streams to interrupt pending read and writes
localSocket.shutdownInput();
localSocket.shutdownOutput();
}
localSocket.close();
} catch (IOException e) {
// what could we do?
throw new RuntimeException(e);
}
}
}
================================================
FILE: app/src/main/java/com/genymobile/gnirehtet/RelayTunnelListener.java
================================================
package com.genymobile.gnirehtet;
import android.os.Handler;
/**
* Convenient wrapper to dispatch events to the given {@link Handler}.
*/
public class RelayTunnelListener {
static final int MSG_RELAY_TUNNEL_CONNECTED = 0;
static final int MSG_RELAY_TUNNEL_DISCONNECTED = 1;
private final Handler handler;
public RelayTunnelListener(Handler handler) {
this.handler = handler;
}
public void notifyRelayTunnelConnected() {
handler.sendEmptyMessage(MSG_RELAY_TUNNEL_CONNECTED);
}
public void notifyRelayTunnelDisconnected() {
handler.sendEmptyMessage(MSG_RELAY_TUNNEL_DISCONNECTED);
}
}
================================================
FILE: app/src/main/java/com/genymobile/gnirehtet/RelayTunnelProvider.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet;
import android.net.VpnService;
import java.io.IOException;
/**
* Provide a valid {@link RelayTunnel}, creating a new one if necessary.
*/
public class RelayTunnelProvider {
private static final int DELAY_BETWEEN_ATTEMPTS_MS = 5000;
private final Object getCurrentTunnelLock = new Object(); // protects getCurrentTunnel()
private final VpnService vpnService;
private final RelayTunnelListener listener;
private RelayTunnel tunnel; // protected both by "this" and "getCurrentTunnelLock"
private boolean first = true; // protected by "getCurrentTunnelLock"
private long lastFailureTimestamp; // protected by "this"
public RelayTunnelProvider(VpnService vpnService, RelayTunnelListener listener) {
this.vpnService = vpnService;
this.listener = listener;
}
public RelayTunnel getCurrentTunnel() throws IOException, InterruptedException {
/*
* To make sure that both the sending and receiving threads use the same tunnel, we must
* guarantee that this method may not be called several times concurrently.
*
* However, since it executes potentially long-running blocking calls, we still want to be
* able to call invalidateTunnel() concurrently, which requires to protect some fields.
*
* Therefore, use one mutex ("getCurrentTunnelLock") to avoid concurrent calls to
* getCurrentTunnel(), and another one ("this") to protect fields shared with
* invalidateTunnel().
*/
synchronized (getCurrentTunnelLock) {
synchronized (this) {
if (tunnel != null) {
return tunnel;
}
waitUntilNextAttemptSlot();
// "tunnel" has not changed during waiting (only getCurrentTunnel() may write it)
tunnel = RelayTunnel.open(vpnService);
}
// the first connection must either notify "connected" or "disconnected"
boolean notifyDisconnectedOnError = first;
first = false;
connectTunnel(notifyDisconnectedOnError);
}
return tunnel;
}
private void connectTunnel(boolean notifyDisconnectedOnError) throws IOException {
try {
tunnel.connect();
notifyConnected();
} catch (IOException e) {
touchFailure();
if (notifyDisconnectedOnError) {
notifyDisconnected();
}
throw e;
}
}
public synchronized void invalidateTunnel() {
if (tunnel != null) {
touchFailure();
tunnel.close();
tunnel = null;
notifyDisconnected();
}
}
/**
* Call {@link #invalidateTunnel()} only if {@code tunnelToInvalidate} is the current tunnel (or
* is {@code null}).
*
* @param tunnelToInvalidate the tunnel to invalidate
*/
public synchronized void invalidateTunnel(Tunnel tunnelToInvalidate) {
if (tunnel == tunnelToInvalidate || tunnelToInvalidate == null) {
invalidateTunnel();
}
}
private synchronized void touchFailure() {
lastFailureTimestamp = System.currentTimeMillis();
}
private void waitUntilNextAttemptSlot() throws InterruptedException {
if (first) {
// do not wait on first attempt
return;
}
long delay = lastFailureTimestamp + DELAY_BETWEEN_ATTEMPTS_MS - System.currentTimeMillis();
while (delay > 0) {
wait(delay);
delay = lastFailureTimestamp + DELAY_BETWEEN_ATTEMPTS_MS - System.currentTimeMillis();
}
}
private void notifyConnected() {
if (listener != null) {
listener.notifyRelayTunnelConnected();
}
}
private void notifyDisconnected() {
if (listener != null) {
listener.notifyRelayTunnelDisconnected();
}
}
}
================================================
FILE: app/src/main/java/com/genymobile/gnirehtet/Tunnel.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet;
import java.io.IOException;
public interface Tunnel {
// blocking
void send(byte[] packet, int len) throws IOException;
// blocking
int receive(byte[] packet) throws IOException;
// blocking
void close();
}
================================================
FILE: app/src/main/java/com/genymobile/gnirehtet/VpnConfiguration.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet;
import android.os.Parcel;
import android.os.Parcelable;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class VpnConfiguration implements Parcelable {
private final InetAddress[] dnsServers;
private final CIDR[] routes;
public VpnConfiguration() {
this.dnsServers = new InetAddress[0];
this.routes = new CIDR[0];
}
public VpnConfiguration(InetAddress[] dnsServers, CIDR[] routes) {
this.dnsServers = dnsServers;
this.routes = routes;
}
private VpnConfiguration(Parcel source) {
int dnsCount = source.readInt();
dnsServers = new InetAddress[dnsCount];
try {
for (int i = 0; i < dnsCount; ++i) {
dnsServers[i] = InetAddress.getByAddress(source.createByteArray());
}
} catch (UnknownHostException e) {
throw new AssertionError("Invalid address", e);
}
routes = source.createTypedArray(CIDR.CREATOR);
}
public InetAddress[] getDnsServers() {
return dnsServers;
}
public CIDR[] getRoutes() {
return routes;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(dnsServers.length);
for (InetAddress addr : dnsServers) {
dest.writeByteArray(addr.getAddress());
}
dest.writeTypedArray(routes, 0);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<VpnConfiguration> CREATOR = new Creator<VpnConfiguration>() {
@Override
public VpnConfiguration createFromParcel(Parcel source) {
return new VpnConfiguration(source);
}
@Override
public VpnConfiguration[] newArray(int size) {
return new VpnConfiguration[size];
}
};
}
================================================
FILE: app/src/main/res/drawable/ic_close_24dp.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_report_problem_24dp.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_usb_24dp.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M15,7v4h1v2h-3V5h2l-3,-4 -3,4h2v8H8v-2.07c0.7,-0.37 1.2,-1.08 1.2,-1.93 0,-1.21 -0.99,-2.2 -2.2,-2.2 -1.21,0 -2.2,0.99 -2.2,2.2 0,0.85 0.5,1.56 1.2,1.93V13c0,1.11 0.89,2 2,2h3v3.05c-0.71,0.37 -1.2,1.1 -1.2,1.95 0,1.22 0.99,2.2 2.2,2.2 1.21,0 2.2,-0.98 2.2,-2.2 0,-0.85 -0.49,-1.58 -1.2,-1.95V15h3c1.11,0 2,-0.89 2,-2v-2h1V7h-4z"/>
</vector>
================================================
FILE: app/src/main/res/values/strings.xml
================================================
<resources>
<string name="app_name" translatable="false">Gnirehtet</string>
<string name="relay_connected">Reverse tethering enabled</string>
<string name="relay_disconnected">Disconnected from the relay server</string>
<string name="stop_vpn">Stop Gnirehtet</string>
</resources>
================================================
FILE: app/src/main/res/values/styles.xml
================================================
<resources>
<!-- Base application theme. -->
<style name="AppTheme">
<!-- Customize your theme here. -->
</style>
<style name="Theme.Transparent" parent="android:Theme">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
</resources>
================================================
FILE: app/src/main/res/values-fr/strings.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="relay_connected">Reverse tethering activé</string>
<string name="relay_disconnected">Déconnecté du serveur relais</string>
<string name="stop_vpn">Arrêter Gnirehtet</string>
</resources>
================================================
FILE: app/src/test/java/com/genymobile/gnirehtet/TestIPPacketOutputSteam.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet;
import org.junit.Assert;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
@SuppressWarnings("checkstyle:MagicNumber")
public class TestIPPacketOutputSteam {
private ByteBuffer createMockPacket() {
ByteBuffer buffer = ByteBuffer.allocate(32);
writeMockPacketTo(buffer);
buffer.flip();
return buffer;
}
private void writeMockPacketTo(ByteBuffer buffer) {
buffer.put((byte) ((4 << 4) | 5)); // versionAndIHL
buffer.put((byte) 0); // ToS
buffer.putShort((short) 32); // total length 20 + 8 + 4
buffer.putInt(0); // IdFlagsFragmentOffset
buffer.put((byte) 0); // TTL
buffer.put((byte) 17); // protocol (UDP)
buffer.putShort((short) 0); // checksum
buffer.putInt(0x12345678); // source address
buffer.putInt(0x42424242); // destination address
buffer.putShort((short) 1234); // source port
buffer.putShort((short) 5678); // destination port
buffer.putShort((short) 12); // length
buffer.putShort((short) 0); // checksum
buffer.putInt(0x11223344); // payload
}
@Test
public void testSimplePacket() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
IPPacketOutputStream pos = new IPPacketOutputStream(bos);
byte[] rawPacket = createMockPacket().array();
pos.write(rawPacket, 0, 14);
Assert.assertEquals("Partial packet should not be written", 0, bos.size());
pos.write(rawPacket, 14, 14);
Assert.assertEquals("Partial packet should not be written", 0, bos.size());
pos.write(rawPacket, 28, 4);
Assert.assertEquals("Complete packet should be written", 32, bos.size());
byte[] result = bos.toByteArray();
Assert.assertTrue("Resulting array must be identical", Arrays.equals(rawPacket, result));
}
@Test
public void testSeveralPacketsAtOnce() throws IOException {
class CapturingOutputStream extends ByteArrayOutputStream {
private int packetCount;
@Override
public void write(byte[] b, int off, int len) {
super.write(b, off, len);
++packetCount;
}
}
CapturingOutputStream cos = new CapturingOutputStream();
IPPacketOutputStream pos = new IPPacketOutputStream(cos);
ByteBuffer buffer = ByteBuffer.allocate(3 * 32);
for (int i = 0; i < 3; ++i) {
writeMockPacketTo(buffer);
}
byte[] rawPackets = buffer.array();
pos.write(rawPackets, 0, 70); // 2 packets + 6 bytes
Assert.assertEquals("Exactly 2 packets should have been written", 64, cos.size());
Assert.assertEquals("Packets should be written individually to the target", 2, cos.packetCount);
pos.write(rawPackets, 70, 26);
Assert.assertEquals("Exactly 3 packets should have been written", 96, cos.size());
Assert.assertEquals("Packets should be written individually to the target", 3, cos.packetCount);
}
}
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
ext {
compileSdkVersion = 28
buildToolsVersion = "28.0.3"
}
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
google()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
task debugJava(dependsOn: [':app:assembleDebug', ':relay-java:assembleDebug'])
task releaseJava(dependsOn: [':app:assembleRelease', ':relay-java:assembleRelease'])
task debugRust(dependsOn: [':app:assembleDebug', ':relay-rust:debug'])
task releaseRust(dependsOn: [':app:assembleRelease', ':relay-rust:release'])
task releaseRustWindows(dependsOn: [':app:assembleRelease', 'relay-rust:releaseCrossToWindows'])
task debugAll(dependsOn: ['debugJava', 'debugRust'])
task releaseAll(dependsOn: ['releaseJava', 'releaseRust'])
task checkJava(dependsOn: [':app:check', ':relay-java:check'])
task checkRust(dependsOn: ['app:check', ':relay-rust:check'])
task checkAll(dependsOn: ['checkJava', 'checkRust'])
================================================
FILE: config/android-checkstyle.gradle
================================================
apply plugin: 'checkstyle'
check.dependsOn 'checkstyle'
checkstyle {
toolVersion = '6.19'
}
task checkstyle(type: Checkstyle) {
description = "Check Java style with Checkstyle"
configFile = rootProject.file("config/checkstyle/checkstyle.xml")
source = javaSources()
classpath = files()
ignoreFailures = true
}
def javaSources() {
def files = []
android.sourceSets.each { sourceSet ->
sourceSet.java.each { javaSource ->
javaSource.getSrcDirs().each {
if (it.exists()) {
files.add(it)
}
}
}
}
return files
}
================================================
FILE: config/android-signing.gradle
================================================
if (project.hasProperty("RELEASE_STORE_FILE")) {
android.signingConfigs {
release {
// to be defined in gradle.properties
storeFile file(RELEASE_STORE_FILE)
storePassword RELEASE_STORE_PASSWORD
keyAlias RELEASE_KEY_ALIAS
keyPassword RELEASE_KEY_PASSWORD
}
}
android.buildTypes.release.signingConfig = android.signingConfigs.release
}
================================================
FILE: config/checkstyle/checkstyle.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<!-- This is a checkstyle configuration file. For descriptions of
what the following rules do, please see the checkstyle configuration
page at http://checkstyle.sourceforge.net/config.html -->
<module name="Checker">
<!-- Checks whether files end with a new line. -->
<!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->
<module name="NewlineAtEndOfFile" />
<!-- Checks that property files contain the same keys. -->
<!-- See http://checkstyle.sf.net/config_misc.html#Translation -->
<module name="Translation" />
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sf.net/config_sizes.html -->
<module name="FileLength" />
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<module name="FileTabCharacter" />
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sf.net/config_misc.html -->
<module name="RegexpSingleline">
<property name="format" value="\s+$" />
<property name="minimum" value="0" />
<property name="maximum" value="0" />
<property name="message" value="Line has trailing spaces." />
<property name="severity" value="info" />
</module>
<module name="SuppressWarningsFilter"/>
<module name="TreeWalker">
<!-- Checks for Naming Conventions. -->
<!-- See http://checkstyle.sf.net/config_naming.html -->
<module name="ConstantName" />
<module name="LocalFinalVariableName" />
<module name="LocalVariableName" />
<module name="MemberName" />
<module name="MethodName" />
<module name="PackageName" />
<module name="ParameterName" />
<module name="StaticVariableName" />
<module name="TypeName" />
<module name="SuppressWarningsHolder"/>
<!-- Checks for imports -->
<!-- See http://checkstyle.sf.net/config_import.html -->
<module name="AvoidStarImport">
<property name="allowStaticMemberImports" value="true" />
</module>
<module name="IllegalImport" />
<!-- defaults to sun.* packages -->
<module name="RedundantImport" />
<module name="UnusedImports" />
<module name="CustomImportOrder">
<property name="thirdPartyPackageRegExp" value=".*"/>
<property name="specialImportsRegExp" value="com.genymobile"/>
<property name="separateLineBetweenGroups" value="true"/>
<property name="customImportOrderRules" value="SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE###STANDARD_JAVA_PACKAGE###STATIC"/>
</module>
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sf.net/config_sizes.html -->
<module name="LineLength">
<!-- what is a good max value? -->
<property name="max" value="150" />
<!-- ignore lines like "$File: //depot/... $" -->
<property name="ignorePattern" value="\$File.*\$" />
<property name="severity" value="info" />
</module>
<module name="MethodLength" />
<module name="ParameterNumber">
<property name="ignoreOverriddenMethods" value="true"/>
</module>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<module name="EmptyForIteratorPad" />
<module name="GenericWhitespace" />
<module name="MethodParamPad" />
<module name="NoWhitespaceAfter" />
<module name="NoWhitespaceBefore" />
<module name="OperatorWrap" />
<module name="ParenPad" />
<module name="TypecastParenPad" />
<module name="WhitespaceAfter" />
<module name="WhitespaceAround" />
<!-- Modifier Checks -->
<!-- See http://checkstyle.sf.net/config_modifiers.html -->
<module name="ModifierOrder" />
<module name="RedundantModifier" />
<!-- Checks for blocks. You know, those {}'s -->
<!-- See http://checkstyle.sf.net/config_blocks.html -->
<module name="AvoidNestedBlocks" />
<module name="EmptyBlock">
<property name="option" value="text" />
</module>
<module name="LeftCurly" />
<module name="NeedBraces" />
<module name="RightCurly" />
<!-- Checks for common coding problems -->
<!-- See http://checkstyle.sf.net/config_coding.html -->
<!-- <module name="AvoidInlineConditionals"/> -->
<module name="EmptyStatement" />
<module name="EqualsHashCode" />
<module name="HiddenField">
<property name="tokens" value="VARIABLE_DEF" />
<!-- only check variables not parameters -->
<property name="ignoreConstructorParameter" value="true" />
<property name="ignoreSetter" value="true" />
<property name="severity" value="warning" />
</module>
<module name="IllegalInstantiation" />
<module name="InnerAssignment" />
<module name="MagicNumber">
<property name="severity" value="info" />
<property name="ignoreHashCodeMethod" value="true" />
<property name="ignoreAnnotation" value="true" />
</module>
<module name="MissingSwitchDefault" />
<module name="SimplifyBooleanExpression" />
<module name="SimplifyBooleanReturn" />
<!-- Checks for class design -->
<!-- See http://checkstyle.sf.net/config_design.html -->
<!-- <module name="DesignForExtension"/> -->
<module name="FinalClass" />
<module name="HideUtilityClassConstructor" />
<module name="InterfaceIsType" />
<module name="VisibilityModifier" />
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sf.net/config_misc.html -->
<module name="ArrayTypeStyle" />
<!-- <module name="FinalParameters"/> -->
<module name="TodoComment">
<property name="format" value="TODO" />
<property name="severity" value="info" />
</module>
<module name="UpperEll" />
<module name="FileContentsHolder" />
<!-- Required by comment suppression filters -->
</module>
<module name="SuppressionFilter">
<!--<property name="file" value="team-props/checkstyle/checkstyle-suppressions.xml" />-->
</module>
<!-- Enable suppression comments -->
<module name="SuppressionCommentFilter">
<property name="offCommentFormat" value="CHECKSTYLE IGNORE\s+(\S+)" />
<property name="onCommentFormat" value="CHECKSTYLE END IGNORE\s+(\S+)" />
<property name="checkFormat" value="$1" />
</module>
<module name="SuppressWithNearbyCommentFilter">
<!-- Syntax is "SUPPRESS CHECKSTYLE name" -->
<property name="commentFormat" value="SUPPRESS CHECKSTYLE (\w+)" />
<property name="checkFormat" value="$1" />
<property name="influenceFormat" value="1" />
</module>
</module>
================================================
FILE: config/java-checkstyle.gradle
================================================
apply plugin: 'checkstyle'
checkstyle {
toolVersion = '6.19'
configFile = rootProject.file("config/checkstyle/checkstyle.xml")
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Sat Sep 07 21:43:49 CEST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
================================================
FILE: gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: relay-java/build.gradle
================================================
apply plugin: 'application'
mainClassName = 'com.genymobile.gnirehtet.Main'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
}
jar {
manifest {
attributes(
'Main-Class': mainClassName
)
}
baseName 'gnirehtet'
}
task assembleDebug(dependsOn: 'jar')
task assembleRelease(dependsOn: ['build', 'jar'])
apply from: "$project.rootDir/config/java-checkstyle.gradle"
test {
// to log using System.out.println(…) in tests
testLogging.showStandardStreams = true
}
================================================
FILE: relay-java/scripts/gnirehtet
================================================
#!/bin/bash
java -jar gnirehtet.jar "$@"
================================================
FILE: relay-java/scripts/gnirehtet-run.cmd
================================================
@java -jar gnirehtet.jar run
@pause
================================================
FILE: relay-java/scripts/gnirehtet.cmd
================================================
@java -jar gnirehtet.jar %*
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/AdbMonitor.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet;
import com.genymobile.gnirehtet.relay.Log;
import java.io.EOFException;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class AdbMonitor {
public interface AdbDevicesCallback {
void onNewDeviceConnected(String serial);
}
private static final String TAG = AdbMonitor.class.getSimpleName();
private static final int ADBD_PORT = 5037;
private static final String TRACK_DEVICES_REQUEST = "0012host:track-devices";
private static final int BUFFER_SIZE = 1024;
private static final int LENGTH_FIELD_SIZE = 4;
private static final int OKAY_SIZE = 4;
private static final long RETRY_DELAY_ADB_DAEMON_OK = 1000;
private static final long RETRY_DELAY_ADB_DAEMON_KO = 5000;
private List<String> connectedDevices = new ArrayList<>();
private AdbDevicesCallback callback;
private static final byte[] BUFFER = new byte[BUFFER_SIZE]; // used only locally to avoid allocations, so static is ok
private final ByteBuffer socketBuffer = ByteBuffer.allocate(BUFFER_SIZE);
public AdbMonitor(AdbDevicesCallback callback) {
this.callback = callback;
}
public void monitor() {
while (true) {
try {
trackDevices();
} catch (Exception e) {
Log.e(TAG, "Failed to monitor adb devices", e);
repairAdbDaemon();
}
}
}
private void trackDevices() throws IOException {
SocketChannel socketChannel = SocketChannel.open();
try {
socketChannel.connect(new InetSocketAddress(Inet4Address.getLoopbackAddress(), ADBD_PORT));
trackDevicesOnChannel(socketChannel);
} finally {
socketChannel.close();
}
}
private void trackDevicesOnChannel(ByteChannel channel) throws IOException {
socketBuffer.clear();
writeRequest(channel, TRACK_DEVICES_REQUEST);
// the daemon initially sends "OKAY" if it understands the request
if (!consumeOkay(channel)) {
return;
}
while (true) {
String packet = nextPacket(channel);
handlePacket(packet);
}
}
private static void writeRequest(WritableByteChannel channel, String request) throws IOException {
ByteBuffer requestBuffer = ByteBuffer.wrap(request.getBytes(StandardCharsets.US_ASCII));
channel.write(requestBuffer);
}
private boolean consumeOkay(ReadableByteChannel channel) throws IOException {
while (channel.read(socketBuffer) != -1) {
if (socketBuffer.position() < OKAY_SIZE) {
// not enough data
continue;
}
socketBuffer.flip();
socketBuffer.get(BUFFER, 0, OKAY_SIZE);
socketBuffer.compact();
socketBuffer.flip();
String text = new String(BUFFER, 0, OKAY_SIZE, StandardCharsets.US_ASCII);
return "OKAY".equals(text);
}
return false;
}
private String nextPacket(ReadableByteChannel channel) throws IOException {
String packet;
while ((packet = readPacket(socketBuffer)) == null) {
// need more data
fillBufferFrom(channel);
}
return packet;
}
private void fillBufferFrom(ReadableByteChannel channel) throws IOException {
socketBuffer.compact();
int r;
if (channel.read(socketBuffer) == -1) {
throw new EOFException("ADB daemon closed the track-devices connexion");
}
socketBuffer.flip();
}
static String readPacket(ByteBuffer input) {
if (input.remaining() < LENGTH_FIELD_SIZE) {
return null;
}
// each packet contains 4 bytes representing the String length in hexa, followed by a list of device states, one per line;
// each line contains: the device serial, `\t', the state, '\n'
// for example: "00360123456789abcdef\tdevice\nfedcba9876543210\tunauthorized\n":
// - 0036 indicates that the data is 0x36 (54) bytes length
// - the device with serial 0123456789abcdef is connected
// - the device with serial fedcba9876543210 is unauthorized
input.get(BUFFER, 0, LENGTH_FIELD_SIZE);
int length = parseLength(BUFFER);
if (length > BUFFER.length) {
throw new IllegalArgumentException("Packet size should not be that big: " + length);
}
if (input.remaining() < length) {
// not enough data
input.rewind();
return null;
}
input.get(BUFFER, 0, length);
return new String(BUFFER, 0, length, StandardCharsets.UTF_8);
}
void handlePacket(String packet) {
List<String> currentConnectedDevices = parseConnectedDevices(packet);
for (String serial : currentConnectedDevices) {
if (!connectedDevices.contains(serial)) {
callback.onNewDeviceConnected(serial);
}
}
connectedDevices = currentConnectedDevices;
}
private static List<String> parseConnectedDevices(String packet) {
List<String> list = new ArrayList<>();
for (String line : packet.split("\\n")) {
String[] tokens = line.split("\\s+");
if (tokens.length == 2) {
String state = tokens[1];
if ("device".equals(state)) {
String serial = tokens[0];
list.add(serial);
}
}
}
return list;
}
@SuppressWarnings("checkstyle:MagicNumber")
private static int parseLength(byte[] data) {
if (data.length < LENGTH_FIELD_SIZE) {
throw new IllegalArgumentException("Length field must be at least 4 bytes length");
}
int result = 0;
for (int i = 0; i < LENGTH_FIELD_SIZE; ++i) {
char c = (char) data[i];
result = (result << 4) + Character.digit(c, 0x10);
}
return result;
}
private static void repairAdbDaemon() {
if (startAdbDaemon()) {
sleep(RETRY_DELAY_ADB_DAEMON_OK);
} else {
sleep(RETRY_DELAY_ADB_DAEMON_KO);
}
}
private static boolean startAdbDaemon() {
Log.i(TAG, "Restarting adb deamon");
try {
Process process = new ProcessBuilder("adb", "start-server")
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.redirectError(ProcessBuilder.Redirect.INHERIT).start();
int exitCode = process.waitFor();
if (exitCode != 0) {
Log.e(TAG, "Could not restart adb daemon (exited on error)");
return false;
}
return true;
} catch (InterruptedException | IOException e) {
Log.e(TAG, "Could not restart adb daemon", e);
return false;
}
}
private static void sleep(long delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
// should never happen
}
}
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/CommandLineArguments.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet;
/**
* Simple specific command-line arguments parser.
*/
@SuppressWarnings("checkstyle:MagicNumber")
public class CommandLineArguments {
public static final int PARAM_NONE = 0;
public static final int PARAM_SERIAL = 1;
public static final int PARAM_DNS_SERVER = 1 << 1;
public static final int PARAM_ROUTES = 1 << 2;
public static final int PARAM_PORT = 1 << 3;
public static final int DEFAULT_PORT = 31416;
private int port;
private String serial;
private String dnsServers;
private String routes;
public static CommandLineArguments parse(int acceptedParameters, String... args) {
CommandLineArguments arguments = new CommandLineArguments();
for (int i = 0; i < args.length; ++i) {
String arg = args[i];
if ((acceptedParameters & PARAM_DNS_SERVER) != 0 && "-d".equals(arg)) {
if (arguments.dnsServers != null) {
throw new IllegalArgumentException("DNS servers already set");
}
if (i == args.length - 1) {
throw new IllegalArgumentException("Missing -d parameter");
}
arguments.dnsServers = args[i + 1];
++i; // consume the -d parameter
} else if ((acceptedParameters & PARAM_ROUTES) != 0 && "-r".equals(arg)) {
if (arguments.routes != null) {
throw new IllegalArgumentException("Routes already set");
}
if (i == args.length - 1) {
throw new IllegalArgumentException("Missing -r parameter");
}
arguments.routes = args[i + 1];
++i; // consume the -r parameter
} else if ((acceptedParameters & PARAM_PORT) != 0 && "-p".equals(arg)) {
if (arguments.port != 0) {
throw new IllegalArgumentException("Port already set");
}
if (i == args.length - 1) {
throw new IllegalArgumentException("Missing -p parameter");
}
arguments.port = Integer.parseInt(args[i + 1]);
if (arguments.port <= 0 || arguments.port >= 65536) {
throw new IllegalArgumentException("Invalid port: " + arguments.port);
}
++i;
} else if ((acceptedParameters & PARAM_SERIAL) != 0 && arguments.serial == null) {
arguments.serial = arg;
} else {
throw new IllegalArgumentException("Unexpected argument: \"" + arg + "\"");
}
}
if (arguments.port == 0) {
arguments.port = DEFAULT_PORT;
}
return arguments;
}
public String getSerial() {
return serial;
}
public String getDnsServers() {
return dnsServers;
}
public String getRoutes() {
return routes;
}
public int getPort() {
return port;
}
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/Main.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet;
import com.genymobile.gnirehtet.relay.CommandExecutionException;
import com.genymobile.gnirehtet.relay.Log;
import com.genymobile.gnirehtet.relay.Relay;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class Main {
private static final String TAG = "Gnirehtet";
private static final String NL = System.lineSeparator();
private static final String REQUIRED_APK_VERSION_CODE = "9";
private Main() {
// not instantiable
}
private static String getAdbPath() {
String adb = System.getenv("ADB");
return adb != null ? adb : "adb";
}
private static String getApkPath() {
String apk = System.getenv("GNIREHTET_APK");
return apk != null ? apk : "gnirehtet.apk";
}
enum Command {
INSTALL("install", CommandLineArguments.PARAM_SERIAL) {
@Override
String getDescription() {
return "Install the client on the Android device and exit.\n"
+ "If several devices are connected via adb, then serial must be\n"
+ "specified.";
}
@Override
void execute(CommandLineArguments args) throws Exception {
cmdInstall(args.getSerial());
}
},
UNINSTALL("uninstall", CommandLineArguments.PARAM_SERIAL) {
@Override
String getDescription() {
return "Uninstall the client from the Android device and exit.\n"
+ "If several devices are connected via adb, then serial must be\n"
+ "specified.";
}
@Override
void execute(CommandLineArguments args) throws Exception {
cmdUninstall(args.getSerial());
}
},
REINSTALL("reinstall", CommandLineArguments.PARAM_SERIAL) {
@Override
String getDescription() {
return "Uninstall then install.";
}
@Override
void execute(CommandLineArguments args) throws Exception {
cmdReinstall(args.getSerial());
}
},
RUN("run", CommandLineArguments.PARAM_SERIAL | CommandLineArguments.PARAM_DNS_SERVER | CommandLineArguments.PARAM_ROUTES
| CommandLineArguments.PARAM_PORT) {
@Override
String getDescription() {
return "Enable reverse tethering for exactly one device:\n"
+ " - install the client if necessary;\n"
+ " - start the client;\n"
+ " - start the relay server;\n"
+ " - on Ctrl+C, stop both the relay server and the client.";
}
@Override
void execute(CommandLineArguments args) throws Exception {
cmdRun(args.getSerial(), args.getDnsServers(), args.getRoutes(), args.getPort());
}
},
AUTORUN("autorun", CommandLineArguments.PARAM_DNS_SERVER | CommandLineArguments.PARAM_ROUTES | CommandLineArguments.PARAM_PORT) {
@Override
String getDescription() {
return "Enable reverse tethering for all devices:\n"
+ " - monitor devices and start clients (autostart);\n"
+ " - start the relay server.";
}
@Override
void execute(CommandLineArguments args) throws Exception {
cmdAutorun(args.getDnsServers(), args.getRoutes(), args.getPort());
}
},
START("start", CommandLineArguments.PARAM_SERIAL | CommandLineArguments.PARAM_DNS_SERVER | CommandLineArguments.PARAM_ROUTES
| CommandLineArguments.PARAM_PORT) {
@Override
String getDescription() {
return "Start a client on the Android device and exit.\n"
+ "If several devices are connected via adb, then serial must be\n"
+ "specified.\n"
+ "If -d is given, then make the Android device use the specified\n"
+ "DNS server(s). Otherwise, use 8.8.8.8 (Google public DNS).\n"
+ "If -r is given, then only reverse tether the specified routes.\n"
+ "If -p is given, then make the relay server listen on the specified\n"
+ "port. Otherwise, use port 31416.\n"
+ "Otherwise, use 0.0.0.0/0 (redirect the whole traffic).\n"
+ "If the client is already started, then do nothing, and ignore\n"
+ "the other parameters.\n"
+ "10.0.2.2 is mapped to the host 'localhost'.";
}
@Override
void execute(CommandLineArguments args) throws Exception {
cmdStart(args.getSerial(), args.getDnsServers(), args.getRoutes(), args.getPort());
}
},
AUTOSTART("autostart", CommandLineArguments.PARAM_DNS_SERVER | CommandLineArguments.PARAM_ROUTES | CommandLineArguments.PARAM_PORT) {
@Override
String getDescription() {
return "Listen for device connexions and start a client on every detected\n"
+ "device.\n"
+ "Accept the same parameters as the start command (excluding the\n"
+ "serial, which will be taken from the detected device).";
}
@Override
void execute(CommandLineArguments args) throws Exception {
cmdAutostart(args.getDnsServers(), args.getRoutes(), args.getPort());
}
},
STOP("stop", CommandLineArguments.PARAM_SERIAL) {
@Override
String getDescription() {
return "Stop the client on the Android device and exit.\n"
+ "If several devices are connected via adb, then serial must be\n"
+ "specified.";
}
@Override
void execute(CommandLineArguments args) throws Exception {
cmdStop(args.getSerial());
}
},
RESTART("restart", CommandLineArguments.PARAM_SERIAL | CommandLineArguments.PARAM_DNS_SERVER | CommandLineArguments.PARAM_ROUTES
| CommandLineArguments.PARAM_PORT) {
@Override
String getDescription() {
return "Stop then start.";
}
@Override
void execute(CommandLineArguments args) throws Exception {
cmdRestart(args.getSerial(), args.getDnsServers(), args.getRoutes(), args.getPort());
}
},
TUNNEL("tunnel", CommandLineArguments.PARAM_SERIAL | CommandLineArguments.PARAM_PORT) {
@Override
String getDescription() {
return "Set up the 'adb reverse' tunnel.\n"
+ "If a device is unplugged then plugged back while gnirehtet is\n"
+ "active, resetting the tunnel is sufficient to get the\n"
+ "connection back.";
}
@Override
void execute(CommandLineArguments args) throws Exception {
cmdTunnel(args.getSerial(), args.getPort());
}
},
RELAY("relay", CommandLineArguments.PARAM_PORT) {
@Override
String getDescription() {
return "Start the relay server in the current terminal.";
}
@Override
void execute(CommandLineArguments args) throws Exception {
cmdRelay(args.getPort());
}
};
private String command;
private int acceptedParameters;
Command(String command, int acceptedParameters) {
this.command = command;
this.acceptedParameters = acceptedParameters;
}
abstract String getDescription();
abstract void execute(CommandLineArguments args) throws Exception;
}
private static void cmdInstall(String serial) throws InterruptedException, IOException, CommandExecutionException {
Log.i(TAG, "Installing gnirehtet client...");
execAdb(serial, "install", "-r", getApkPath());
}
private static void cmdUninstall(String serial) throws InterruptedException, IOException, CommandExecutionException {
Log.i(TAG, "Uninstalling gnirehtet client...");
execAdb(serial, "uninstall", "com.genymobile.gnirehtet");
}
private static void cmdReinstall(String serial) throws InterruptedException, IOException, CommandExecutionException {
cmdUninstall(serial);
cmdInstall(serial);
}
private static void cmdRun(String serial, String dnsServers, String routes, int port) throws IOException {
// start in parallel so that the relay server is ready when the client connects
asyncStart(serial, dnsServers, routes, port);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
// executed on Ctrl+C
try {
cmdStop(serial);
} catch (Exception e) {
Log.e(TAG, "Cannot stop client", e);
}
}));
cmdRelay(port);
}
private static void cmdAutorun(final String dnsServers, final String routes, int port) throws IOException {
new Thread(() -> {
try {
cmdAutostart(dnsServers, routes, port);
} catch (Exception e) {
Log.e(TAG, "Cannot auto start clients", e);
}
}).start();
cmdRelay(port);
}
@SuppressWarnings("checkstyle:MagicNumber")
private static void cmdStart(String serial, String dnsServers, String routes, int port) throws InterruptedException, IOException,
CommandExecutionException {
if (mustInstallClient(serial)) {
cmdInstall(serial);
// wait a bit after the app is installed so that intent actions are correctly registered
Thread.sleep(500); // ms
}
Log.i(TAG, "Starting client...");
cmdTunnel(serial, port);
List<String> cmd = new ArrayList<>();
Collections.addAll(cmd, "shell", "am", "start", "-a", "com.genymobile.gnirehtet.START", "-n",
"com.genymobile.gnirehtet/.GnirehtetActivity");
if (dnsServers != null) {
Collections.addAll(cmd, "--esa", "dnsServers", dnsServers);
}
if (routes != null) {
Collections.addAll(cmd, "--esa", "routes", routes);
}
execAdb(serial, cmd);
}
private static void cmdAutostart(final String dnsServers, final String routes, int port) {
AdbMonitor adbMonitor = new AdbMonitor((serial) -> {
asyncStart(serial, dnsServers, routes, port);
});
adbMonitor.monitor();
}
private static void cmdStop(String serial) throws InterruptedException, IOException, CommandExecutionException {
Log.i(TAG, "Stopping client...");
execAdb(serial, "shell", "am", "start", "-a", "com.genymobile.gnirehtet.STOP", "-n",
"com.genymobile.gnirehtet/.GnirehtetActivity");
}
private static void cmdRestart(String serial, String dnsServers, String routes, int port) throws InterruptedException, IOException,
CommandExecutionException {
cmdStop(serial);
cmdStart(serial, dnsServers, routes, port);
}
private static void cmdTunnel(String serial, int port) throws InterruptedException, IOException, CommandExecutionException {
execAdb(serial, "reverse", "localabstract:gnirehtet", "tcp:" + port);
}
private static void cmdRelay(int port) throws IOException {
Log.i(TAG, "Starting relay server on port " + port + "...");
new Relay(port).run();
}
private static void asyncStart(String serial, String dnsServers, String routes, int port) {
new Thread(() -> {
try {
cmdStart(serial, dnsServers, routes, port);
} catch (Exception e) {
Log.e(TAG, "Cannot start client", e);
}
}).start();
}
private static void execAdb(String serial, String... adbArgs) throws InterruptedException, IOException, CommandExecutionException {
execSync(createAdbCommand(serial, adbArgs));
}
private static List<String> createAdbCommand(String serial, String... adbArgs) {
List<String> command = new ArrayList<>();
command.add(getAdbPath());
if (serial != null) {
command.add("-s");
command.add(serial);
}
Collections.addAll(command, adbArgs);
return command;
}
private static void execAdb(String serial, List<String> adbArgList) throws InterruptedException, IOException, CommandExecutionException {
String[] adbArgs = adbArgList.toArray(new String[adbArgList.size()]);
execAdb(serial, adbArgs);
}
private static void execSync(List<String> command) throws InterruptedException, IOException, CommandExecutionException {
Log.d(TAG, "Execute: " + command);
ProcessBuilder processBuilder = new ProcessBuilder(command);
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT).redirectError(ProcessBuilder.Redirect.INHERIT);
Process process = processBuilder.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new CommandExecutionException(command, exitCode);
}
}
private static boolean mustInstallClient(String serial) throws InterruptedException, IOException, CommandExecutionException {
Log.i(TAG, "Checking gnirehtet client...");
List<String> command = createAdbCommand(serial, "shell", "dumpsys", "package", "com.genymobile.gnirehtet");
Log.d(TAG, "Execute: " + command);
Process process = new ProcessBuilder(command).start();
try {
Scanner scanner = new Scanner(process.getInputStream());
// read the versionCode of the installed package
Pattern pattern = Pattern.compile("^ versionCode=(\\p{Digit}+).*");
while (scanner.hasNextLine()) {
Matcher matcher = pattern.matcher(scanner.nextLine());
if (matcher.matches()) {
String installedVersionCode = matcher.group(1);
return !REQUIRED_APK_VERSION_CODE.equals(installedVersionCode);
}
}
} finally {
int exitCode = process.waitFor();
if (exitCode != 0) {
// Overwrite any pending exception, the command just failed
throw new CommandExecutionException(command, exitCode);
}
}
return true;
}
private static void printUsage() {
StringBuilder builder = new StringBuilder("Syntax: gnirehtet (");
Command[] commands = Command.values();
for (int i = 0; i < commands.length; ++i) {
if (i != 0) {
builder.append('|');
}
builder.append(commands[i].command);
}
builder.append(") ...").append(NL);
for (Command command : commands) {
builder.append(NL);
appendCommandUsage(builder, command);
}
System.err.print(builder.toString());
}
private static void appendCommandUsage(StringBuilder builder, Command command) {
builder.append(" gnirehtet ").append(command.command);
if ((command.acceptedParameters & CommandLineArguments.PARAM_SERIAL) != 0) {
builder.append(" [serial]");
}
if ((command.acceptedParameters & CommandLineArguments.PARAM_DNS_SERVER) != 0) {
builder.append(" [-d DNS[,DNS2,...]]");
}
if ((command.acceptedParameters & CommandLineArguments.PARAM_PORT) != 0) {
builder.append(" [-p PORT]");
}
if ((command.acceptedParameters & CommandLineArguments.PARAM_ROUTES) != 0) {
builder.append(" [-r ROUTE[,ROUTE2,...]]");
}
builder.append(NL);
String[] descLines = command.getDescription().split("\n");
for (String descLine : descLines) {
builder.append(" ").append(descLine).append(NL);
}
}
private static void printCommandUsage(Command command) {
StringBuilder builder = new StringBuilder();
appendCommandUsage(builder, command);
System.err.print(builder.toString());
}
public static void main(String... args) throws Exception {
if (args.length == 0) {
printUsage();
return;
}
String cmd = args[0];
for (Command command : Command.values()) {
if (cmd.equals(command.command)) {
// forget args[0] containing the command name
String[] commandArgs = Arrays.copyOfRange(args, 1, args.length);
CommandLineArguments arguments;
try {
arguments = CommandLineArguments.parse(command.acceptedParameters, commandArgs);
} catch (IllegalArgumentException e) {
Log.e(TAG, e.getMessage());
printCommandUsage(command);
return;
}
command.execute(arguments);
return;
}
}
if ("rt".equals(cmd)) {
Log.e(TAG, "The 'rt' command has been renamed to 'run'. Try 'gnirehtet run' instead.");
printCommandUsage(Command.RUN);
} else {
Log.e(TAG, "Unknown command: " + cmd);
printUsage();
}
}
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/AbstractConnection.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet.relay;
import java.net.InetAddress;
import java.net.InetSocketAddress;
public abstract class AbstractConnection implements Connection {
private static final int LOCALHOST_FORWARD = 0x0a000202; // 10.0.2.2 must be forwarded to localhost
private final ConnectionId id;
private final Client client;
protected AbstractConnection(ConnectionId id, Client client) {
this.id = id;
this.client = client;
}
@Override
public ConnectionId getId() {
return id;
}
protected void close() {
disconnect();
client.getRouter().remove(this);
}
protected void consume(PacketSource source) {
client.consume(source);
}
protected boolean sendToClient(IPv4Packet packet) {
return client.sendToClient(packet);
}
private static InetAddress getRewrittenAddress(int ip) {
return ip == LOCALHOST_FORWARD ? InetAddress.getLoopbackAddress() : Net.toInetAddress(ip);
}
/**
* Get destination, rewritten to {@code localhost} if it was {@code 10.0.2.2}.
*
* @return Destination to connect to.
*/
protected InetSocketAddress getRewrittenDestination() {
int destIp = id.getDestinationIp();
int port = id.getDestinationPort();
return new InetSocketAddress(getRewrittenAddress(destIp), port);
}
public void logv(String tag, String message, Throwable e) {
Log.v(tag, id + " " + message);
}
public void logv(String tag, String message) {
logv(tag, message, null);
}
public void logd(String tag, String message, Throwable e) {
Log.d(tag, id + " " + message);
}
public void logd(String tag, String message) {
logd(tag, message, null);
}
public void logi(String tag, String message, Throwable e) {
Log.i(tag, id + " " + message);
}
public void logi(String tag, String message) {
logi(tag, message, null);
}
public void logw(String tag, String message, Throwable e) {
Log.w(tag, id + " " + message);
}
public void logw(String tag, String message) {
logw(tag, message, null);
}
public void loge(String tag, String message, Throwable e) {
Log.e(tag, id + " " + message);
}
public void loge(String tag, String message) {
loge(tag, message, null);
}
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Binary.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet.relay;
import java.nio.ByteBuffer;
@SuppressWarnings("checkstyle:MagicNumber")
public final class Binary {
private static final int MAX_STRING_PACKET_SIZE = 20;
private Binary() {
// not instantiable
}
public static String buildPacketString(byte[] data, int offset, int len) {
int limit = Math.min(MAX_STRING_PACKET_SIZE, len);
StringBuilder builder = new StringBuilder();
builder.append('[').append(len).append(" bytes] ");
for (int i = 0; i < limit; ++i) {
if (i != 0) {
String sep = i % 4 == 0 ? " " : " ";
builder.append(sep);
}
builder.append(String.format("%02X", data[offset + i] & 0xff));
}
if (limit < len) {
builder.append(" ... +").append(len - limit).append(" bytes");
}
return builder.toString();
}
public static String buildPacketString(ByteBuffer buffer) {
return buildPacketString(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
}
public static ByteBuffer copy(ByteBuffer buffer) {
buffer.rewind();
ByteBuffer result = ByteBuffer.allocate(buffer.remaining());
result.put(buffer);
buffer.rewind();
result.flip();
return result;
}
public static ByteBuffer slice(ByteBuffer buffer, int offset, int length) {
// save
int position = buffer.position();
int limit = buffer.limit();
// slice
buffer.limit(offset + length).position(offset);
ByteBuffer result = buffer.slice();
// restore
buffer.limit(limit).position(position);
return result;
}
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Client.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet.relay;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Client {
private static final String TAG = Client.class.getSimpleName();
private static int nextId = 0;
private final int id;
private final SocketChannel clientChannel;
private final SelectionKey selectionKey;
private final CloseListener<Client> closeListener;
private int interests;
private final IPv4PacketBuffer clientToNetwork = new IPv4PacketBuffer();
private final StreamBuffer networkToClient = new StreamBuffer(16 * IPv4Packet.MAX_PACKET_LENGTH);
private final Router router;
private final List<PacketSource> pendingPacketSources = new ArrayList<>();
// store the remaining bytes of "id" to send to the client before relaying any data
private ByteBuffer pendingIdBuffer;
public Client(Selector selector, SocketChannel clientChannel, CloseListener<Client> closeListener) throws ClosedChannelException {
id = nextId++;
this.clientChannel = clientChannel;
router = new Router(this, selector);
pendingIdBuffer = createIntBuffer(id);
SelectionHandler selectionHandler = (selectionKey) -> {
if (selectionKey.isValid() && selectionKey.isWritable()) {
processSend();
}
if (selectionKey.isValid() && selectionKey.isReadable()) {
processReceive();
}
if (selectionKey.isValid()) {
updateInterests();
}
};
// on start, we are interested only in writing (we must first send the client id)
interests = SelectionKey.OP_WRITE;
selectionKey = clientChannel.register(selector, interests, selectionHandler);
this.closeListener = closeListener;
}
private static ByteBuffer createIntBuffer(int value) {
final int intSize = 4;
ByteBuffer buffer = ByteBuffer.allocate(intSize);
buffer.putInt(value);
buffer.flip();
return buffer;
}
public int getId() {
return id;
}
public Router getRouter() {
return router;
}
private void processReceive() {
if (!read()) {
close();
return;
}
pushToNetwork();
}
private void processSend() {
if (mustSendId()) {
if (!sendId()) {
close();
}
return;
}
if (!write()) {
close();
return;
}
processPending();
}
private boolean read() {
try {
return clientToNetwork.readFrom(clientChannel) != -1;
} catch (IOException e) {
Log.e(TAG, "Cannot read", e);
return false;
}
}
private boolean write() {
try {
return networkToClient.writeTo(clientChannel) != -1;
} catch (IOException e) {
Log.e(TAG, "Cannot write", e);
return false;
}
}
private boolean mustSendId() {
return pendingIdBuffer != null && pendingIdBuffer.hasRemaining();
}
private boolean sendId() {
assert mustSendId();
try {
if (clientChannel.write(pendingIdBuffer) == -1) {
Log.w(TAG, "Cannot write client id #" + id + " (EOF)");
return false;
}
if (!pendingIdBuffer.hasRemaining()) {
// we don't need this buffer anymore, release it
Log.d(TAG, "Client id #" + id + " sent to client");
pendingIdBuffer = null;
}
return true;
} catch (IOException e) {
Log.e(TAG, "Cannot write client id #" + id, e);
return false;
}
}
private void pushToNetwork() {
IPv4Packet packet;
while ((packet = clientToNetwork.asIPv4Packet()) != null) {
router.sendToNetwork(packet);
clientToNetwork.next();
}
}
private void close() {
selectionKey.cancel();
try {
clientChannel.close();
} catch (IOException e) {
Log.e(TAG, "Cannot close client connection", e);
}
router.clear();
closeListener.onClosed(this);
}
private void updateInterests() {
int interestOps = SelectionKey.OP_READ; // we always want to read
if (!networkToClient.isEmpty()) {
interestOps |= SelectionKey.OP_WRITE;
}
if (interests != interestOps) {
// interests must be changed
interests = interestOps;
selectionKey.interestOps(interestOps);
}
}
public boolean sendToClient(IPv4Packet packet) {
if (networkToClient.remaining() < packet.getRawLength()) {
Log.w(TAG, "Client buffer full");
return false;
}
networkToClient.readFrom(packet.getRaw());
updateInterests();
return true;
}
public void consume(PacketSource source) {
IPv4Packet packet = source.get();
if (sendToClient(packet)) {
source.next();
return;
}
assert !pendingPacketSources.contains(source);
pendingPacketSources.add(source);
}
private void processPending() {
Iterator<PacketSource> iterator = pendingPacketSources.iterator();
while (iterator.hasNext()) {
PacketSource packetSource = iterator.next();
IPv4Packet packet = packetSource.get();
if (sendToClient(packet)) {
packetSource.next();
Log.d(TAG, "Pending packet sent to client (" + packet.getRawLength() + ")");
iterator.remove();
} else {
Log.w(TAG, "Pending packet not sent to client (" + packet.getRawLength() + "), client buffer full again");
return;
}
}
}
public void cleanExpiredConnections() {
router.cleanExpiredConnections();
}
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/CloseListener.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet.relay;
public interface CloseListener<T> {
void onClosed(T object);
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/CommandExecutionException.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet.relay;
import java.util.List;
public class CommandExecutionException extends Exception {
private List<String> command;
private int exitCode;
public CommandExecutionException(List<String> command, int exitCode) {
super(createMessage(command, exitCode));
this.command = command;
this.exitCode = exitCode;
}
private static String createMessage(List<String> command, int exitCode) {
return "Command " + command + " returned with value " + exitCode;
}
public int getExitCode() {
return exitCode;
}
public List<String> getCommand() {
return command;
}
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Connection.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet.relay;
public interface Connection {
ConnectionId getId();
void sendToNetwork(IPv4Packet packet);
void disconnect();
boolean isExpired();
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/ConnectionId.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet.relay;
public class ConnectionId {
private final IPv4Header.Protocol protocol;
private final int sourceIp;
private final short sourcePort;
private final int destIp;
private final short destPort;
private final String idString;
public ConnectionId(IPv4Header.Protocol protocol, int sourceIp, short sourcePort, int destIp, short destPort) {
this.protocol = protocol;
this.sourceIp = sourceIp;
this.sourcePort = sourcePort;
this.destIp = destIp;
this.destPort = destPort;
// compute the String representation only once
idString = protocol + " " + Net.toString(sourceIp, sourcePort) + " -> " + Net.toString(destIp, destPort);
}
public IPv4Header.Protocol getProtocol() {
return protocol;
}
public int getSourceIp() {
return sourceIp;
}
public int getSourcePort() {
return Short.toUnsignedInt(sourcePort);
}
public int getDestinationIp() {
return destIp;
}
public int getDestinationPort() {
return Short.toUnsignedInt(destPort);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ConnectionId that = (ConnectionId) o;
return sourceIp == that.sourceIp
&& sourcePort == that.sourcePort
&& destIp == that.destIp
&& destPort == that.destPort
&& protocol == that.protocol;
}
@Override
public int hashCode() {
int result = protocol.hashCode();
result = 31 * result + sourceIp;
result = 31 * result + (int) sourcePort;
result = 31 * result + destIp;
result = 31 * result + (int) destPort;
return result;
}
@Override
public String toString() {
return idString;
}
public static ConnectionId from(IPv4Header ipv4Header, TransportHeader transportHeader) {
IPv4Header.Protocol protocol = ipv4Header.getProtocol();
int sourceAddress = ipv4Header.getSource();
short sourcePort = (short) transportHeader.getSourcePort();
int destinationAddress = ipv4Header.getDestination();
short destinationPort = (short) transportHeader.getDestinationPort();
return new ConnectionId(protocol, sourceAddress, sourcePort, destinationAddress, destinationPort);
}
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/DatagramBuffer.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet.relay;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;
/**
* Circular buffer to store datagrams (preserving their boundaries).
* <p>
* <pre>
* circularBufferLength
* |<------------------------->| extra space for storing the last datagram in one block
* +---------------------------+------+
* | | |
* |[D4] [ D1 ][ D2 ][ D3 ] |
* +---------------------------+------+
* ^ ^
* head tail
* </pre>
*/
@SuppressWarnings("checkstyle:MagicNumber")
public class DatagramBuffer {
private static final String TAG = DatagramBuffer.class.getSimpleName();
// every datagram is stored along with a header storing its length, on 16 bits
private static final int HEADER_LENGTH = 2;
private static final int MAX_DATAGRAM_LENGTH = 1 << 16;
private static final int MAX_BLOCK_LENGTH = HEADER_LENGTH + MAX_DATAGRAM_LENGTH;
private final byte[] data;
private final ByteBuffer wrapper;
private int head;
private int tail;
private final int circularBufferLength;
public DatagramBuffer(int capacity) {
data = new byte[capacity + MAX_BLOCK_LENGTH];
wrapper = ByteBuffer.wrap(data);
circularBufferLength = capacity + 1;
}
public boolean isEmpty() {
return head == tail;
}
public boolean hasEnoughSpaceFor(int datagramLength) {
if (head >= tail) {
// there is at least the extra space for storing 1 packet
return true;
}
int remaining = tail - head - 1; // 1 extra byte to distinguish empty vs full
return HEADER_LENGTH + datagramLength <= remaining;
}
public int capacity() {
return circularBufferLength - 1;
}
public boolean writeTo(WritableByteChannel channel) throws IOException {
int length = readLength();
wrapper.limit(tail + length).position(tail);
tail += length;
if (tail >= circularBufferLength) {
tail = 0;
}
int w = channel.write(wrapper);
if (w != length) {
Log.e(TAG, "Cannot write the whole datagram to the channel (only " + w + "/" + length + ")");
return false;
}
return true;
}
public boolean readFrom(ByteBuffer buffer) {
int length = buffer.remaining();
if (length > MAX_DATAGRAM_LENGTH) {
throw new IllegalArgumentException("Datagram length (" + buffer.remaining() + ") may not be greater than "
+ MAX_DATAGRAM_LENGTH + " bytes");
}
if (!hasEnoughSpaceFor(length)) {
return false;
}
writeLength(length);
buffer.get(data, head, length);
head += length;
if (head >= circularBufferLength) {
head = 0;
}
return true;
}
private void writeLength(int length) {
assert (length & ~0xffff) == 0 : "Length must be stored on 16 bits";
data[head++] = (byte) ((length >> 8) & 0xff);
data[head++] = (byte) (length & 0xff);
}
private int readLength() {
int length = ((data[tail] & 0xff) << 8) | (data[tail + 1] & 0xff);
tail += 2;
return length;
}
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/IPv4Header.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet.relay;
import java.nio.ByteBuffer;
@SuppressWarnings("checkstyle:MagicNumber")
public class IPv4Header {
public enum Protocol {
TCP(6), UDP(17), OTHER(-1);
private final int number;
Protocol(int number) {
this.number = number;
}
int getNumber() {
return number;
}
static Protocol fromNumber(int number) {
if (number == TCP.number) {
return TCP;
}
if (number == UDP.number) {
return UDP;
}
return OTHER;
}
}
private static final int MIN_IPV4_HEADER_LENGTH = 20;
private ByteBuffer raw;
private byte version;
private int headerLength;
private int totalLength;
private Protocol protocol;
private int source;
private int destination;
public IPv4Header(ByteBuffer raw) {
assert raw.limit() >= MIN_IPV4_HEADER_LENGTH : "IPv4 headers length must be at least 20 bytes";
this.raw = raw;
byte versionAndIHL = raw.get(0);
version = (byte) (versionAndIHL >> 4);
byte ihl = (byte) (versionAndIHL & 0xf);
headerLength = ihl << 2;
raw.limit(headerLength);
totalLength = Short.toUnsignedInt(raw.getShort(2));
//raw.limit(); // by design
//assert totalLength == Binary.unsigned(raw.getShort(2)) : "Inconsistent packet length";
int protocolNumber = Short.toUnsignedInt(raw.get(9));
protocol = Protocol.fromNumber(protocolNumber);
source = raw.getInt(12);
destination = raw.getInt(16);
}
public boolean isSupported() {
return version == 4 && protocol != Protocol.OTHER;
}
public Protocol getProtocol() {
return protocol;
}
public int getHeaderLength() {
return headerLength;
}
public int getTotalLength() {
return totalLength;
}
public void setTotalLength(int totalLength) {
this.totalLength = totalLength;
// apply changes to raw
raw.putShort(2, (short) totalLength);
}
public int getSource() {
return source;
}
public int getDestination() {
return destination;
}
public void setSource(int source) {
this.source = source;
raw.putInt(12, source);
}
public void setDestination(int destination) {
this.destination = destination;
raw.putInt(16, destination);
}
public void swapSourceAndDestination() {
int tmp = source;
setSource(destination);
setDestination(tmp);
}
public ByteBuffer getRaw() {
raw.rewind();
return raw.slice();
}
public IPv4Header copyTo(ByteBuffer target) {
raw.rewind();
ByteBuffer slice = Binary.slice(target, target.position(), getHeaderLength());
target.put(raw);
return new IPv4Header(slice);
}
public IPv4Header copy() {
return new IPv4Header(Binary.copy(raw));
}
public void computeChecksum() {
// reset checksum field
setChecksum((short) 0);
// checksum computation is the most CPU-intensive task in gnirehtet
// prefer optimization over readability
byte[] rawArray = raw.array();
int rawArrayOffset = raw.arrayOffset();
int sum = 0;
for (int i = 0; i < headerLength / 2; ++i) {
// compute a 16-bit value from two 8-bit values manually
sum += (rawArray[rawArrayOffset + 2 * i] & 0xff) << 8 | (rawArray[rawArrayOffset + 2 * i + 1] & 0xff);
}
while ((sum & ~0xffff) != 0) {
sum = (sum & 0xffff) + (sum >> 16);
}
setChecksum((short) ~sum);
}
private void setChecksum(short checksum) {
raw.putShort(10, checksum);
}
public short getChecksum() {
return raw.getShort(10);
}
/**
* Read the packet IP version, assuming that an IP packets is stored at absolute position 0.
*
* @param buffer the buffer
* @return the IP version, or {@code -1} if not available
*/
public static int readVersion(ByteBuffer buffer) {
if (buffer.limit() == 0) {
// buffer is empty
return -1;
}
// version is stored in the 4 first bits
byte versionAndIHL = buffer.get(0);
return (versionAndIHL & 0xf0) >> 4;
}
/**
* Read the packet length, assuming thatan IP packet is stored at absolute position 0.
*
* @param buffer the buffer
* @return the packet length, or {@code -1} if not available
*/
public static int readLength(ByteBuffer buffer) {
if (buffer.limit() < 4) {
// buffer does not even contains the length field
return -1;
}
// packet length is 16 bits starting at offset 2
return Short.toUnsignedInt(buffer.getShort(2));
}
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/IPv4Packet.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet.relay;
import java.nio.ByteBuffer;
public class IPv4Packet {
private static final String TAG = IPv4Packet.class.getSimpleName();
@SuppressWarnings("checkstyle:MagicNumber")
public static final int MAX_PACKET_LENGTH = 1 << 16; // packet length is stored on 16 bits
private final ByteBuffer raw;
private final IPv4Header ipv4Header;
private final TransportHeader transportHeader;
public IPv4Packet(ByteBuffer raw) {
this.raw = raw;
raw.rewind();
if (Log.isVerboseEnabled()) {
Log.v(TAG, "IPv4Packet: " + Binary.buildPacketString(raw));
}
ipv4Header = new IPv4Header(raw.duplicate());
if (!ipv4Header.isSupported()) {
Log.d(TAG, "Unsupported IPv4 headers");
transportHeader = null;
return;
}
transportHeader = createTransportHeader();
raw.limit(ipv4Header.getTotalLength());
}
public boolean isValid() {
return transportHeader != null;
}
private TransportHeader createTransportHeader() {
IPv4Header.Protocol protocol = ipv4Header.getProtocol();
switch (protocol) {
case UDP:
return new UDPHeader(getRawTransport());
case TCP:
return new TCPHeader(getRawTransport());
default:
throw new AssertionError("Should be unreachable if ipv4Header.isSupported()");
}
}
private ByteBuffer getRawTransport() {
raw.position(ipv4Header.getHeaderLength());
return raw.slice();
}
public IPv4Header getIpv4Header() {
return ipv4Header;
}
public TransportHeader getTransportHeader() {
return transportHeader;
}
public void swapSourceAndDestination() {
ipv4Header.swapSourceAndDestination();
transportHeader.swapSourceAndDestination();
}
public ByteBuffer getRaw() {
raw.rewind();
return raw.duplicate();
}
public int getRawLength() {
return raw.limit();
}
public ByteBuffer getPayload() {
int headersLength = ipv4Header.getHeaderLength() + transportHeader.getHeaderLength();
raw.position(headersLength);
return raw.slice();
}
public int getPayloadLength() {
return raw.limit() - ipv4Header.getHeaderLength() - transportHeader.getHeaderLength();
}
public void computeChecksums() {
ipv4Header.computeChecksum();
transportHeader.computeChecksum(ipv4Header, getPayload());
}
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/IPv4PacketBuffer.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet.relay;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
public class IPv4PacketBuffer {
private final ByteBuffer buffer = ByteBuffer.allocate(IPv4Packet.MAX_PACKET_LENGTH);
public int readFrom(ReadableByteChannel channel) throws IOException {
return channel.read(buffer);
}
@SuppressWarnings("checkstyle:MagicNumber")
private int getAvailablePacketLength() {
int length = IPv4Header.readLength(buffer);
assert length == -1 || IPv4Header.readVersion(buffer) == 4 : "This function must not be called when the packet is not IPv4";
if (length == -1) {
// no packet
return 0;
}
if (length > buffer.remaining()) {
// no full packet available
return 0;
}
return length;
}
public IPv4Packet asIPv4Packet() {
buffer.flip();
int length = getAvailablePacketLength();
if (length == 0) {
buffer.compact();
return null;
}
int limit = buffer.limit();
buffer.limit(length).position(0);
ByteBuffer packetBuffer = buffer.slice();
buffer.limit(limit).position(length);
// In order to avoid copies, packetBuffer is shared with this IPv4Packet instance that is returned.
// Don't use it after another call to next()!
return new IPv4Packet(packetBuffer);
}
public void next() {
buffer.compact();
}
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Log.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet.relay;
import java.io.PrintStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public final class Log {
enum Level {
VERBOSE("V"),
DEBUG("D"),
INFO("I"),
WARNING("W"),
ERROR("E");
private final String id;
Level(String id) {
this.id = id;
}
}
private static Level threshold = Level.INFO;
private static final DateFormat FORMAT = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss.SSS");
private static final Date DATE = new Date();
private Log() {
// not instantiable
}
public static Level getThreshold() {
return threshold;
}
public static void setThreshold(Level threshold) {
Log.threshold = threshold;
}
public static boolean isEnabled(Level level) {
return level.ordinal() >= threshold.ordinal();
}
public static boolean isVerboseEnabled() {
return isEnabled(Level.VERBOSE);
}
public static boolean isDebugEnabled() {
return isEnabled(Level.DEBUG);
}
public static boolean isInfoEnabled() {
return isEnabled(Level.INFO);
}
public static boolean isWarningEnabled() {
return isEnabled(Level.WARNING);
}
public static boolean isErrorEnabled() {
return isEnabled(Level.ERROR);
}
private static String getDate() {
DATE.setTime(System.currentTimeMillis());
return FORMAT.format(DATE);
}
private static String format(Level level, String tag, String message) {
return getDate() + " " + level.id + " " + tag + ": " + message;
}
private static void l(Level level, PrintStream stream, String tag, String message, Throwable e) {
if (isEnabled(level)) {
stream.println(format(level, tag, message));
if (e != null) {
e.printStackTrace();
}
}
}
public static void v(String tag, String message, Throwable e) {
l(Level.VERBOSE, System.out, tag, message, e);
}
public static void v(String tag, String message) {
v(tag, message, null);
}
public static void d(String tag, String message, Throwable e) {
l(Level.DEBUG, System.out, tag, message, e);
}
public static void d(String tag, String message) {
d(tag, message, null);
}
public static void i(String tag, String message, Throwable e) {
l(Level.INFO, System.out, tag, message, e);
}
public static void i(String tag, String message) {
i(tag, message, null);
}
public static void w(String tag, String message, Throwable e) {
l(Level.WARNING, System.out, tag, message, e);
}
public static void w(String tag, String message) {
w(tag, message, null);
}
public static void e(String tag, String message, Throwable e) {
l(Level.ERROR, System.err, tag, message, e);
}
public static void e(String tag, String message) {
e(tag, message, null);
}
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Net.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet.relay;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
public final class Net {
private Net() {
// not instantiable
}
public static InetAddress[] toInetAddresses(String... addresses) {
InetAddress[] result = new InetAddress[addresses.length];
for (int i = 0; i < result.length; ++i) {
result[i] = toInetAddress(addresses[i]);
}
return result;
}
public static InetAddress toInetAddress(String address) {
try {
return InetAddress.getByName(address);
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e);
}
}
public static InetAddress toInetAddress(byte[] raw) {
try {
return InetAddress.getByAddress(raw);
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e);
}
}
@SuppressWarnings("checkstyle:MagicNumber")
public static InetAddress toInetAddress(int ipAddr) {
byte[] ip = {
(byte) (ipAddr >>> 24),
(byte) ((ipAddr >> 16) & 0xff),
(byte) ((ipAddr >> 8) & 0xff),
(byte) (ipAddr & 0xff)
};
return toInetAddress(ip);
}
public static String toString(InetSocketAddress address) {
return address.getAddress().getHostAddress() + ":" + address.getPort();
}
public static String toString(int ip, short port) {
return toString(new InetSocketAddress(Net.toInetAddress(ip), Short.toUnsignedInt(port)));
}
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/PacketSource.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet.relay;
/**
* Source that may produce packets.
* <p>
* When a {@link TCPConnection} sends a packet to the {@link Client} while its buffers are full,
* then it fails. To recover, once some space becomes available, the {@link Client} must pull the
* available packets.
* <p>
* This interface provides the abstraction of a packet source from which it can pull packets.
* <p>
* It is implemented by {@link TCPConnection}.
*/
public interface PacketSource {
IPv4Packet get();
void next();
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Packetizer.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet.relay;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
/**
* Convert from level 5 to level 3 by appending correct IP and transport headers.
*/
public class Packetizer {
private final ByteBuffer buffer = ByteBuffer.allocate(IPv4Packet.MAX_PACKET_LENGTH);
private final ByteBuffer payloadBuffer;
private final IPv4Header responseIPv4Header;
private final TransportHeader responseTransportHeader;
public Packetizer(IPv4Header ipv4Header, TransportHeader transportHeader) {
responseIPv4Header = ipv4Header.copyTo(buffer);
responseTransportHeader = transportHeader.copyTo(buffer);
payloadBuffer = buffer.slice();
}
public IPv4Header getResponseIPv4Header() {
return responseIPv4Header;
}
public TransportHeader getResponseTransportHeader() {
return responseTransportHeader;
}
public IPv4Packet packetizeEmptyPayload() {
payloadBuffer.limit(0).position(0);
return inflate();
}
public IPv4Packet packetize(ReadableByteChannel channel, int maxChunkSize) throws IOException {
payloadBuffer.limit(maxChunkSize).position(0);
int payloadLength = channel.read(payloadBuffer);
if (payloadLength == -1) {
return null;
}
payloadBuffer.flip();
return inflate();
}
public IPv4Packet packetize(ReadableByteChannel channel) throws IOException {
return packetize(channel, payloadBuffer.capacity());
}
private IPv4Packet inflate() {
int payloadLength = payloadBuffer.remaining();
buffer.limit(payloadBuffer.arrayOffset() + payloadBuffer.limit()).position(0);
int ipv4HeaderLength = responseIPv4Header.getHeaderLength();
int transportHeaderLength = responseTransportHeader.getHeaderLength();
int totalLength = ipv4HeaderLength + transportHeaderLength + payloadLength;
responseIPv4Header.setTotalLength(totalLength);
responseTransportHeader.setPayloadLength(payloadLength);
// In order to avoid copies, buffer is shared with this IPv4Packet instance that is returned.
// Don't use it after another call to packetize()!
IPv4Packet packet = new IPv4Packet(buffer);
packet.computeChecksums();
return packet;
}
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Relay.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.genymobile.gnirehtet.relay;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Set;
public class Relay {
private static final String TAG = Relay.class.getSimpleName();
private static final int CLEANING_INTERVAL = 60 * 1000;
private final int port;
public Relay(int port) {
this.port = port;
}
public void run() throws IOException {
Selector selector = Selector.open();
// will register the socket on the selector
TunnelServer tunnelServer = new TunnelServer(port, selector);
Log.i(TAG, "Relay server started");
long nextCleaningDeadline = System.currentTimeMillis() + UDPConnection.IDLE_TIMEOUT;
while (true) {
long timeout = Math.max(0, nextCleaningDeadline - System.currentTimeMillis());
selector.select(timeout);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
long now = System.currentTimeMillis();
if (now >= nextCleaningDeadline || selectedKeys.isEmpty()) {
tunnelServer.cleanUp();
nextCleaningDeadline = now + CLEANING_INTERVAL;
}
for (SelectionKey selectedKey : selectedKeys) {
SelectionHandler selectionHandler = (SelectionHandler) selectedKey.attachment();
selectionHandler.onReady(selectedKey);
}
// by design, we handled everything
selectedKeys.clear();
}
}
}
================================================
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Router.java
================================================
/*
* Copyright (C) 2017 Genymobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You ma
gitextract_jsgxj4af/ ├── .gitignore ├── DEVELOP.md ├── LICENSE ├── README.md ├── app/ │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── genymobile/ │ │ │ └── gnirehtet/ │ │ │ ├── Binary.java │ │ │ ├── CIDR.java │ │ │ ├── Forwarder.java │ │ │ ├── GnirehtetActivity.java │ │ │ ├── GnirehtetService.java │ │ │ ├── IPPacketOutputStream.java │ │ │ ├── InvalidCIDRException.java │ │ │ ├── Net.java │ │ │ ├── Notifier.java │ │ │ ├── PersistentRelayTunnel.java │ │ │ ├── RelayTunnel.java │ │ │ ├── RelayTunnelListener.java │ │ │ ├── RelayTunnelProvider.java │ │ │ ├── Tunnel.java │ │ │ └── VpnConfiguration.java │ │ └── res/ │ │ ├── drawable/ │ │ │ ├── ic_close_24dp.xml │ │ │ ├── ic_report_problem_24dp.xml │ │ │ └── ic_usb_24dp.xml │ │ ├── values/ │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── values-fr/ │ │ └── strings.xml │ └── test/ │ └── java/ │ └── com/ │ └── genymobile/ │ └── gnirehtet/ │ └── TestIPPacketOutputSteam.java ├── build.gradle ├── config/ │ ├── android-checkstyle.gradle │ ├── android-signing.gradle │ ├── checkstyle/ │ │ └── checkstyle.xml │ └── java-checkstyle.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── relay-java/ │ ├── build.gradle │ ├── scripts/ │ │ ├── gnirehtet │ │ ├── gnirehtet-run.cmd │ │ └── gnirehtet.cmd │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── genymobile/ │ │ └── gnirehtet/ │ │ ├── AdbMonitor.java │ │ ├── CommandLineArguments.java │ │ ├── Main.java │ │ └── relay/ │ │ ├── AbstractConnection.java │ │ ├── Binary.java │ │ ├── Client.java │ │ ├── CloseListener.java │ │ ├── CommandExecutionException.java │ │ ├── Connection.java │ │ ├── ConnectionId.java │ │ ├── DatagramBuffer.java │ │ ├── IPv4Header.java │ │ ├── IPv4Packet.java │ │ ├── IPv4PacketBuffer.java │ │ ├── Log.java │ │ ├── Net.java │ │ ├── PacketSource.java │ │ ├── Packetizer.java │ │ ├── Relay.java │ │ ├── Router.java │ │ ├── SelectionHandler.java │ │ ├── StreamBuffer.java │ │ ├── TCPConnection.java │ │ ├── TCPHeader.java │ │ ├── TransportHeader.java │ │ ├── TunnelServer.java │ │ ├── UDPConnection.java │ │ └── UDPHeader.java │ └── test/ │ └── java/ │ └── com/ │ └── genymobile/ │ └── gnirehtet/ │ ├── AdbMonitorTest.java │ ├── CommandLineArgumentsTest.java │ └── relay/ │ ├── DatagramBufferTest.java │ ├── IPv4HeaderTest.java │ ├── IPv4PacketBufferTest.java │ ├── IPv4PacketTest.java │ ├── InetAddressTest.java │ ├── PacketizerTest.java │ ├── StreamBufferTest.java │ ├── TCPHeaderTest.java │ └── UDPHeaderTest.java ├── relay-rust/ │ ├── .gitignore │ ├── Cargo.toml │ ├── build.gradle │ ├── scripts/ │ │ └── gnirehtet-run.cmd │ └── src/ │ ├── adb_monitor.rs │ ├── cli_args.rs │ ├── execution_error.rs │ ├── lib.rs │ ├── logger.rs │ ├── main.rs │ └── relay/ │ ├── binary.rs │ ├── byte_buffer.rs │ ├── client.rs │ ├── close_listener.rs │ ├── connection.rs │ ├── datagram.rs │ ├── datagram_buffer.rs │ ├── interrupt.rs │ ├── ipv4_header.rs │ ├── ipv4_packet.rs │ ├── ipv4_packet_buffer.rs │ ├── mod.rs │ ├── net.rs │ ├── packet_source.rs │ ├── packetizer.rs │ ├── relay.rs │ ├── router.rs │ ├── selector.rs │ ├── stream_buffer.rs │ ├── tcp_connection.rs │ ├── tcp_header.rs │ ├── transport_header.rs │ ├── tunnel_server.rs │ ├── udp_connection.rs │ └── udp_header.rs ├── release └── settings.gradle
SYMBOL INDEX (1110 symbols across 84 files)
FILE: app/src/main/java/com/genymobile/gnirehtet/Binary.java
class Binary (line 19) | @SuppressWarnings("checkstyle:MagicNumber")
method Binary (line 24) | private Binary() {
method unsigned (line 28) | public static int unsigned(byte value) {
method unsigned (line 32) | public static int unsigned(short value) {
method unsigned (line 36) | public static long unsigned(int value) {
method buildPacketString (line 40) | public static String buildPacketString(byte[] data, int len) {
FILE: app/src/main/java/com/genymobile/gnirehtet/CIDR.java
class CIDR (line 26) | public class CIDR implements Parcelable {
method CIDR (line 31) | public CIDR(InetAddress address, int prefixLength) {
method CIDR (line 36) | private CIDR(Parcel source) {
method parse (line 46) | @SuppressWarnings("checkstyle:MagicNumber")
method getAddress (line 69) | public InetAddress getAddress() {
method getPrefixLength (line 73) | public int getPrefixLength() {
method toString (line 77) | @Override
method describeContents (line 82) | @Override
method writeToParcel (line 87) | @Override
method createFromParcel (line 94) | @Override
method newArray (line 99) | @Override
FILE: app/src/main/java/com/genymobile/gnirehtet/Forwarder.java
class Forwarder (line 34) | public class Forwarder {
method Forwarder (line 51) | public Forwarder(VpnService vpnService, FileDescriptor vpnFileDescript...
method forward (line 56) | public void forward() {
method stop (line 83) | public void stop() {
method forwardDeviceToTunnel (line 90) | @SuppressWarnings("checkstyle:MagicNumber")
method forwardTunnelToDevice (line 118) | private void forwardTunnelToDevice(Tunnel tunnel) throws IOException {
method wakeUpReadWorkaround (line 150) | private void wakeUpReadWorkaround() {
FILE: app/src/main/java/com/genymobile/gnirehtet/GnirehtetActivity.java
class GnirehtetActivity (line 17) | public class GnirehtetActivity extends Activity {
method onCreate (line 31) | @Override
method handleIntent (line 37) | private void handleIntent(Intent intent) {
method createConfig (line 53) | private static VpnConfiguration createConfig(Intent intent) {
method startGnirehtet (line 65) | private boolean startGnirehtet(VpnConfiguration config) {
method stopGnirehtet (line 79) | private void stopGnirehtet() {
method requestAuthorization (line 83) | private void requestAuthorization(Intent vpnIntent, VpnConfiguration c...
method onActivityResult (line 88) | @Override
FILE: app/src/main/java/com/genymobile/gnirehtet/GnirehtetService.java
class GnirehtetService (line 36) | public class GnirehtetService extends VpnService {
method start (line 56) | public static void start(Context context, VpnConfiguration config) {
method stop (line 67) | public static void stop(Context context) {
method createStopIntent (line 75) | static Intent createStopIntent(Context context) {
method onStartCommand (line 81) | @Override
method isRunning (line 101) | private boolean isRunning() {
method startVpn (line 105) | private void startVpn(VpnConfiguration config) {
method setupVpn (line 112) | @SuppressWarnings("checkstyle:MagicNumber")
method setAsUndernlyingNetwork (line 154) | @SuppressWarnings("checkstyle:MagicNumber")
method findVpnNetwork (line 167) | private Network findVpnNetwork() {
method startForwarding (line 182) | private void startForwarding() {
method close (line 187) | private void close() {
class RelayTunnelConnectionStateHandler (line 206) | private static final class RelayTunnelConnectionStateHandler extends H...
method RelayTunnelConnectionStateHandler (line 210) | private RelayTunnelConnectionStateHandler(GnirehtetService vpnServic...
method handleMessage (line 214) | @Override
FILE: app/src/main/java/com/genymobile/gnirehtet/IPPacketOutputStream.java
class IPPacketOutputStream (line 28) | @SuppressWarnings("checkstyle:MagicNumber")
method IPPacketOutputStream (line 39) | public IPPacketOutputStream(OutputStream target) {
method close (line 43) | @Override
method flush (line 48) | @Override
method write (line 53) | @Override
method write (line 70) | @Override
method sink (line 81) | private void sink() throws IOException {
method sinkPacket (line 88) | private boolean sinkPacket() throws IOException {
method readPacketVersion (line 117) | public static int readPacketVersion(ByteBuffer buffer) {
method readPacketLength (line 133) | public static int readPacketLength(ByteBuffer buffer) {
FILE: app/src/main/java/com/genymobile/gnirehtet/InvalidCIDRException.java
class InvalidCIDRException (line 19) | public class InvalidCIDRException extends Exception {
method createMessage (line 23) | private static String createMessage(String cidr) {
method InvalidCIDRException (line 27) | public InvalidCIDRException(String cidr, Throwable cause) {
method InvalidCIDRException (line 32) | public InvalidCIDRException(String cidr) {
method getCIDR (line 37) | public String getCIDR() {
FILE: app/src/main/java/com/genymobile/gnirehtet/Net.java
class Net (line 23) | public final class Net {
method Net (line 24) | private Net() {
method toInetAddresses (line 28) | public static InetAddress[] toInetAddresses(String... addresses) {
method toInetAddress (line 36) | public static InetAddress toInetAddress(String address) {
method toInetAddress (line 44) | public static InetAddress toInetAddress(byte[] raw) {
method toCIDR (line 52) | public static CIDR toCIDR(String cidr) {
method toCIDRs (line 60) | public static CIDR[] toCIDRs(String... cidrs) {
method getLocalhostIPv4 (line 68) | @SuppressWarnings("checkstyle:MagicNumber")
FILE: app/src/main/java/com/genymobile/gnirehtet/Notifier.java
class Notifier (line 16) | public class Notifier {
method Notifier (line 24) | public Notifier(Service context) {
method createNotification (line 28) | private Notification createNotification(boolean failure) {
method createNotificationBuilder (line 42) | @SuppressWarnings("deprecation")
method createNotificationChannel (line 50) | @TargetApi(26)
method deleteNotificationChannel (line 57) | @TargetApi(26)
method start (line 62) | public void start() {
method stop (line 70) | public void stop() {
method setFailure (line 77) | public void setFailure(boolean failure) {
method createStopAction (line 85) | private Notification.Action createStopAction() {
method getNotificationManager (line 95) | private NotificationManager getNotificationManager() {
FILE: app/src/main/java/com/genymobile/gnirehtet/PersistentRelayTunnel.java
class PersistentRelayTunnel (line 29) | public class PersistentRelayTunnel implements Tunnel {
method PersistentRelayTunnel (line 36) | public PersistentRelayTunnel(VpnService vpnService, RelayTunnelListene...
method send (line 40) | @Override
method receive (line 58) | @Override
method close (line 81) | @Override
FILE: app/src/main/java/com/genymobile/gnirehtet/RelayTunnel.java
class RelayTunnel (line 28) | public final class RelayTunnel implements Tunnel {
method RelayTunnel (line 36) | private RelayTunnel() {
method open (line 40) | @SuppressWarnings("unused")
method connect (line 48) | public void connect() throws IOException {
method readClientId (line 70) | private static void readClientId(InputStream inputStream) throws IOExc...
method send (line 76) | @Override
method receive (line 84) | @Override
method close (line 93) | @Override
FILE: app/src/main/java/com/genymobile/gnirehtet/RelayTunnelListener.java
class RelayTunnelListener (line 8) | public class RelayTunnelListener {
method RelayTunnelListener (line 15) | public RelayTunnelListener(Handler handler) {
method notifyRelayTunnelConnected (line 19) | public void notifyRelayTunnelConnected() {
method notifyRelayTunnelDisconnected (line 23) | public void notifyRelayTunnelDisconnected() {
FILE: app/src/main/java/com/genymobile/gnirehtet/RelayTunnelProvider.java
class RelayTunnelProvider (line 26) | public class RelayTunnelProvider {
method RelayTunnelProvider (line 38) | public RelayTunnelProvider(VpnService vpnService, RelayTunnelListener ...
method getCurrentTunnel (line 43) | public RelayTunnel getCurrentTunnel() throws IOException, InterruptedE...
method connectTunnel (line 75) | private void connectTunnel(boolean notifyDisconnectedOnError) throws I...
method invalidateTunnel (line 88) | public synchronized void invalidateTunnel() {
method invalidateTunnel (line 103) | public synchronized void invalidateTunnel(Tunnel tunnelToInvalidate) {
method touchFailure (line 109) | private synchronized void touchFailure() {
method waitUntilNextAttemptSlot (line 113) | private void waitUntilNextAttemptSlot() throws InterruptedException {
method notifyConnected (line 125) | private void notifyConnected() {
method notifyDisconnected (line 131) | private void notifyDisconnected() {
FILE: app/src/main/java/com/genymobile/gnirehtet/Tunnel.java
type Tunnel (line 21) | public interface Tunnel {
method send (line 24) | void send(byte[] packet, int len) throws IOException;
method receive (line 27) | int receive(byte[] packet) throws IOException;
method close (line 30) | void close();
FILE: app/src/main/java/com/genymobile/gnirehtet/VpnConfiguration.java
class VpnConfiguration (line 25) | public class VpnConfiguration implements Parcelable {
method VpnConfiguration (line 30) | public VpnConfiguration() {
method VpnConfiguration (line 35) | public VpnConfiguration(InetAddress[] dnsServers, CIDR[] routes) {
method VpnConfiguration (line 40) | private VpnConfiguration(Parcel source) {
method getDnsServers (line 53) | public InetAddress[] getDnsServers() {
method getRoutes (line 57) | public CIDR[] getRoutes() {
method writeToParcel (line 61) | @Override
method describeContents (line 70) | @Override
method createFromParcel (line 76) | @Override
method newArray (line 81) | @Override
FILE: app/src/test/java/com/genymobile/gnirehtet/TestIPPacketOutputSteam.java
class TestIPPacketOutputSteam (line 27) | @SuppressWarnings("checkstyle:MagicNumber")
method createMockPacket (line 30) | private ByteBuffer createMockPacket() {
method writeMockPacketTo (line 37) | private void writeMockPacketTo(ByteBuffer buffer) {
method testSimplePacket (line 56) | @Test
method testSeveralPacketsAtOnce (line 76) | @Test
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/AdbMonitor.java
class AdbMonitor (line 34) | public class AdbMonitor {
type AdbDevicesCallback (line 36) | public interface AdbDevicesCallback {
method onNewDeviceConnected (line 37) | void onNewDeviceConnected(String serial);
method AdbMonitor (line 57) | public AdbMonitor(AdbDevicesCallback callback) {
method monitor (line 61) | public void monitor() {
method trackDevices (line 72) | private void trackDevices() throws IOException {
method trackDevicesOnChannel (line 82) | private void trackDevicesOnChannel(ByteChannel channel) throws IOExcep...
method writeRequest (line 95) | private static void writeRequest(WritableByteChannel channel, String r...
method consumeOkay (line 100) | private boolean consumeOkay(ReadableByteChannel channel) throws IOExce...
method nextPacket (line 116) | private String nextPacket(ReadableByteChannel channel) throws IOExcept...
method fillBufferFrom (line 125) | private void fillBufferFrom(ReadableByteChannel channel) throws IOExce...
method readPacket (line 134) | static String readPacket(ByteBuffer input) {
method handlePacket (line 158) | void handlePacket(String packet) {
method parseConnectedDevices (line 168) | private static List<String> parseConnectedDevices(String packet) {
method parseLength (line 183) | @SuppressWarnings("checkstyle:MagicNumber")
method repairAdbDaemon (line 196) | private static void repairAdbDaemon() {
method startAdbDaemon (line 204) | private static boolean startAdbDaemon() {
method sleep (line 222) | private static void sleep(long delay) {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/CommandLineArguments.java
class CommandLineArguments (line 22) | @SuppressWarnings("checkstyle:MagicNumber")
method parse (line 38) | public static CommandLineArguments parse(int acceptedParameters, Strin...
method getSerial (line 84) | public String getSerial() {
method getDnsServers (line 88) | public String getDnsServers() {
method getRoutes (line 92) | public String getRoutes() {
method getPort (line 96) | public int getPort() {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/Main.java
class Main (line 32) | public final class Main {
method Main (line 37) | private Main() {
method getAdbPath (line 41) | private static String getAdbPath() {
method getApkPath (line 46) | private static String getApkPath() {
type Command (line 51) | enum Command {
method getDescription (line 53) | @Override
method execute (line 60) | @Override
method getDescription (line 66) | @Override
method execute (line 73) | @Override
method getDescription (line 79) | @Override
method execute (line 84) | @Override
method getDescription (line 91) | @Override
method execute (line 100) | @Override
method getDescription (line 106) | @Override
method execute (line 113) | @Override
method getDescription (line 120) | @Override
method execute (line 136) | @Override
method getDescription (line 142) | @Override
method execute (line 150) | @Override
method getDescription (line 156) | @Override
method execute (line 163) | @Override
method getDescription (line 170) | @Override
method execute (line 175) | @Override
method getDescription (line 181) | @Override
method execute (line 189) | @Override
method getDescription (line 195) | @Override
method execute (line 200) | @Override
method Command (line 209) | Command(String command, int acceptedParameters) {
method getDescription (line 214) | abstract String getDescription();
method execute (line 216) | abstract void execute(CommandLineArguments args) throws Exception;
method cmdInstall (line 219) | private static void cmdInstall(String serial) throws InterruptedExcept...
method cmdUninstall (line 224) | private static void cmdUninstall(String serial) throws InterruptedExce...
method cmdReinstall (line 229) | private static void cmdReinstall(String serial) throws InterruptedExce...
method cmdRun (line 234) | private static void cmdRun(String serial, String dnsServers, String ro...
method cmdAutorun (line 250) | private static void cmdAutorun(final String dnsServers, final String r...
method cmdStart (line 262) | @SuppressWarnings("checkstyle:MagicNumber")
method cmdAutostart (line 286) | private static void cmdAutostart(final String dnsServers, final String...
method cmdStop (line 293) | private static void cmdStop(String serial) throws InterruptedException...
method cmdRestart (line 299) | private static void cmdRestart(String serial, String dnsServers, Strin...
method cmdTunnel (line 305) | private static void cmdTunnel(String serial, int port) throws Interrup...
method cmdRelay (line 309) | private static void cmdRelay(int port) throws IOException {
method asyncStart (line 314) | private static void asyncStart(String serial, String dnsServers, Strin...
method execAdb (line 324) | private static void execAdb(String serial, String... adbArgs) throws I...
method createAdbCommand (line 328) | private static List<String> createAdbCommand(String serial, String... ...
method execAdb (line 339) | private static void execAdb(String serial, List<String> adbArgList) th...
method execSync (line 344) | private static void execSync(List<String> command) throws InterruptedE...
method mustInstallClient (line 355) | private static boolean mustInstallClient(String serial) throws Interru...
method printUsage (line 382) | private static void printUsage() {
method appendCommandUsage (line 401) | private static void appendCommandUsage(StringBuilder builder, Command ...
method printCommandUsage (line 422) | private static void printCommandUsage(Command command) {
method main (line 428) | public static void main(String... args) throws Exception {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/AbstractConnection.java
class AbstractConnection (line 22) | public abstract class AbstractConnection implements Connection {
method AbstractConnection (line 29) | protected AbstractConnection(ConnectionId id, Client client) {
method getId (line 34) | @Override
method close (line 39) | protected void close() {
method consume (line 44) | protected void consume(PacketSource source) {
method sendToClient (line 48) | protected boolean sendToClient(IPv4Packet packet) {
method getRewrittenAddress (line 52) | private static InetAddress getRewrittenAddress(int ip) {
method getRewrittenDestination (line 61) | protected InetSocketAddress getRewrittenDestination() {
method logv (line 67) | public void logv(String tag, String message, Throwable e) {
method logv (line 71) | public void logv(String tag, String message) {
method logd (line 75) | public void logd(String tag, String message, Throwable e) {
method logd (line 79) | public void logd(String tag, String message) {
method logi (line 83) | public void logi(String tag, String message, Throwable e) {
method logi (line 87) | public void logi(String tag, String message) {
method logw (line 91) | public void logw(String tag, String message, Throwable e) {
method logw (line 95) | public void logw(String tag, String message) {
method loge (line 99) | public void loge(String tag, String message, Throwable e) {
method loge (line 103) | public void loge(String tag, String message) {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Binary.java
class Binary (line 21) | @SuppressWarnings("checkstyle:MagicNumber")
method Binary (line 26) | private Binary() {
method buildPacketString (line 30) | public static String buildPacketString(byte[] data, int offset, int le...
method buildPacketString (line 47) | public static String buildPacketString(ByteBuffer buffer) {
method copy (line 51) | public static ByteBuffer copy(ByteBuffer buffer) {
method slice (line 60) | public static ByteBuffer slice(ByteBuffer buffer, int offset, int leng...
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Client.java
class Client (line 29) | public class Client {
method Client (line 50) | public Client(Selector selector, SocketChannel clientChannel, CloseLis...
method createIntBuffer (line 74) | private static ByteBuffer createIntBuffer(int value) {
method getId (line 82) | public int getId() {
method getRouter (line 86) | public Router getRouter() {
method processReceive (line 90) | private void processReceive() {
method processSend (line 98) | private void processSend() {
method read (line 112) | private boolean read() {
method write (line 121) | private boolean write() {
method mustSendId (line 130) | private boolean mustSendId() {
method sendId (line 134) | private boolean sendId() {
method pushToNetwork (line 153) | private void pushToNetwork() {
method close (line 161) | private void close() {
method updateInterests (line 172) | private void updateInterests() {
method sendToClient (line 184) | public boolean sendToClient(IPv4Packet packet) {
method consume (line 194) | public void consume(PacketSource source) {
method processPending (line 204) | private void processPending() {
method cleanExpiredConnections (line 220) | public void cleanExpiredConnections() {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/CloseListener.java
type CloseListener (line 19) | public interface CloseListener<T> {
method onClosed (line 20) | void onClosed(T object);
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/CommandExecutionException.java
class CommandExecutionException (line 21) | public class CommandExecutionException extends Exception {
method CommandExecutionException (line 26) | public CommandExecutionException(List<String> command, int exitCode) {
method createMessage (line 32) | private static String createMessage(List<String> command, int exitCode) {
method getExitCode (line 36) | public int getExitCode() {
method getCommand (line 40) | public List<String> getCommand() {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Connection.java
type Connection (line 19) | public interface Connection {
method getId (line 21) | ConnectionId getId();
method sendToNetwork (line 22) | void sendToNetwork(IPv4Packet packet);
method disconnect (line 23) | void disconnect();
method isExpired (line 24) | boolean isExpired();
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/ConnectionId.java
class ConnectionId (line 19) | public class ConnectionId {
method ConnectionId (line 28) | public ConnectionId(IPv4Header.Protocol protocol, int sourceIp, short ...
method getProtocol (line 39) | public IPv4Header.Protocol getProtocol() {
method getSourceIp (line 43) | public int getSourceIp() {
method getSourcePort (line 47) | public int getSourcePort() {
method getDestinationIp (line 51) | public int getDestinationIp() {
method getDestinationPort (line 55) | public int getDestinationPort() {
method equals (line 59) | @Override
method hashCode (line 77) | @Override
method toString (line 87) | @Override
method from (line 92) | public static ConnectionId from(IPv4Header ipv4Header, TransportHeader...
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/DatagramBuffer.java
class DatagramBuffer (line 37) | @SuppressWarnings("checkstyle:MagicNumber")
method DatagramBuffer (line 53) | public DatagramBuffer(int capacity) {
method isEmpty (line 59) | public boolean isEmpty() {
method hasEnoughSpaceFor (line 63) | public boolean hasEnoughSpaceFor(int datagramLength) {
method capacity (line 72) | public int capacity() {
method writeTo (line 76) | public boolean writeTo(WritableByteChannel channel) throws IOException {
method readFrom (line 91) | public boolean readFrom(ByteBuffer buffer) {
method writeLength (line 109) | private void writeLength(int length) {
method readLength (line 115) | private int readLength() {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/IPv4Header.java
class IPv4Header (line 21) | @SuppressWarnings("checkstyle:MagicNumber")
type Protocol (line 24) | public enum Protocol {
method Protocol (line 29) | Protocol(int number) {
method getNumber (line 33) | int getNumber() {
method fromNumber (line 37) | static Protocol fromNumber(int number) {
method IPv4Header (line 58) | public IPv4Header(ByteBuffer raw) {
method isSupported (line 81) | public boolean isSupported() {
method getProtocol (line 85) | public Protocol getProtocol() {
method getHeaderLength (line 89) | public int getHeaderLength() {
method getTotalLength (line 93) | public int getTotalLength() {
method setTotalLength (line 97) | public void setTotalLength(int totalLength) {
method getSource (line 103) | public int getSource() {
method getDestination (line 107) | public int getDestination() {
method setSource (line 111) | public void setSource(int source) {
method setDestination (line 116) | public void setDestination(int destination) {
method swapSourceAndDestination (line 121) | public void swapSourceAndDestination() {
method getRaw (line 127) | public ByteBuffer getRaw() {
method copyTo (line 132) | public IPv4Header copyTo(ByteBuffer target) {
method copy (line 139) | public IPv4Header copy() {
method computeChecksum (line 143) | public void computeChecksum() {
method setChecksum (line 163) | private void setChecksum(short checksum) {
method getChecksum (line 167) | public short getChecksum() {
method readVersion (line 177) | public static int readVersion(ByteBuffer buffer) {
method readLength (line 193) | public static int readLength(ByteBuffer buffer) {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/IPv4Packet.java
class IPv4Packet (line 21) | public class IPv4Packet {
method IPv4Packet (line 32) | public IPv4Packet(ByteBuffer raw) {
method isValid (line 50) | public boolean isValid() {
method createTransportHeader (line 54) | private TransportHeader createTransportHeader() {
method getRawTransport (line 66) | private ByteBuffer getRawTransport() {
method getIpv4Header (line 71) | public IPv4Header getIpv4Header() {
method getTransportHeader (line 75) | public TransportHeader getTransportHeader() {
method swapSourceAndDestination (line 79) | public void swapSourceAndDestination() {
method getRaw (line 84) | public ByteBuffer getRaw() {
method getRawLength (line 89) | public int getRawLength() {
method getPayload (line 93) | public ByteBuffer getPayload() {
method getPayloadLength (line 99) | public int getPayloadLength() {
method computeChecksums (line 103) | public void computeChecksums() {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/IPv4PacketBuffer.java
class IPv4PacketBuffer (line 23) | public class IPv4PacketBuffer {
method readFrom (line 27) | public int readFrom(ReadableByteChannel channel) throws IOException {
method getAvailablePacketLength (line 31) | @SuppressWarnings("checkstyle:MagicNumber")
method asIPv4Packet (line 46) | public IPv4Packet asIPv4Packet() {
method next (line 62) | public void next() {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Log.java
class Log (line 24) | public final class Log {
type Level (line 26) | enum Level {
method Level (line 35) | Level(String id) {
method Log (line 45) | private Log() {
method getThreshold (line 49) | public static Level getThreshold() {
method setThreshold (line 53) | public static void setThreshold(Level threshold) {
method isEnabled (line 57) | public static boolean isEnabled(Level level) {
method isVerboseEnabled (line 61) | public static boolean isVerboseEnabled() {
method isDebugEnabled (line 65) | public static boolean isDebugEnabled() {
method isInfoEnabled (line 69) | public static boolean isInfoEnabled() {
method isWarningEnabled (line 73) | public static boolean isWarningEnabled() {
method isErrorEnabled (line 77) | public static boolean isErrorEnabled() {
method getDate (line 81) | private static String getDate() {
method format (line 86) | private static String format(Level level, String tag, String message) {
method l (line 90) | private static void l(Level level, PrintStream stream, String tag, Str...
method v (line 99) | public static void v(String tag, String message, Throwable e) {
method v (line 103) | public static void v(String tag, String message) {
method d (line 107) | public static void d(String tag, String message, Throwable e) {
method d (line 111) | public static void d(String tag, String message) {
method i (line 115) | public static void i(String tag, String message, Throwable e) {
method i (line 119) | public static void i(String tag, String message) {
method w (line 123) | public static void w(String tag, String message, Throwable e) {
method w (line 127) | public static void w(String tag, String message) {
method e (line 131) | public static void e(String tag, String message, Throwable e) {
method e (line 135) | public static void e(String tag, String message) {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Net.java
class Net (line 23) | public final class Net {
method Net (line 24) | private Net() {
method toInetAddresses (line 28) | public static InetAddress[] toInetAddresses(String... addresses) {
method toInetAddress (line 36) | public static InetAddress toInetAddress(String address) {
method toInetAddress (line 44) | public static InetAddress toInetAddress(byte[] raw) {
method toInetAddress (line 52) | @SuppressWarnings("checkstyle:MagicNumber")
method toString (line 63) | public static String toString(InetSocketAddress address) {
method toString (line 67) | public static String toString(int ip, short port) {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/PacketSource.java
type PacketSource (line 30) | public interface PacketSource {
method get (line 32) | IPv4Packet get();
method next (line 34) | void next();
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Packetizer.java
class Packetizer (line 26) | public class Packetizer {
method Packetizer (line 34) | public Packetizer(IPv4Header ipv4Header, TransportHeader transportHead...
method getResponseIPv4Header (line 40) | public IPv4Header getResponseIPv4Header() {
method getResponseTransportHeader (line 44) | public TransportHeader getResponseTransportHeader() {
method packetizeEmptyPayload (line 48) | public IPv4Packet packetizeEmptyPayload() {
method packetize (line 53) | public IPv4Packet packetize(ReadableByteChannel channel, int maxChunkS...
method packetize (line 63) | public IPv4Packet packetize(ReadableByteChannel channel) throws IOExce...
method inflate (line 67) | private IPv4Packet inflate() {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Relay.java
class Relay (line 24) | public class Relay {
method Relay (line 32) | public Relay(int port) {
method run (line 36) | public void run() throws IOException {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Router.java
class Router (line 24) | public class Router {
method Router (line 34) | public Router(Client client, Selector selector) {
method sendToNetwork (line 39) | public void sendToNetwork(IPv4Packet packet) {
method getConnection (line 55) | private Connection getConnection(IPv4Header ipv4Header, TransportHeade...
method createConnection (line 65) | private Connection createConnection(ConnectionId id, IPv4Header ipv4He...
method find (line 76) | private Connection find(ConnectionId id) {
method clear (line 85) | public void clear() {
method remove (line 92) | public void remove(Connection connection) {
method cleanExpiredConnections (line 98) | public void cleanExpiredConnections() {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/SelectionHandler.java
type SelectionHandler (line 21) | public interface SelectionHandler {
method onReady (line 23) | void onReady(SelectionKey selectionKey);
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/StreamBuffer.java
class StreamBuffer (line 26) | public class StreamBuffer {
method StreamBuffer (line 33) | public StreamBuffer(int capacity) {
method isEmpty (line 38) | public boolean isEmpty() {
method isFull (line 42) | public boolean isFull() {
method size (line 46) | public int size() {
method capacity (line 53) | public int capacity() {
method remaining (line 57) | public int remaining() {
method writeTo (line 61) | public int writeTo(WritableByteChannel channel) throws IOException {
method readFrom (line 82) | public void readFrom(ByteBuffer buffer) {
method optimize (line 103) | private void optimize() {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/TCPConnection.java
class TCPConnection (line 25) | public class TCPConnection extends AbstractConnection implements PacketS...
type State (line 39) | public enum State {
method isConnected (line 49) | public boolean isConnected() {
method isClosed (line 53) | public boolean isClosed() {
method TCPConnection (line 75) | public TCPConnection(ConnectionId id, Client client, Selector selector...
method disconnect (line 104) | @Override
method processReceive (line 115) | private void processReceive() {
method processSend (line 134) | private void processSend() {
method eof (line 158) | private void eof() {
method getRemainingClientWindow (line 171) | private int getRemainingClientWindow() {
method isExpired (line 181) | @Override
method updateHeaders (line 187) | private void updateHeaders(int flags) {
method createChannel (line 194) | private SocketChannel createChannel() throws IOException {
method sendToNetwork (line 202) | @Override
method handlePacket (line 209) | private void handlePacket(IPv4Packet packet) {
method handleFirstPacket (line 258) | private void handleFirstPacket(IPv4Packet packet) {
method handleDuplicateSyn (line 280) | private void handleDuplicateSyn(IPv4Packet packet) {
method handleFin (line 293) | private void handleFin() {
method doHandleFin (line 303) | private void doHandleFin() {
method handleFinAck (line 329) | private void handleFinAck() {
method handleAck (line 347) | private void handleAck(IPv4Packet packet) {
method processConnect (line 374) | private void processConnect() {
method finishConnect (line 387) | private boolean finishConnect() {
method resetConnection (line 396) | private void resetConnection() {
method createEmptyResponsePacket (line 403) | private IPv4Packet createEmptyResponsePacket(int flags) {
method sendEmptyPacketToClient (line 416) | private void sendEmptyPacketToClient(int flags) {
method updateInterests (line 420) | protected void updateInterests() {
method mayRead (line 441) | private boolean mayRead() {
method mayWrite (line 452) | private boolean mayWrite() {
method mayConnect (line 456) | private boolean mayConnect() {
method numbers (line 460) | private String numbers() {
method get (line 464) | @Override
method updateAcknowledgementNumber (line 471) | private void updateAcknowledgementNumber(IPv4Packet packet) {
method next (line 477) | @Override
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/TCPHeader.java
class TCPHeader (line 21) | @SuppressWarnings("checkstyle:MagicNumber")
method TCPHeader (line 40) | public TCPHeader(ByteBuffer raw) {
method getWindow (line 57) | public int getWindow() {
method getSourcePort (line 61) | @Override
method getDestinationPort (line 66) | @Override
method setSourcePort (line 71) | @Override
method setDestinationPort (line 77) | @Override
method getSequenceNumber (line 83) | public int getSequenceNumber() {
method setSequenceNumber (line 87) | public void setSequenceNumber(int sequenceNumber) {
method getAcknowledgementNumber (line 92) | public int getAcknowledgementNumber() {
method setAcknowledgementNumber (line 96) | public void setAcknowledgementNumber(int acknowledgementNumber) {
method getHeaderLength (line 101) | @Override
method setPayloadLength (line 106) | @Override
method getFlags (line 111) | public int getFlags() {
method setFlags (line 115) | public void setFlags(int flags) {
method shrinkOptions (line 122) | public void shrinkOptions() {
method setDataOffset (line 127) | private void setDataOffset(int dataOffset) {
method isFin (line 134) | public boolean isFin() {
method isSyn (line 138) | public boolean isSyn() {
method isRst (line 142) | public boolean isRst() {
method isPsh (line 146) | public boolean isPsh() {
method isAck (line 150) | public boolean isAck() {
method isUrg (line 154) | public boolean isUrg() {
method getRaw (line 158) | @Override
method copyTo (line 164) | @Override
method copy (line 172) | public TCPHeader copy() {
method computeChecksum (line 176) | @Override
method setChecksum (line 224) | private void setChecksum(short checksum) {
method getChecksum (line 228) | public short getChecksum() {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/TransportHeader.java
type TransportHeader (line 21) | public interface TransportHeader {
method getSourcePort (line 23) | int getSourcePort();
method getDestinationPort (line 25) | int getDestinationPort();
method setSourcePort (line 27) | void setSourcePort(int port);
method setDestinationPort (line 29) | void setDestinationPort(int port);
method getHeaderLength (line 31) | int getHeaderLength();
method setPayloadLength (line 33) | void setPayloadLength(int payloadLength);
method getRaw (line 35) | ByteBuffer getRaw();
method copyTo (line 37) | TransportHeader copyTo(ByteBuffer buffer);
method computeChecksum (line 39) | void computeChecksum(IPv4Header ipv4Header, ByteBuffer payload);
method swapSourceAndDestination (line 41) | default void swapSourceAndDestination() {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/TunnelServer.java
class TunnelServer (line 16) | public class TunnelServer {
method TunnelServer (line 22) | public TunnelServer(int port, Selector selector) throws IOException {
method acceptClient (line 39) | private void acceptClient(Selector selector, ServerSocketChannel serve...
method removeClient (line 48) | private void removeClient(Client client) {
method cleanUp (line 53) | public void cleanUp() {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/UDPConnection.java
class UDPConnection (line 24) | public class UDPConnection extends AbstractConnection {
method UDPConnection (line 39) | public UDPConnection(ConnectionId id, Client client, Selector selector...
method sendToNetwork (line 63) | @Override
method disconnect (line 72) | @Override
method isExpired (line 83) | @Override
method createChannel (line 88) | private DatagramChannel createChannel() throws IOException {
method touch (line 96) | private void touch() {
method processReceive (line 100) | private void processReceive() {
method processSend (line 109) | private void processSend() {
method read (line 115) | private IPv4Packet read() {
method write (line 124) | private boolean write() {
method pushToClient (line 133) | private void pushToClient(IPv4Packet packet) {
method updateInterests (line 144) | protected void updateInterests() {
method mayWrite (line 159) | private boolean mayWrite() {
FILE: relay-java/src/main/java/com/genymobile/gnirehtet/relay/UDPHeader.java
class UDPHeader (line 21) | @SuppressWarnings("checkstyle:MagicNumber")
method UDPHeader (line 30) | public UDPHeader(ByteBuffer raw) {
method getSourcePort (line 37) | @Override
method getDestinationPort (line 42) | @Override
method setSourcePort (line 47) | @Override
method setDestinationPort (line 53) | @Override
method getHeaderLength (line 59) | @Override
method setPayloadLength (line 64) | @Override
method getRaw (line 70) | @Override
method copyTo (line 76) | @Override
method computeChecksum (line 84) | @Override
FILE: relay-java/src/test/java/com/genymobile/gnirehtet/AdbMonitorTest.java
class AdbMonitorTest (line 25) | public class AdbMonitorTest {
method toByteBuffer (line 27) | private static ByteBuffer toByteBuffer(String s) {
method testReadValidPacket (line 31) | @Test
method testReadValidPackets (line 38) | @Test
method testReadValidPacketWithGarbage (line 45) | @Test
method testReadShortPacket (line 52) | @Test
method testHandlePacketDevice (line 59) | @Test
method testHandlePacketOffline (line 68) | @Test
method testMultipleConnectedDevices (line 77) | @Test
method testMultipleConnectedDevicesWithDisconnection (line 93) | @Test
FILE: relay-java/src/test/java/com/genymobile/gnirehtet/CommandLineArgumentsTest.java
class CommandLineArgumentsTest (line 22) | public class CommandLineArgumentsTest {
method testNoArgs (line 27) | @Test
method testSerialOnly (line 34) | @Test
method testInvalidParameter (line 41) | @Test(expected = IllegalArgumentException.class)
method testDnsServersOnly (line 46) | @Test
method testSerialAndDnsServers (line 53) | @Test
method testDnsServersAndSerial (line 60) | @Test
method testSerialWithNoDnsServersParameter (line 67) | @Test(expected = IllegalArgumentException.class)
method testNoDnsServersParameter (line 72) | @Test(expected = IllegalArgumentException.class)
method testRoutesParameter (line 77) | @Test
method testNoRoutesParameter (line 83) | @Test(expected = IllegalArgumentException.class)
FILE: relay-java/src/test/java/com/genymobile/gnirehtet/relay/DatagramBufferTest.java
class DatagramBufferTest (line 28) | @SuppressWarnings("checkstyle:MagicNumber")
method createDatagram (line 31) | private static ByteBuffer createDatagram(int size) {
method testSimple (line 39) | @Test
method testDatagramBoundaries (line 54) | @Test
method testCircular (line 93) | @Test
FILE: relay-java/src/test/java/com/genymobile/gnirehtet/relay/IPv4HeaderTest.java
class IPv4HeaderTest (line 25) | @SuppressWarnings("checkstyle:MagicNumber")
method testReadIPVersionUnavailable (line 28) | @Test
method testReadIPVersionAvailable (line 36) | @Test
method testReadLengthUnavailable (line 46) | @Test
method testReadLengthAvailable (line 54) | @Test
method createMockHeaders (line 65) | private static ByteBuffer createMockHeaders() {
method testParsePacketHeaders (line 84) | @Test
method testEditHeaders (line 94) | @Test
method testComputeChecksum (line 128) | @Test
method benchComputeChecksum (line 147) | @Ignore // manual benchmark
FILE: relay-java/src/test/java/com/genymobile/gnirehtet/relay/IPv4PacketBufferTest.java
class IPv4PacketBufferTest (line 28) | @SuppressWarnings("checkstyle:MagicNumber")
method createMockPacket (line 31) | private static ByteBuffer createMockPacket() {
method writeMockPacketTo (line 38) | private static void writeMockPacketTo(ByteBuffer buffer) {
method contentToChannel (line 57) | private static ReadableByteChannel contentToChannel(ByteBuffer buffer) {
method testParseIPv4PacketBuffer (line 62) | @Test
method testParseFragmentedIPv4PacketBuffer (line 76) | @Test
method createMockPackets (line 98) | private static ByteBuffer createMockPackets() {
method testMultiPackets (line 107) | @Test
method checkPacketHeaders (line 125) | private static void checkPacketHeaders(IPv4Packet packet) {
FILE: relay-java/src/test/java/com/genymobile/gnirehtet/relay/IPv4PacketTest.java
class IPv4PacketTest (line 24) | @SuppressWarnings("checkstyle:MagicNumber")
method createMockPacket (line 27) | private static ByteBuffer createMockPacket() {
method testParseHeaders (line 50) | @Test
method testPayload (line 86) | @Test
FILE: relay-java/src/test/java/com/genymobile/gnirehtet/relay/InetAddressTest.java
class InetAddressTest (line 24) | @SuppressWarnings("checkstyle:MagicNumber")
method testIntToInetAddress (line 27) | @Test
method testUnsignedIntToInetAddress (line 34) | @Test
FILE: relay-java/src/test/java/com/genymobile/gnirehtet/relay/PacketizerTest.java
class PacketizerTest (line 28) | @SuppressWarnings("checkstyle:MagicNumber")
method createMockPacket (line 31) | private static ByteBuffer createMockPacket() {
method testMergeHeadersAndPayload (line 54) | @Test
method testPacketizeChunks (line 72) | @Test
FILE: relay-java/src/test/java/com/genymobile/gnirehtet/relay/StreamBufferTest.java
class StreamBufferTest (line 29) | @SuppressWarnings("checkstyle:MagicNumber")
method createChunk (line 32) | private static ByteBuffer createChunk() {
method testSimple (line 37) | @Test
class DevNullChannel (line 52) | static class DevNullChannel implements ByteChannel {
method DevNullChannel (line 56) | DevNullChannel(int writeChunkSize) {
method read (line 60) | @Override
method write (line 65) | @Override
method isOpen (line 72) | @Override
method close (line 77) | @Override
method testCircular (line 83) | @Test
method testNotEnoughSpace (line 118) | @Test
FILE: relay-java/src/test/java/com/genymobile/gnirehtet/relay/TCPHeaderTest.java
class TCPHeaderTest (line 25) | @SuppressWarnings("checkstyle:MagicNumber")
method createMockPacket (line 28) | private static ByteBuffer createMockPacket() {
method createMockOddPacket (line 55) | private static ByteBuffer createMockOddPacket() {
method createMockTCPHeader (line 84) | private static ByteBuffer createMockTCPHeader() {
method testEditHeaders (line 100) | @Test
method testComputeChecksum (line 142) | @Test
method testComputeChecksumOddLength (line 171) | @Test
method testCopyTo (line 201) | @Test
method createLongPacket (line 216) | private static ByteBuffer createLongPacket() {
method benchComputeChecksum (line 246) | @Ignore // manual benchmark
FILE: relay-java/src/test/java/com/genymobile/gnirehtet/relay/UDPHeaderTest.java
class UDPHeaderTest (line 24) | @SuppressWarnings("checkstyle:MagicNumber")
method createMockHeaders (line 27) | private static ByteBuffer createMockHeaders() {
method testParsePacketHeaders (line 38) | @Test
method testEditHeaders (line 46) | @Test
method testCopyTo (line 79) | @Test
FILE: relay-rust/src/adb_monitor.rs
constant TAG (line 26) | const TAG: &str = "AdbMonitor";
type AdbMonitorCallback (line 28) | pub trait AdbMonitorCallback {
method on_new_device_connected (line 29) | fn on_new_device_connected(&self, serial: &str);
method on_new_device_connected (line 36) | fn on_new_device_connected(&self, serial: &str) {
type AdbMonitor (line 40) | pub struct AdbMonitor {
constant TRACK_DEVICES_REQUEST (line 47) | const TRACK_DEVICES_REQUEST: &'static [u8] = b"0012host:track-devices";
constant BUFFER_SIZE (line 48) | const BUFFER_SIZE: usize = 1024;
constant RETRY_DELAY_ADB_DAEMON_OK (line 49) | const RETRY_DELAY_ADB_DAEMON_OK: u64 = 1000;
constant RETRY_DELAY_ADB_DAEMON_KO (line 50) | const RETRY_DELAY_ADB_DAEMON_KO: u64 = 5000;
method new (line 52) | pub fn new(callback: Box<dyn AdbMonitorCallback>) -> Self {
method monitor (line 60) | pub fn monitor(&mut self) {
method track_devices (line 69) | fn track_devices(&mut self) -> io::Result<()> {
method track_devices_on_stream (line 75) | fn track_devices_on_stream(&mut self, stream: &mut TcpStream) -> io::R...
method consume_okay (line 86) | fn consume_okay(&mut self, stream: &mut TcpStream) -> io::Result<bool> {
method read_packet (line 95) | fn read_packet(buf: &mut ByteBuffer) -> io::Result<Option<String>> {
method next_packet (line 107) | fn next_packet(&mut self, stream: &mut TcpStream) -> io::Result<String> {
method fill_buffer_from (line 118) | fn fill_buffer_from(&mut self, stream: &mut TcpStream) -> io::Result<(...
method available_packet_length (line 129) | fn available_packet_length(input: &[u8]) -> io::Result<Option<usize>> {
method handle_packet (line 157) | fn handle_packet(&mut self, packet: &str) {
method parse_connected_devices (line 167) | fn parse_connected_devices(&self, packet: &str) -> Vec<String> {
method parse_length (line 184) | fn parse_length(data: &[u8]) -> io::Result<u32> {
method repair_adb_daemon (line 200) | fn repair_adb_daemon() {
method start_adb_daemon (line 208) | fn start_adb_daemon() -> bool {
method binary_to_string (line 232) | fn binary_to_string(data: &[u8]) -> io::Result<String> {
function test_read_valid_packet (line 253) | fn test_read_valid_packet() {
function test_read_valid_packets (line 265) | fn test_read_valid_packets() {
function test_read_valid_packet_with_garbage (line 280) | fn test_read_valid_packet_with_garbage() {
function test_read_short_packet (line 292) | fn test_read_short_packet() {
function test_handle_packet_device (line 304) | fn test_handle_packet_device() {
function test_handle_packet_offline (line 317) | fn test_handle_packet_offline() {
function test_multiple_connected_devices (line 330) | fn test_multiple_connected_devices() {
function test_multiple_connected_devices_with_disconnection (line 346) | fn test_multiple_connected_devices_with_disconnection() {
FILE: relay-rust/src/cli_args.rs
constant PARAM_NONE (line 17) | pub const PARAM_NONE: u8 = 0;
constant PARAM_SERIAL (line 18) | pub const PARAM_SERIAL: u8 = 1;
constant PARAM_DNS_SERVERS (line 19) | pub const PARAM_DNS_SERVERS: u8 = 1 << 1;
constant PARAM_ROUTES (line 20) | pub const PARAM_ROUTES: u8 = 1 << 2;
constant PARAM_PORT (line 21) | pub const PARAM_PORT: u8 = 1 << 3;
constant DEFAULT_PORT (line 23) | pub const DEFAULT_PORT: u16 = 31416;
type CommandLineArguments (line 25) | pub struct CommandLineArguments {
method parse (line 34) | pub fn parse<S: Into<String>>(accepted_parameters: u8, args: Vec<S>) -...
method serial (line 90) | pub fn serial(&self) -> Option<&str> {
method dns_servers (line 94) | pub fn dns_servers(&self) -> Option<&str> {
method routes (line 98) | pub fn routes(&self) -> Option<&str> {
method port (line 102) | pub fn port(&self) -> u16 {
constant ACCEPT_ALL (line 111) | const ACCEPT_ALL: u8 = PARAM_SERIAL | PARAM_DNS_SERVERS | PARAM_ROUTES;
function test_no_args (line 114) | fn test_no_args() {
function test_serial_only (line 121) | fn test_serial_only() {
function test_invalid_paramater (line 128) | fn test_invalid_paramater() {
function test_dns_servers_only (line 134) | fn test_dns_servers_only() {
function test_serial_and_dns_servers (line 142) | fn test_serial_and_dns_servers() {
function test_dns_servers_and_serial (line 150) | fn test_dns_servers_and_serial() {
function test_serial_with_no_dns_servers_parameter (line 158) | fn test_serial_with_no_dns_servers_parameter() {
function test_no_dns_servers_parameter (line 164) | fn test_no_dns_servers_parameter() {
function test_routes_parameter (line 170) | fn test_routes_parameter() {
function test_no_routes_parameter (line 177) | fn test_no_routes_parameter() {
FILE: relay-rust/src/execution_error.rs
type CommandExecutionError (line 25) | pub enum CommandExecutionError {
method fmt (line 131) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
method source (line 141) | fn source(&self) -> Option<&(dyn error::Error + 'static)> {
method from (line 151) | fn from(error: ProcessIoError) -> Self {
method from (line 157) | fn from(error: ProcessStatusError) -> Self {
method from (line 163) | fn from(error: io::Error) -> Self {
type ProcessStatusError (line 32) | pub struct ProcessStatusError {
method new (line 88) | pub fn new(cmd: Cmd, status: ExitStatus) -> Self {
method fmt (line 97) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
type ProcessIoError (line 38) | pub struct ProcessIoError {
method new (line 113) | pub fn new(cmd: Cmd, error: io::Error) -> Self {
method fmt (line 119) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
method source (line 125) | fn source(&self) -> Option<&(dyn error::Error + 'static)> {
type Cmd (line 44) | pub struct Cmd {
method fmt (line 69) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
method new (line 75) | pub fn new<S1, S2>(command: S1, args: Vec<S2>) -> Cmd
type Termination (line 50) | pub enum Termination {
method from (line 57) | fn from(status: ExitStatus) -> Self {
FILE: relay-rust/src/lib.rs
function relay (line 23) | pub fn relay(port: u16) -> io::Result<()> {
FILE: relay-rust/src/logger.rs
constant THRESHOLD (line 22) | const THRESHOLD: LevelFilter = LevelFilter::Info;
type SimpleLogger (line 24) | pub struct SimpleLogger;
method enabled (line 27) | fn enabled(&self, metadata: &Metadata) -> bool {
method log (line 31) | fn log(&self, record: &Record) {
method flush (line 50) | fn flush(&self) {
function init (line 56) | pub fn init() -> Result<(), SetLoggerError> {
FILE: relay-rust/src/main.rs
constant TAG (line 36) | const TAG: &str = "Main";
constant REQUIRED_APK_VERSION_CODE (line 37) | const REQUIRED_APK_VERSION_CODE: &str = "9";
function get_adb_path (line 40) | fn get_adb_path() -> String {
function get_apk_path (line 49) | fn get_apk_path() -> String {
constant COMMANDS (line 57) | const COMMANDS: &[&dyn Command] = &[
type Command (line 71) | trait Command {
method command (line 72) | fn command(&self) -> &'static str;
method accepted_parameters (line 73) | fn accepted_parameters(&self) -> u8;
method description (line 74) | fn description(&self) -> &'static str;
method execute (line 75) | fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandEx...
method command (line 91) | fn command(&self) -> &'static str {
method accepted_parameters (line 95) | fn accepted_parameters(&self) -> u8 {
method description (line 99) | fn description(&self) -> &'static str {
method execute (line 105) | fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandEx...
method command (line 111) | fn command(&self) -> &'static str {
method accepted_parameters (line 115) | fn accepted_parameters(&self) -> u8 {
method description (line 119) | fn description(&self) -> &'static str {
method execute (line 125) | fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandEx...
method command (line 131) | fn command(&self) -> &'static str {
method accepted_parameters (line 135) | fn accepted_parameters(&self) -> u8 {
method description (line 139) | fn description(&self) -> &'static str {
method execute (line 143) | fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandEx...
method command (line 149) | fn command(&self) -> &'static str {
method accepted_parameters (line 153) | fn accepted_parameters(&self) -> u8 {
method description (line 160) | fn description(&self) -> &'static str {
method execute (line 168) | fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandEx...
method command (line 179) | fn command(&self) -> &'static str {
method accepted_parameters (line 183) | fn accepted_parameters(&self) -> u8 {
method description (line 187) | fn description(&self) -> &'static str {
method execute (line 193) | fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandEx...
method command (line 199) | fn command(&self) -> &'static str {
method accepted_parameters (line 203) | fn accepted_parameters(&self) -> u8 {
method description (line 210) | fn description(&self) -> &'static str {
method execute (line 225) | fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandEx...
method command (line 236) | fn command(&self) -> &'static str {
method accepted_parameters (line 240) | fn accepted_parameters(&self) -> u8 {
method description (line 244) | fn description(&self) -> &'static str {
method execute (line 251) | fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandEx...
method command (line 257) | fn command(&self) -> &'static str {
method accepted_parameters (line 261) | fn accepted_parameters(&self) -> u8 {
method description (line 265) | fn description(&self) -> &'static str {
method execute (line 271) | fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandEx...
method command (line 277) | fn command(&self) -> &'static str {
method accepted_parameters (line 281) | fn accepted_parameters(&self) -> u8 {
method description (line 288) | fn description(&self) -> &'static str {
method execute (line 292) | fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandEx...
method command (line 305) | fn command(&self) -> &'static str {
method accepted_parameters (line 309) | fn accepted_parameters(&self) -> u8 {
method description (line 313) | fn description(&self) -> &'static str {
method execute (line 320) | fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandEx...
method command (line 326) | fn command(&self) -> &'static str {
method accepted_parameters (line 330) | fn accepted_parameters(&self) -> u8 {
method description (line 334) | fn description(&self) -> &'static str {
method execute (line 338) | fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandEx...
type InstallCommand (line 78) | struct InstallCommand;
type UninstallCommand (line 79) | struct UninstallCommand;
type ReinstallCommand (line 80) | struct ReinstallCommand;
type RunCommand (line 81) | struct RunCommand;
type AutorunCommand (line 82) | struct AutorunCommand;
type StartCommand (line 83) | struct StartCommand;
type AutostartCommand (line 84) | struct AutostartCommand;
type StopCommand (line 85) | struct StopCommand;
type RestartCommand (line 86) | struct RestartCommand;
type TunnelCommand (line 87) | struct TunnelCommand;
type RelayCommand (line 88) | struct RelayCommand;
function cmd_install (line 344) | fn cmd_install(serial: Option<&str>) -> Result<(), CommandExecutionError> {
function cmd_uninstall (line 349) | fn cmd_uninstall(serial: Option<&str>) -> Result<(), CommandExecutionErr...
function cmd_reinstall (line 354) | fn cmd_reinstall(serial: Option<&str>) -> Result<(), CommandExecutionErr...
function cmd_run (line 360) | fn cmd_run(
function cmd_autorun (line 385) | fn cmd_autorun(
function cmd_start (line 405) | fn cmd_start(
function cmd_autostart (line 439) | fn cmd_autostart(
function cmd_stop (line 455) | fn cmd_stop(serial: Option<&str>) -> Result<(), CommandExecutionError> {
function cmd_tunnel (line 471) | fn cmd_tunnel(serial: Option<&str>, port: u16) -> Result<(), CommandExec...
function cmd_relay (line 482) | fn cmd_relay(port: u16) -> Result<(), CommandExecutionError> {
function async_start (line 488) | fn async_start(serial: Option<&str>, dns_servers: Option<&str>, routes: ...
function create_adb_args (line 502) | fn create_adb_args<S: Into<String>>(serial: Option<&str>, args: Vec<S>) ...
function exec_adb (line 514) | fn exec_adb<S: Into<String>>(
function must_install_client (line 537) | fn must_install_client(serial: Option<&str>) -> Result<bool, CommandExec...
function print_usage (line 577) | fn print_usage() {
function append_command_usage (line 592) | fn append_command_usage(msg: &mut String, command: &dyn Command) {
function print_command_usage (line 616) | fn print_command_usage(command: &dyn Command) {
function main (line 622) | fn main() {
FILE: relay-rust/src/relay/binary.rs
constant MAX_STRING_PACKET_SIZE (line 21) | const MAX_STRING_PACKET_SIZE: usize = 20;
function to_byte_array (line 23) | pub fn to_byte_array(value: u32) -> [u8; 4] {
function build_packet_string (line 29) | pub fn build_packet_string(data: &[u8]) -> String {
function ptr_data_eq (line 49) | pub fn ptr_data_eq<T: ?Sized>(lhs: *const T, rhs: *const T) -> bool {
FILE: relay-rust/src/relay/byte_buffer.rs
type ByteBuffer (line 20) | pub struct ByteBuffer {
method new (line 26) | pub fn new(length: usize) -> Self {
method read_from (line 33) | pub fn read_from<R: io::Read>(&mut self, source: &mut R) -> io::Result...
method peek (line 40) | pub fn peek(&self) -> &[u8] {
method peek_mut (line 44) | pub fn peek_mut(&mut self) -> &mut [u8] {
method consume (line 48) | pub fn consume(&mut self, length: usize) {
function produce_consume_byte_buffer (line 85) | fn produce_consume_byte_buffer() {
FILE: relay-rust/src/relay/client.rs
constant TAG (line 35) | const TAG: &str = "Client";
type Client (line 37) | pub struct Client {
method create (line 112) | pub fn create(
method id (line 150) | pub fn id(&self) -> u32 {
method router (line 154) | pub fn router(&mut self) -> &mut Router {
method channel (line 158) | pub fn channel(&mut self) -> ClientChannel {
method close (line 167) | fn close(&mut self, selector: &mut Selector) {
method on_ready (line 178) | fn on_ready(&mut self, selector: &mut Selector, event: Event) {
method process (line 190) | fn process(&mut self, selector: &mut Selector, event: Event) -> io::Re...
method process_send (line 207) | fn process_send(&mut self, selector: &mut Selector) -> io::Result<()> {
method process_receive (line 237) | fn process_receive(&mut self, selector: &mut Selector) -> io::Result<(...
method send_to_client (line 256) | pub fn send_to_client(
method register_pending_packet_source (line 274) | pub fn register_pending_packet_source(&mut self, source: Rc<RefCell<dy...
method send_id (line 278) | fn send_id(&mut self) -> io::Result<()> {
method update_interests (line 286) | fn update_interests(&mut self, selector: &mut Selector) {
method read (line 290) | fn read(&mut self) -> io::Result<bool> {
method write (line 294) | fn write(&mut self) -> io::Result<()> {
method push_to_network (line 299) | fn push_to_network(&mut self, selector: &mut Selector) {
method push_one_packet_to_network (line 305) | fn push_one_packet_to_network(&mut self, selector: &mut Selector) -> b...
method process_pending (line 322) | fn process_pending(&mut self, selector: &mut Selector) {
method clean_expired_connections (line 353) | pub fn clean_expired_connections(&mut self, selector: &mut Selector) {
method must_send_id (line 357) | fn must_send_id(&self) -> bool {
type ClientChannel (line 53) | pub struct ClientChannel<'a> {
function new (line 61) | fn new(
function send_to_client (line 77) | pub fn send_to_client(
function update_interests (line 95) | fn update_interests(&mut self, selector: &mut Selector) {
FILE: relay-rust/src/relay/close_listener.rs
type CloseListener (line 17) | pub trait CloseListener<T> {
method on_closed (line 18) | fn on_closed(&self, target: &T);
method on_closed (line 25) | fn on_closed(&self, target: &T) {
FILE: relay-rust/src/relay/connection.rs
constant LOCALHOST_FORWARD (line 27) | const LOCALHOST_FORWARD: u32 = 0x0A_00_02_02;
constant LOCALHOST (line 28) | const LOCALHOST: u32 = 0x7F_00_00_01;
type Connection (line 30) | pub trait Connection {
method id (line 31) | fn id(&self) -> &ConnectionId;
method send_to_network (line 32) | fn send_to_network(
method close (line 38) | fn close(&mut self, selector: &mut Selector);
method is_expired (line 39) | fn is_expired(&self) -> bool;
method is_closed (line 40) | fn is_closed(&self) -> bool;
type ConnectionId (line 44) | pub struct ConnectionId {
method from_headers (line 54) | pub fn from_headers(
method protocol (line 77) | pub fn protocol(&self) -> Protocol {
method rewritten_destination (line 81) | pub fn rewritten_destination(&self) -> SocketAddrV4 {
method fmt (line 92) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
FILE: relay-rust/src/relay/datagram.rs
constant MAX_DATAGRAM_LENGTH (line 21) | pub const MAX_DATAGRAM_LENGTH: usize = 1 << 16;
type DatagramSender (line 23) | pub trait DatagramSender {
method send (line 24) | fn send(&mut self, buf: &[u8]) -> io::Result<usize>;
method send (line 33) | fn send(&mut self, buf: &[u8]) -> io::Result<usize> {
method send (line 112) | fn send(&mut self, buf: &[u8]) -> io::Result<usize> {
type DatagramReceiver (line 27) | pub trait DatagramReceiver {
method recv (line 28) | fn recv(&mut self, buf: &mut [u8]) -> io::Result<usize>;
method recv (line 41) | fn recv(&mut self, buf: &mut [u8]) -> io::Result<usize> {
method recv (line 72) | fn recv(&mut self, buf: &mut [u8]) -> io::Result<usize> {
method recv (line 121) | fn recv(&mut self, buf: &mut [u8]) -> io::Result<usize> {
type ReadAdapter (line 48) | pub struct ReadAdapter<'a, R>
function new (line 60) | pub fn new(read: &'a mut R, max_chunk_size: Option<usize>) -> Self {
type MockDatagramSocket (line 87) | pub struct MockDatagramSocket {
method new (line 93) | pub fn new() -> Self {
method from_data (line 100) | pub fn from_data(data: &[u8]) -> Self {
method data (line 106) | pub fn data(&self) -> &[u8] {
function mock_send (line 129) | fn mock_send() {
function mock_recv (line 138) | fn mock_recv() {
function read_adapter (line 147) | fn read_adapter() {
function read_adapter_chunks (line 157) | fn read_adapter_chunks() {
FILE: relay-rust/src/relay/datagram_buffer.rs
constant HEADER_LENGTH (line 23) | const HEADER_LENGTH: usize = 2;
constant MAX_BLOCK_LENGTH (line 24) | const MAX_BLOCK_LENGTH: usize = HEADER_LENGTH + MAX_DATAGRAM_LENGTH;
constant TAG (line 26) | const TAG: &str = "DatagramBuffer";
type DatagramBuffer (line 40) | pub struct DatagramBuffer {
method new (line 48) | pub fn new(capacity: usize) -> Self {
method is_empty (line 57) | pub fn is_empty(&self) -> bool {
method has_enough_space_for (line 61) | pub fn has_enough_space_for(&self, datagram_length: usize) -> bool {
method write_to (line 71) | pub fn write_to<S: DatagramSender>(&mut self, destination: &mut S) -> ...
method read_from (line 96) | pub fn read_from(&mut self, source: &[u8]) -> io::Result<()> {
method read_length (line 119) | fn read_length(&mut self) -> u16 {
method write_length (line 124) | fn write_length(&mut self, length: u16) {
function create_datagram (line 135) | fn create_datagram(length: u8) -> Vec<u8> {
function bufferize_datagram (line 140) | fn bufferize_datagram() {
function split_datagrams_at_boundaries (line 149) | fn split_datagrams_at_boundaries() {
function circular (line 169) | fn circular() {
function read_datagram (line 191) | fn read_datagram(datagram_buffer: &mut DatagramBuffer) -> Vec<u8> {
FILE: relay-rust/src/relay/ipv4_header.rs
type Ipv4Header (line 20) | pub struct Ipv4Header<'a> {
type Ipv4HeaderMut (line 25) | pub struct Ipv4HeaderMut<'a> {
type Ipv4HeaderData (line 31) | pub struct Ipv4HeaderData {
method parse (line 49) | pub fn parse(raw: &[u8]) -> Self {
method bind (line 64) | pub fn bind<'c, 'a: 'c, 'b: 'c>(&'a self, raw: &'b [u8]) -> Ipv4Header...
method bind_mut (line 68) | pub fn bind_mut<'c, 'a: 'c, 'b: 'c>(&'a mut self, raw: &'b mut [u8]) -...
method header_length (line 72) | pub fn header_length(&self) -> u8 {
method total_length (line 76) | pub fn total_length(&self) -> u16 {
method protocol (line 80) | pub fn protocol(&self) -> Protocol {
method source (line 84) | pub fn source(&self) -> u32 {
method destination (line 88) | pub fn destination(&self) -> u32 {
type Protocol (line 41) | pub enum Protocol {
function peek_version_length (line 93) | pub fn peek_version_length(raw: &[u8]) -> Option<(u8, u16)> {
function raw_mut (line 155) | pub fn raw_mut(&mut self) -> &mut [u8] {
function data_mut (line 159) | pub fn data_mut(&mut self) -> &mut Ipv4HeaderData {
function set_total_length (line 163) | pub fn set_total_length(&mut self, total_length: u16) {
function set_source (line 168) | pub fn set_source(&mut self, source: u32) {
function set_destination (line 173) | pub fn set_destination(&mut self, destination: u32) {
function swap_source_and_destination (line 178) | pub fn swap_source_and_destination(&mut self) {
function checksum (line 185) | fn checksum(&self) -> u16 {
function set_checksum (line 189) | fn set_checksum(&mut self, checksum: u16) {
function update_checksum (line 193) | pub fn update_checksum(&mut self) {
function create_header (line 215) | fn create_header() -> Vec<u8> {
function parse_header (line 231) | fn parse_header() {
function edit_header (line 243) | fn edit_header() {
function compute_checksum (line 275) | fn compute_checksum() {
function peek_version_length_unavailable (line 295) | fn peek_version_length_unavailable() {
function peek_version_length_available (line 303) | fn peek_version_length_available() {
FILE: relay-rust/src/relay/ipv4_packet.rs
constant MAX_PACKET_LENGTH (line 20) | pub const MAX_PACKET_LENGTH: usize = 1 << 16;
type Ipv4Packet (line 22) | pub struct Ipv4Packet<'a> {
function parse (line 29) | pub fn parse(raw: &'a mut [u8]) -> Self {
function new (line 42) | pub fn new(
function raw (line 55) | pub fn raw(&self) -> &[u8] {
function headers_data (line 60) | pub fn headers_data(&self) -> (&Ipv4HeaderData, Option<&TransportHeaderD...
function headers (line 64) | pub fn headers(&self) -> (Ipv4Header, Option<TransportHeader>) {
function ipv4_header_data (line 83) | pub fn ipv4_header_data(&self) -> &Ipv4HeaderData {
function ipv4_header (line 89) | pub fn ipv4_header(&self) -> Ipv4Header {
function ipv4_header_mut (line 96) | pub fn ipv4_header_mut(&mut self) -> Ipv4HeaderMut {
function transport_header_data (line 103) | pub fn transport_header_data(&self) -> Option<&TransportHeaderData> {
function transport_header (line 108) | pub fn transport_header(&self) -> Option<TransportHeader> {
function transport_header_mut (line 127) | fn transport_header_mut(&mut self) -> Option<TransportHeaderMut> {
function split (line 149) | pub fn split(&self) -> (Ipv4Header, Option<(TransportHeader, &[u8])>) {
function split_mut (line 170) | pub fn split_mut(&mut self) -> (Ipv4HeaderMut, Option<(TransportHeaderMu...
function is_valid (line 189) | pub fn is_valid(&self) -> bool {
function length (line 194) | pub fn length(&self) -> u16 {
function payload (line 198) | pub fn payload(&self) -> Option<&[u8]> {
function compute_checksums (line 208) | pub fn compute_checksums(&mut self) {
function create_packet (line 231) | fn create_packet() -> Vec<u8> {
function parse_headers (line 256) | fn parse_headers() {
function payload (line 280) | fn payload() {
FILE: relay-rust/src/relay/ipv4_packet_buffer.rs
type Ipv4PacketBuffer (line 25) | pub struct Ipv4PacketBuffer {
method new (line 30) | pub fn new() -> Self {
method read_from (line 36) | pub fn read_from<R: io::Read>(&mut self, source: &mut R) -> io::Result...
method available_packet_length (line 40) | fn available_packet_length(&self) -> Option<u16> {
method as_ipv4_packet (line 58) | pub fn as_ipv4_packet(&mut self) -> Option<Ipv4Packet> {
method next (line 67) | pub fn next(&mut self) {
function create_packet (line 84) | fn create_packet() -> Vec<u8> {
function write_packet_to (line 90) | fn write_packet_to(raw: &mut Vec<u8>) {
function write_another_packet_to (line 109) | fn write_another_packet_to(raw: &mut Vec<u8>) {
function check_packet_headers (line 128) | fn check_packet_headers(ipv4_packet: &Ipv4Packet) {
function check_another_packet_headers (line 145) | fn check_another_packet_headers(ipv4_packet: &Ipv4Packet) {
function parse_ipv4_packet_buffer (line 163) | fn parse_ipv4_packet_buffer() {
function parse_fragmented_ipv4_packet_buffer (line 175) | fn parse_fragmented_ipv4_packet_buffer() {
function create_multi_packets (line 191) | fn create_multi_packets() -> Vec<u8> {
function parse_multi_packets (line 200) | fn parse_multi_packets() {
FILE: relay-rust/src/relay/net.rs
function to_addr (line 20) | pub fn to_addr(ipv4: u32) -> Ipv4Addr {
function to_socket_addr (line 25) | pub fn to_socket_addr(ipv4: u32, port: u16) -> SocketAddrV4 {
FILE: relay-rust/src/relay/packet_source.rs
type PacketSource (line 29) | pub trait PacketSource {
method get (line 30) | fn get(&mut self) -> Option<Ipv4Packet>;
method next (line 31) | fn next(&mut self, selector: &mut Selector);
FILE: relay-rust/src/relay/packetizer.rs
type Packetizer (line 25) | pub struct Packetizer {
method new (line 34) | pub fn new(
method packetize_empty_payload (line 69) | pub fn packetize_empty_payload(&mut self) -> Ipv4Packet {
method packetize (line 73) | pub fn packetize<R: DatagramReceiver>(&mut self, source: &mut R) -> io...
method packetize_read (line 84) | pub fn packetize_read<R: io::Read>(
method ipv4_header_mut (line 100) | pub fn ipv4_header_mut(&mut self) -> Ipv4HeaderMut {
method transport_header_mut (line 105) | pub fn transport_header_mut(&mut self) -> TransportHeaderMut {
method build (line 110) | fn build(&mut self, payload_length: u16) -> Ipv4Packet {
method inflate (line 126) | pub fn inflate(&mut self, packet_length: u16) -> Ipv4Packet {
function create_packet (line 142) | fn create_packet() -> Vec<u8> {
function merge_headers_and_payload (line 164) | fn merge_headers_and_payload() {
function last_packet (line 181) | fn last_packet() {
function packetize_chunks (line 199) | fn packetize_chunks() {
FILE: relay-rust/src/relay/relay.rs
constant TAG (line 30) | const TAG: &str = "Relay";
constant CLEANING_INTERVAL_SECONDS (line 31) | const CLEANING_INTERVAL_SECONDS: i64 = 60;
type Relay (line 33) | pub struct Relay {
method new (line 38) | pub fn new(port: u16) -> Self {
method run (line 42) | pub fn run(&self) -> io::Result<()> {
method poll_loop (line 49) | fn poll_loop(
FILE: relay-rust/src/relay/router.rs
constant TAG (line 31) | const TAG: &str = "Router";
type Router (line 33) | pub struct Router {
method new (line 40) | pub fn new() -> Self {
method set_client (line 48) | pub fn set_client(&mut self, client: Weak<RefCell<Client>>) {
method send_to_network (line 52) | pub fn send_to_network(
method connection (line 95) | fn connection(
method create_connection (line 116) | fn create_connection(
method find_index (line 146) | fn find_index(&self, id: &ConnectionId) -> Option<usize> {
method remove (line 152) | pub fn remove(&mut self, connection: &dyn Connection) {
method clear (line 169) | pub fn clear(&mut self, selector: &mut Selector) {
method clean_expired_connections (line 176) | pub fn clean_expired_connections(&mut self, selector: &mut Selector) {
FILE: relay-rust/src/relay/selector.rs
constant TAG (line 24) | const TAG: &str = "Selector";
type EventHandler (line 26) | pub trait EventHandler {
method on_ready (line 27) | fn on_ready(&self, selector: &mut Selector, event: Event);
method on_ready (line 34) | fn on_ready(&self, selector: &mut Selector, event: Event) {
type Selector (line 39) | pub struct Selector {
method create (line 47) | pub fn create() -> io::Result<Self> {
method register (line 55) | pub fn register<E, H>(
method reregister (line 76) | pub fn reregister<E>(
method deregister (line 89) | pub fn deregister<E>(&mut self, handle: &E, token: Token) -> io::Resul...
method clean_removed_tokens (line 99) | fn clean_removed_tokens(&mut self) {
method poll (line 106) | pub fn poll(&mut self, events: &mut Events, timeout: Option<Duration>)...
method run_handlers (line 110) | pub fn run_handlers(&mut self, events: &Events) {
FILE: relay-rust/src/relay/stream_buffer.rs
type StreamBuffer (line 20) | pub struct StreamBuffer {
method new (line 27) | pub fn new(capacity: usize) -> Self {
method is_empty (line 35) | pub fn is_empty(&self) -> bool {
method size (line 39) | pub fn size(&self) -> usize {
method capacity (line 47) | pub fn capacity(&self) -> usize {
method remaining (line 51) | pub fn remaining(&self) -> usize {
method write_to (line 55) | pub fn write_to<W: io::Write>(&mut self, destination: &mut W) -> io::R...
method read_from (line 76) | pub fn read_from(&mut self, source: &[u8]) {
method optimize (line 109) | fn optimize(&mut self) {
function create_data (line 121) | fn create_data() -> Vec<u8> {
function bufferize_data (line 126) | fn bufferize_data() {
function circular (line 138) | fn circular() {
function just_enough_space (line 165) | fn just_enough_space() {
function read_some (line 178) | fn read_some(stream_buffer: &mut StreamBuffer, bytes: usize) -> Vec<u8> {
function read (line 187) | fn read(stream_buffer: &mut StreamBuffer) -> Vec<u8> {
FILE: relay-rust/src/relay/tcp_connection.rs
constant TAG (line 39) | const TAG: &str = "TcpConnection";
constant MTU (line 42) | const MTU: u16 = 0x4000;
constant MAX_PAYLOAD_LENGTH (line 44) | const MAX_PAYLOAD_LENGTH: u16 = MTU - 20 - 20 as u16;
type TcpConnection (line 46) | pub struct TcpConnection {
method create (line 135) | pub fn create(
method create_stream (line 198) | fn create_stream(id: &ConnectionId) -> io::Result<TcpStream> {
method remove_from_router (line 202) | fn remove_from_router(&self) {
method on_ready (line 209) | fn on_ready(&mut self, selector: &mut Selector, event: Event) {
method process (line 220) | fn process(&mut self, selector: &mut Selector, event: Event) -> io::Re...
method process_send (line 252) | fn process_send(&mut self, selector: &mut Selector) -> io::Result<()> {
method process_receive (line 300) | fn process_receive(&mut self, selector: &mut Selector) -> io::Result<(...
method process_connect (line 366) | fn process_connect(&mut self, selector: &mut Selector) {
method send_to_client (line 374) | fn send_to_client(
method send_empty_packet_to_client (line 387) | fn send_empty_packet_to_client(&mut self, selector: &mut Selector, fla...
method reply_empty_packet_to_client (line 397) | fn reply_empty_packet_to_client(
method eof (line 420) | fn eof(&mut self, selector: &mut Selector) {
method tcp_header_of_transport (line 433) | fn tcp_header_of_transport(transport_header: TransportHeader) -> TcpHe...
method tcp_header_of_transport_mut (line 442) | fn tcp_header_of_transport_mut(transport_header: TransportHeaderMut) -...
method tcp_header_of_packet (line 451) | fn tcp_header_of_packet<'a>(ipv4_packet: &'a Ipv4Packet) -> TcpHeader<...
method update_headers (line 459) | fn update_headers(packetizer: &mut Packetizer, tcb: &Tcb, flags: u16) {
method handle_packet (line 466) | fn handle_packet(
method handle_first_packet (line 539) | fn handle_first_packet(
method handle_duplicate_syn (line 579) | fn handle_duplicate_syn(
method handle_fin (line 599) | fn handle_fin(&mut self, selector: &mut Selector, client_channel: &mut...
method do_handle_fin (line 619) | fn do_handle_fin(&mut self, selector: &mut Selector, client_channel: &...
method handle_fin_ack (line 651) | fn handle_fin_ack(&mut self, selector: &mut Selector) {
method handle_ack (line 667) | fn handle_ack(
method create_empty_response_packet (line 704) | fn create_empty_response_packet<'a>(
method update_interests (line 733) | fn update_interests(&mut self, selector: &mut Selector) {
method may_read (line 757) | fn may_read(&self) -> bool {
method may_write (line 768) | fn may_write(&self) -> bool {
type Tcb (line 61) | struct Tcb {
method new (line 100) | fn new() -> Self {
method remaining_client_window (line 113) | fn remaining_client_window(&self) -> u16 {
method numbers (line 125) | fn numbers(&self) -> String {
type TcpState (line 74) | enum TcpState {
method is_connected (line 87) | fn is_connected(&self) -> bool {
method is_closed (line 91) | fn is_closed(&self) -> bool {
method id (line 774) | fn id(&self) -> &ConnectionId {
method send_to_network (line 778) | fn send_to_network(
method close (line 790) | fn close(&mut self, selector: &mut Selector) {
method is_expired (line 806) | fn is_expired(&self) -> bool {
method is_closed (line 811) | fn is_closed(&self) -> bool {
method get (line 817) | fn get(&mut self) -> Option<Ipv4Packet> {
method next (line 825) | fn next(&mut self, selector: &mut Selector) {
FILE: relay-rust/src/relay/tcp_header.rs
type TcpHeader (line 21) | pub struct TcpHeader<'a> {
type TcpHeaderMut (line 26) | pub struct TcpHeaderMut<'a> {
type TcpHeaderData (line 32) | pub struct TcpHeaderData {
method parse (line 50) | pub fn parse(raw: &[u8]) -> Self {
method bind (line 64) | pub fn bind<'c, 'a: 'c, 'b: 'c>(&'a self, raw: &'b [u8]) -> TcpHeader<...
method bind_mut (line 69) | pub fn bind_mut<'c, 'a: 'c, 'b: 'c>(&'a mut self, raw: &'b mut [u8]) -...
method header_length (line 74) | pub fn header_length(&self) -> u8 {
method source_port (line 79) | pub fn source_port(&self) -> u16 {
method destination_port (line 84) | pub fn destination_port(&self) -> u16 {
method sequence_number (line 89) | pub fn sequence_number(&self) -> u32 {
method acknowledgement_number (line 94) | pub fn acknowledgement_number(&self) -> u32 {
method window (line 99) | pub fn window(&self) -> u16 {
method flags (line 104) | pub fn flags(&self) -> u16 {
method is_fin (line 109) | pub fn is_fin(&self) -> bool {
method is_syn (line 114) | pub fn is_syn(&self) -> bool {
method is_rst (line 119) | pub fn is_rst(&self) -> bool {
method is_psh (line 124) | pub fn is_psh(&self) -> bool {
method is_ack (line 129) | pub fn is_ack(&self) -> bool {
constant FLAG_FIN (line 42) | pub const FLAG_FIN: u16 = 1;
constant FLAG_SYN (line 43) | pub const FLAG_SYN: u16 = 1 << 1;
constant FLAG_RST (line 44) | pub const FLAG_RST: u16 = 1 << 2;
constant FLAG_PSH (line 45) | pub const FLAG_PSH: u16 = 1 << 3;
constant FLAG_ACK (line 46) | pub const FLAG_ACK: u16 = 1 << 4;
function raw_mut (line 227) | pub fn raw_mut(&mut self) -> &mut [u8] {
function data_mut (line 232) | pub fn data_mut(&mut self) -> &mut TcpHeaderData {
function set_source_port (line 237) | pub fn set_source_port(&mut self, source_port: u16) {
function set_destination_port (line 243) | pub fn set_destination_port(&mut self, destination_port: u16) {
function swap_source_and_destination (line 248) | pub fn swap_source_and_destination(&mut self) {
function set_sequence_number (line 256) | pub fn set_sequence_number(&mut self, sequence_number: u32) {
function set_acknowledgement_number (line 262) | pub fn set_acknowledgement_number(&mut self, acknowledgement_number: u32) {
function set_flags (line 268) | pub fn set_flags(&mut self, flags: u16) {
function shrink_options (line 277) | pub fn shrink_options(&mut self) {
function set_data_offset (line 282) | fn set_data_offset(&mut self, data_offset: u8) {
function checksum (line 290) | fn checksum(&self) -> u16 {
function set_checksum (line 295) | fn set_checksum(&mut self, checksum: u16) {
function update_checksum (line 299) | pub fn update_checksum(&mut self, ipv4_header_data: &Ipv4HeaderData, pay...
function create_packet (line 371) | fn create_packet() -> Vec<u8> {
function create_odd_packet (line 399) | fn create_odd_packet() -> Vec<u8> {
function create_empty_packet (line 429) | fn create_empty_packet() -> Vec<u8> {
function create_tcp_header (line 455) | fn create_tcp_header() -> Vec<u8> {
function edit_header (line 472) | fn edit_header() {
function compute_checksum (line 518) | fn compute_checksum() {
function compute_checksum_odd (line 560) | fn compute_checksum_odd() {
function compute_checksum_empty_payload (line 602) | fn compute_checksum_empty_payload() {
function create_long_packet (line 640) | fn create_long_packet() -> Vec<u8> {
function bench_checksum (line 676) | fn bench_checksum() {
FILE: relay-rust/src/relay/transport_header.rs
type TransportHeader (line 21) | pub enum TransportHeader<'a> {
type TransportHeaderMut (line 26) | pub enum TransportHeaderMut<'a> {
type TransportHeaderData (line 32) | pub enum TransportHeaderData {
method parse (line 39) | pub fn parse(protocol: Protocol, raw: &[u8]) -> Option<Self> {
method bind (line 48) | pub fn bind<'c, 'a: 'c, 'b: 'c>(&'a self, raw: &'b [u8]) -> TransportH...
method bind_mut (line 53) | pub fn bind_mut<'c, 'a: 'c, 'b: 'c>(&'a mut self, raw: &'b mut [u8]) -...
method source_port (line 58) | pub fn source_port(&self) -> u16 {
method destination_port (line 66) | pub fn destination_port(&self) -> u16 {
method header_length (line 74) | pub fn header_length(&self) -> u8 {
method from (line 204) | fn from(tcp_header_data: TcpHeaderData) -> TransportHeaderData {
method from (line 210) | fn from(udp_header_data: UdpHeaderData) -> TransportHeaderData {
function new (line 83) | pub fn new(raw: &'a [u8], data: &'a TransportHeaderData) -> Self {
function new (line 92) | pub fn new(raw: &'a mut [u8], data: &'a mut TransportHeaderData) -> Self {
function raw_mut (line 164) | pub fn raw_mut(&mut self) -> &mut [u8] {
function swap_source_and_destination (line 172) | pub fn swap_source_and_destination(&mut self) {
function set_payload_length (line 180) | pub fn set_payload_length(&mut self, payload_length: u16) {
function update_checksum (line 191) | pub fn update_checksum(&mut self, ipv4_header_data: &Ipv4HeaderData, pay...
function from (line 216) | fn from(tcp_header: TcpHeader) -> TransportHeader {
function from (line 222) | fn from(udp_header: UdpHeader) -> TransportHeader {
function from (line 228) | fn from(tcp_header: TcpHeaderMut) -> TransportHeaderMut {
function from (line 234) | fn from(udp_header: UdpHeaderMut) -> TransportHeaderMut {
FILE: relay-rust/src/relay/tunnel_server.rs
constant TAG (line 29) | const TAG: &str = "TunnelServer";
type TunnelServer (line 31) | pub struct TunnelServer {
method create (line 39) | pub fn create(port: u16, selector: &mut Selector) -> io::Result<Rc<Ref...
method start_socket (line 64) | fn start_socket(port: u16) -> io::Result<TcpListener> {
method on_ready (line 71) | fn on_ready(&mut self, selector: &mut Selector, _: Event) {
method accept_client (line 81) | fn accept_client(&mut self, selector: &mut Selector) -> io::Result<()> {
method remove_client (line 103) | fn remove_client(&mut self, client: &Client) {
method clean_up (line 116) | pub fn clean_up(&mut self, selector: &mut Selector) {
FILE: relay-rust/src/relay/udp_connection.rs
constant TAG (line 36) | const TAG: &str = "UdpConnection";
constant IDLE_TIMEOUT_SECONDS (line 38) | pub const IDLE_TIMEOUT_SECONDS: u64 = 2 * 60;
type UdpConnection (line 40) | pub struct UdpConnection {
method create (line 54) | pub fn create(
method create_socket (line 91) | fn create_socket(id: &ConnectionId) -> io::Result<UdpSocket> {
method remove_from_router (line 98) | fn remove_from_router(&self) {
method on_ready (line 105) | fn on_ready(&mut self, selector: &mut Selector, event: Event) {
method process (line 117) | fn process(&mut self, selector: &mut Selector, event: Event) -> io::Re...
method process_send (line 144) | fn process_send(&mut self, selector: &mut Selector) -> io::Result<()> {
method process_receive (line 169) | fn process_receive(&mut self, selector: &mut Selector) -> io::Result<(...
method read (line 190) | fn read(&mut self, selector: &mut Selector) -> io::Result<()> {
method write (line 218) | fn write(&mut self) -> io::Result<()> {
method update_interests (line 223) | fn update_interests(&mut self, selector: &mut Selector) {
method touch (line 239) | fn touch(&mut self) {
method id (line 245) | fn id(&self) -> &ConnectionId {
method send_to_network (line 249) | fn send_to_network(
method close (line 271) | fn close(&mut self, selector: &mut Selector) {
method is_expired (line 287) | fn is_expired(&self) -> bool {
method is_closed (line 291) | fn is_closed(&self) -> bool {
FILE: relay-rust/src/relay/udp_header.rs
constant UDP_HEADER_LENGTH (line 21) | pub const UDP_HEADER_LENGTH: u8 = 8;
type UdpHeader (line 23) | pub struct UdpHeader<'a> {
type UdpHeaderMut (line 28) | pub struct UdpHeaderMut<'a> {
type UdpHeaderData (line 34) | pub struct UdpHeaderData {
method parse (line 41) | pub fn parse(raw: &[u8]) -> Self {
method bind (line 49) | pub fn bind<'c, 'a: 'c, 'b: 'c>(&'a self, raw: &'b [u8]) -> UdpHeader<...
method bind_mut (line 54) | pub fn bind_mut<'c, 'a: 'c, 'b: 'c>(&'a mut self, raw: &'b mut [u8]) -...
method source_port (line 59) | pub fn source_port(&self) -> u16 {
method destination_port (line 64) | pub fn destination_port(&self) -> u16 {
function raw_mut (line 112) | pub fn raw_mut(&mut self) -> &mut [u8] {
function data_mut (line 117) | pub fn data_mut(&mut self) -> &mut UdpHeaderData {
function set_source_port (line 122) | pub fn set_source_port(&mut self, source_port: u16) {
function set_destination_port (line 128) | pub fn set_destination_port(&mut self, destination_port: u16) {
function swap_source_and_destination (line 133) | pub fn swap_source_and_destination(&mut self) {
function set_payload_length (line 141) | pub fn set_payload_length(&mut self, payload_length: u16) {
function set_checksum (line 147) | fn set_checksum(&mut self, checksum: u16) {
function update_checksum (line 152) | pub fn update_checksum(&mut self, _ipv4_header_data: &Ipv4HeaderData, _p...
function create_header (line 163) | fn create_header() -> Vec<u8> {
function parse_header (line 174) | fn parse_header() {
function edit_header (line 182) | fn edit_header() {
Condensed preview — 119 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (527K chars).
[
{
"path": ".gitignore",
"chars": 47,
"preview": "build/\n.gradle/\n.idea/\n*.iml\n/local.properties\n"
},
{
"path": "DEVELOP.md",
"chars": 18090,
"preview": "# Gnirehtet for developers\n\n\n## Getting started\n\n### Requirements\n\nYou need the [Android SDK] (_Android Studio_) and the"
},
{
"path": "LICENSE",
"chars": 11346,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 8011,
"preview": "# Gnirehtet (v2.5.1)\n\nThis project provides **reverse tethering** over `adb` for Android: it\nallows devices to use the i"
},
{
"path": "app/build.gradle",
"chars": 1082,
"preview": "apply plugin: 'com.android.application'\n\nandroid {\n compileSdkVersion rootProject.ext.compileSdkVersion\n buildTool"
},
{
"path": "app/proguard-rules.pro",
"chars": 652,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /h"
},
{
"path": "app/src/main/AndroidManifest.xml",
"chars": 1545,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:to"
},
{
"path": "app/src/main/java/com/genymobile/gnirehtet/Binary.java",
"chars": 1721,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "app/src/main/java/com/genymobile/gnirehtet/CIDR.java",
"chars": 2951,
"preview": "/*\n * Copyright (C) 2018 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "app/src/main/java/com/genymobile/gnirehtet/Forwarder.java",
"chars": 5897,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "app/src/main/java/com/genymobile/gnirehtet/GnirehtetActivity.java",
"chars": 3363,
"preview": "package com.genymobile.gnirehtet;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.net.VpnSer"
},
{
"path": "app/src/main/java/com/genymobile/gnirehtet/GnirehtetService.java",
"chars": 8092,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "app/src/main/java/com/genymobile/gnirehtet/IPPacketOutputStream.java",
"chars": 4573,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "app/src/main/java/com/genymobile/gnirehtet/InvalidCIDRException.java",
"chars": 1131,
"preview": "/*\n * Copyright (C) 2018 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "app/src/main/java/com/genymobile/gnirehtet/Net.java",
"chars": 2209,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "app/src/main/java/com/genymobile/gnirehtet/Notifier.java",
"chars": 3674,
"preview": "package com.genymobile.gnirehtet;\n\nimport android.annotation.TargetApi;\nimport android.app.Notification;\nimport android."
},
{
"path": "app/src/main/java/com/genymobile/gnirehtet/PersistentRelayTunnel.java",
"chars": 2852,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "app/src/main/java/com/genymobile/gnirehtet/RelayTunnel.java",
"chars": 3938,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "app/src/main/java/com/genymobile/gnirehtet/RelayTunnelListener.java",
"chars": 652,
"preview": "package com.genymobile.gnirehtet;\n\nimport android.os.Handler;\n\n/**\n * Convenient wrapper to dispatch events to the given"
},
{
"path": "app/src/main/java/com/genymobile/gnirehtet/RelayTunnelProvider.java",
"chars": 4616,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "app/src/main/java/com/genymobile/gnirehtet/Tunnel.java",
"chars": 869,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "app/src/main/java/com/genymobile/gnirehtet/VpnConfiguration.java",
"chars": 2489,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "app/src/main/res/drawable/ic_close_24dp.xml",
"chars": 391,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "app/src/main/res/drawable/ic_report_problem_24dp.xml",
"chars": 343,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "app/src/main/res/drawable/ic_usb_24dp.xml",
"chars": 617,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "app/src/main/res/values/strings.xml",
"chars": 297,
"preview": "<resources>\n <string name=\"app_name\" translatable=\"false\">Gnirehtet</string>\n <string name=\"relay_connected\">Rever"
},
{
"path": "app/src/main/res/values/styles.xml",
"chars": 591,
"preview": "<resources>\n\n <!-- Base application theme. -->\n <style name=\"AppTheme\">\n <!-- Customize your theme here. --"
},
{
"path": "app/src/main/res/values-fr/strings.xml",
"chars": 263,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"relay_connected\">Reverse tethering activé</string>\n"
},
{
"path": "app/src/test/java/com/genymobile/gnirehtet/TestIPPacketOutputSteam.java",
"chars": 3796,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "build.gradle",
"chars": 1305,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\next {\n compileSd"
},
{
"path": "config/android-checkstyle.gradle",
"chars": 642,
"preview": "apply plugin: 'checkstyle'\ncheck.dependsOn 'checkstyle'\n\ncheckstyle {\n toolVersion = '6.19'\n}\n\ntask checkstyle(type: "
},
{
"path": "config/android-signing.gradle",
"chars": 423,
"preview": "if (project.hasProperty(\"RELEASE_STORE_FILE\")) {\n android.signingConfigs {\n release {\n // to be def"
},
{
"path": "config/checkstyle/checkstyle.xml",
"chars": 6981,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE module PUBLIC\n \"-//Puppy Crawl//DTD Check Configuration 1.3//EN\"\n \"ht"
},
{
"path": "config/java-checkstyle.gradle",
"chars": 138,
"preview": "apply plugin: 'checkstyle'\n\ncheckstyle {\n toolVersion = '6.19'\n configFile = rootProject.file(\"config/checkstyle/c"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 233,
"preview": "#Sat Sep 07 21:43:49 CEST 2019\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER"
},
{
"path": "gradle.properties",
"chars": 730,
"preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will o"
},
{
"path": "gradlew",
"chars": 4971,
"preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n## Gradle start "
},
{
"path": "gradlew.bat",
"chars": 2404,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
},
{
"path": "relay-java/build.gradle",
"chars": 567,
"preview": "apply plugin: 'application'\n\nmainClassName = 'com.genymobile.gnirehtet.Main'\n\ndependencies {\n compile fileTree(dir: '"
},
{
"path": "relay-java/scripts/gnirehtet",
"chars": 41,
"preview": "#!/bin/bash\njava -jar gnirehtet.jar \"$@\"\n"
},
{
"path": "relay-java/scripts/gnirehtet-run.cmd",
"chars": 38,
"preview": "@java -jar gnirehtet.jar run\r\n@pause\r\n"
},
{
"path": "relay-java/scripts/gnirehtet.cmd",
"chars": 29,
"preview": "@java -jar gnirehtet.jar %*\r\n"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/AdbMonitor.java",
"chars": 8107,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/CommandLineArguments.java",
"chars": 3637,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/Main.java",
"chars": 18657,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/AbstractConnection.java",
"chars": 2999,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/Binary.java",
"chars": 2354,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/Client.java",
"chars": 6888,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/CloseListener.java",
"chars": 707,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/CommandExecutionException.java",
"chars": 1275,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/Connection.java",
"chars": 790,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/ConnectionId.java",
"chars": 3116,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/DatagramBuffer.java",
"chars": 3898,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/IPv4Header.java",
"chars": 5568,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/IPv4Packet.java",
"chars": 3179,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/IPv4PacketBuffer.java",
"chars": 2142,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/Log.java",
"chars": 3684,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/Net.java",
"chars": 2240,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/PacketSource.java",
"chars": 1138,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/Packetizer.java",
"chars": 2979,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/Relay.java",
"chars": 2149,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/Router.java",
"chars": 3804,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/SelectionHandler.java",
"chars": 764,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/StreamBuffer.java",
"chars": 3224,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/TCPConnection.java",
"chars": 16732,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/TCPHeader.java",
"chars": 6707,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/TransportHeader.java",
"chars": 1223,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/TunnelServer.java",
"chars": 2122,
"preview": "package com.genymobile.gnirehtet.relay;\n\nimport java.io.IOException;\nimport java.net.Inet4Address;\nimport java.net.InetS"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/UDPConnection.java",
"chars": 4862,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/UDPHeader.java",
"chars": 2428,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/test/java/com/genymobile/gnirehtet/AdbMonitorTest.java",
"chars": 4155,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/test/java/com/genymobile/gnirehtet/CommandLineArgumentsTest.java",
"chars": 3069,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/DatagramBufferTest.java",
"chars": 3990,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/IPv4HeaderTest.java",
"chars": 5518,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/IPv4PacketBufferTest.java",
"chars": 4797,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/IPv4PacketTest.java",
"chars": 3505,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/InetAddressTest.java",
"chars": 1206,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/PacketizerTest.java",
"chars": 4345,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/StreamBufferTest.java",
"chars": 4232,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/TCPHeaderTest.java",
"chars": 9638,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/UDPHeaderTest.java",
"chars": 3167,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/.gitignore",
"chars": 19,
"preview": "target/\n**/*.rs.bk\n"
},
{
"path": "relay-rust/Cargo.toml",
"chars": 566,
"preview": "[package]\nname = \"gnirehtet\"\nversion = \"2.5.1\"\nauthors = [\"Romain Vimont <rom@rom1v.com>\"]\nedition = \"2018\"\n\n[lib]\nname "
},
{
"path": "relay-rust/build.gradle",
"chars": 949,
"preview": "task debug(type: Exec) {\n commandLine 'cargo', 'build'\n}\n\ntask release(type: Exec) {\n commandLine 'cargo', 'build'"
},
{
"path": "relay-rust/scripts/gnirehtet-run.cmd",
"chars": 28,
"preview": "@gnirehtet.exe run\r\n@pause\r\n"
},
{
"path": "relay-rust/src/adb_monitor.rs",
"chars": 11864,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/cli_args.rs",
"chars": 5930,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/execution_error.rs",
"chars": 4323,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/lib.rs",
"chars": 758,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/logger.rs",
"chars": 1696,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/main.rs",
"chars": 20026,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/binary.rs",
"chars": 1788,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/byte_buffer.rs",
"chars": 2929,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/client.rs",
"chars": 11859,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/close_listener.rs",
"chars": 793,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/connection.rs",
"chars": 3855,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/datagram.rs",
"chars": 4924,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/datagram_buffer.rs",
"chars": 6293,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/interrupt.rs",
"chars": 1051,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/ipv4_header.rs",
"chars": 9078,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/ipv4_packet.rs",
"chars": 11092,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/ipv4_packet_buffer.rs",
"chars": 7508,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/mod.rs",
"chars": 1116,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/net.rs",
"chars": 923,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/packet_source.rs",
"chars": 1172,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/packetizer.rs",
"chars": 8644,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/relay.rs",
"chars": 2520,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/router.rs",
"chars": 6716,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/selector.rs",
"chars": 3328,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/stream_buffer.rs",
"chars": 6121,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/tcp_connection.rs",
"chars": 29550,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/tcp_header.rs",
"chars": 23271,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/transport_header.rs",
"chars": 7710,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/tunnel_server.rs",
"chars": 4089,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/udp_connection.rs",
"chars": 9680,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "relay-rust/src/relay/udp_header.rs",
"chars": 6167,
"preview": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
},
{
"path": "release",
"chars": 1523,
"preview": "#!/bin/bash\n# Make a gnirehtet release.\n#\n# Put your keystore properties into ~/.gradle/gradle.properties\n# (check app/b"
},
{
"path": "settings.gradle",
"chars": 45,
"preview": "include ':app', ':relay-java', ':relay-rust'\n"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the Genymobile/gnirehtet GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 119 files (487.8 KB), approximately 119.9k tokens, and a symbol index with 1110 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.