Skip to content

Repository files navigation

FreeProxy

English | 简体中文

An Android client that turns an HTTP / SOCKS5 proxy into a system-wide VPN.

Traffic from every app — or only the ones you pick — is captured by VpnService as raw IP packets, reassembled into TCP / UDP sessions in userspace, and forwarded through the upstream proxy. Apps with no proxy support of their own end up going through it anyway.

Features

  • Profile management: multiple SOCKS5 / HTTP CONNECT profiles with username/password auth; duplicate, edit and delete, plus a connectivity test that performs a real handshake before saving
  • Per-app routing: three modes — all apps / allow list (only the selected ones are proxied) / bypass list (the selected ones go direct)
  • Dark mode: follow system, force light, force dark, plus Android 12+ dynamic colour
  • Notification status: an ongoing notification with live upload/download counters and a one-tap disconnect
  • Activity log: live throughput bar plus a colour-coded event stream recording DNS lookups and the outcome of every connection, labelled with the app that opened it
  • DNS resolution mode: each profile can send DNS through the proxy (privacy) or resolve locally (speed)
  • Tablets and large screens: at 600dp of window width the bottom bar becomes a navigation rail, content is centred within a readable width, and the editor becomes a centred card
  • English and Simplified Chinese, start on boot, stop when the network drops, bypass private networks, IPv6 leak blocking

Building

Requires JDK 17+ and Android SDK Platform 36.

./gradlew assembleDebug      # debug build
./gradlew assembleRelease    # release build (R8 enabled)
./gradlew testDebugUnitTest  # unit tests

Artifacts land in app/build/outputs/apk/.

sdk.dir in local.properties points at the local SDK — change it on another machine.

Implementation notes

Why a pure-Kotlin userspace network stack

The usual approach is to bundle a native library such as tun2socks, which drags in the NDK toolchain. This project instead implements a trimmed-down userspace TCP/IP stack in Kotlin: no NDK, no native artifacts, one build for every ABI.

The key simplification: packets written to the TUN device are handed to the local kernel and never cross a lossy link, so congestion control is unnecessary. As long as the peer's advertised receive window is respected nothing is dropped, and retransmission exists only as a backstop. That keeps TcpSession far simpler than a complete TCP implementation while staying reliable in this local setting.

Data flow

app → system routing → TUN device
                         ↓  reader thread
                   Ipv4Header / TcpHeader / UdpHeader parsing
                         ↓
             TcpSession (userspace state machine) / UdpSession
                         ↓  protect()-ed socket
                   ProxyClient (SOCKS5 handshake / HTTP CONNECT)
                         ↓
                     upstream proxy → destination

Return traffic is re-encapsulated into IP packets by PacketBuilder and sent back to the TUN device through a write queue.

A few things worth knowing

  • The proxy hostname must be resolved up front: once the tunnel is up, DNS queries are routed into it — and the tunnel itself depends on that lookup. FreeProxyVpnService therefore resolves the proxy host before establish() and reuses the address for the whole session.
  • Bypassing LAN traffic is routing, not dropping: RouteCalculator recursively subtracts RFC 1918 and friends from 0.0.0.0/0 and declares only the complement to the TUN device, so private addresses keep using the physical interface.
  • Automatic DNS downgrade: when the proxy cannot carry UDP (an HTTP proxy, or UDP ASSOCIATE turned off), DNS falls back to DNS-over-TCP (RFC 7766) through the proxy, so name resolution keeps working.
  • An empty allow list degrades on purpose: never calling Android's addAllowedApplication is equivalent to "allow everything", which is easy to misread, so an empty allow list falls back to global mode.
  • Where the hostnames in the log come from: inside the tunnel only IP addresses are visible. HostRegistry parses the DNS responses passing through and builds a reverse IP → hostname table, which is why connection entries can show github.com:443 instead of a bare IP.
  • App attribution in the log: ConnectivityManager.getConnectionOwnerUid maps the 4-tuple back to an owning UID and then to a package name. That API arrived in Android 10; on older systems the column stays empty.
  • Large screens are centred, not margin-computed: ResponsiveContainer constrains content to 640dp by centring it. Computing left/right margins from the screen width was tried first, but the navigation rail eats part of the width and the result drifts to one side. The breakpoint measures LocalWindowInfo.containerSize rather than the screen, so split-screen and foldables are handled correctly.

Dependency trade-offs

Neither Hilt nor Room is used: this app has one process, two repositories, one long-running service, and a few dozen profiles at most. A DataStore-backed JSON blob plus a hand-written container is enough — dropping the annotation processors makes builds faster and version conflicts rarer.

Version constraints

The toolchain is pinned at AGP 8.13.2 + compileSdk 36. Newer androidx.core:core (1.19+) and androidx.lifecycle (2.11+) require AGP 9.1 / compileSdk 37, so Android SDK Platform 37 has to be installed before upgrading.

Known limitations

Limitation Detail
No IPv6 forwarding IPv4 only. "Block IPv6" pulls IPv6 into the tunnel and drops it so it cannot leak around the proxy
No ICMP Proxy protocols cannot carry it, so ping inside the tunnel failing is expected
UDP depends on the proxy Needs SOCKS5 with UDP ASSOCIATE; otherwise only DNS works (over TCP)
No window scaling TCP Window Scaling is not negotiated, so per-connection throughput is capped by the 64KB window
No fragment reassembly Fragmented IP packets are dropped; the TUN MTU is set by this app, so normal traffic never fragments
Credentials stored in plaintext Proxy passwords live unencrypted in the app's private directory, excluded from cloud backup
App attribution needs Android 10+ It relies on getConnectionOwnerUid; older systems leave the column empty

Publishing to F-Droid

Store assets live in fastlane/metadata/android/{en-US,zh-CN}/; F-Droid picks up the descriptions, icon and screenshots from this repository automatically. Every release needs a new changelogs/<versionCode>.txt. After changing the icon, run art/render-icon.sh to re-export the 512×512 images/icon.png.

To submit: tag the release as vX.Y.Z, then copy fdroid/tech.xvanturing.freeproxy.yml into metadata/ in fdroiddata and open a merge request. Once merged, F-Droid's build server compiles from source and signs with their own key — meaning the F-Droid build and any self-signed APK are not upgrade-compatible.

Tests

app/src/test/ holds 19 unit tests covering route-complement calculation (including boundary addresses and overlap checks), IP/TCP/UDP packet build-parse round trips, checksum self-consistency, sequence-number wraparound comparison, and DNS message parsing (compression pointers, malformed messages, and infinite-loop defence against self-referencing pointers).

The full path needs a real device: install, add a proxy profile, tap connect and grant the VPN permission.

License

Apache License 2.0.

About

Route Android traffic through a SOCKS5 / HTTP proxy — pure-Kotlin userspace TCP/IP stack, no NDK | 把 SOCKS5 / HTTP 代理转换成 Android 系统级 VPN,纯 Kotlin 用户态协议栈,支持分应用代理

Topics

Resources

Stars

16 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages