Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d29af5f
Add iOS app + TestFlight distribution pipeline
saharev1 Sep 11, 2026
96e9ded
docs: update project structure for iOS build
saharev1 Sep 11, 2026
de589fd
iOS app: add MAX transport, configurable port, crash hardening
saharev1 Sep 11, 2026
135f410
Fix crash: guard SOCKS5 handler and transport goroutines
saharev1 Sep 11, 2026
8efb908
yandex: safe config parsing instead of nil interface panics
saharev1 Sep 11, 2026
0b119b0
yandex: diagnostics + redirect cap in fetchDocInfo
saharev1 Sep 11, 2026
f0eae62
yandex: bound the WebSocket dial and log its outcome
saharev1 Sep 11, 2026
9165fbf
tunnel: add tun2socks PacketTunnel (foundation for iOS Network Extens…
saharev1 Sep 11, 2026
f1d69ef
ios: resolve names over DNS-over-TLS to defeat local DNS poisoning
saharev1 Sep 11, 2026
24829d5
yandex: exponential reconnect backoff instead of instant retry
saharev1 Sep 11, 2026
1dfb394
ios: add NEPacketTunnelProvider (system-wide VPN)
saharev1 Sep 11, 2026
2ab39fc
ios: keep NSExtension keys in the generated extension Info.plist
saharev1 Sep 11, 2026
d86bc9b
ne: bypass backend/DNS routes and cap extension memory
saharev1 Sep 11, 2026
2d7b835
ne: shrink gvisor TCP buffers to fit the extension memory cap
saharev1 Sep 11, 2026
a3821c5
ne: switch VPN to pure L3 forwarding (no gvisor in the extension)
saharev1 Sep 11, 2026
28dc3cc
exit-node: scope the RST-drop via --local-ip instead of host-wide
saharev1 Sep 11, 2026
ecd1446
logging: debug off by default; runtime toggle (perf)
saharev1 Sep 11, 2026
5f51bff
exit-node: aggressive GC to survive on small VPS
saharev1 Sep 11, 2026
beb41e0
fix: alias runtime/debug (shadowed by --debug flag var)
saharev1 Sep 11, 2026
dae0ef5
ne: harden L3 packet path against crashes under load
saharev1 Sep 11, 2026
ecab79d
ne: ICMP port-unreachable for dropped UDP (fast QUIC->TCP fallback)
saharev1 Sep 11, 2026
8be7cda
ios: add info button with donation addresses
saharev1 Sep 11, 2026
d2210ba
change icon
saharev1 Sep 11, 2026
9bc36b4
Feat: fix of RST
saharev1 Sep 12, 2026
3f077e2
ios: auto-reconnect (on-demand) + configurable DoT resolver
saharev1 Sep 12, 2026
3943c5e
transport: add VOLGA (vyandex) as a secondary transport
saharev1 Sep 12, 2026
9e1a13a
Батчинг+zstd в транспорте; мультиплекс, UDP/L3, надёжность iOS
saharev1 Sep 14, 2026
a1b2925
Допушил
saharev1 Sep 14, 2026
ceed076
инструкция для вставки конфигурации
saharev1 Sep 14, 2026
b8f2f07
Авто-согласующий кодек: обновление не ломает старые ноды
saharev1 Sep 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,14 @@ Thumbs.db

.gradle/
*.hprof

# iOS build artifacts
output/
ios-app/build/
ios-app/Lib/*.a
ios-app/Lib/*.h
ios-app/OpenFlux.xcodeproj/
ios-app/DerivedData/

# App Store Connect API private key — never commit
*.p8
49 changes: 37 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,25 @@ Client side runs a SOCKS5 proxy, exit node decapsulates and forwards packets to
## Structure

```
universal-bypass-tool/
├── main.go
OpenFlux/
├── main.go # CLI entry (client / exit-node)
├── export_ios.go # cgo bridge for the iOS static library (build tag: ios)
├── transport/
│ ├── transport.go # Transport interface
│ └── yandex/ # Yandex Docs backend
│ └── oneme/ # MAX Messenger backend
│ ├── transport.go # Transport interface
│ ├── compressor.go # Compression wrapper
│ ├── yandex/ # Yandex Docs backend
│ └── oneme/ # MAX Messenger backend
├── tunnel/
│ ├── tunnel.go # TCP tunnel core
│ ├── endpoint.go # Virtual NIC
│ └── rawsocket.go # Raw socket (exit node)
├── socks5/ # SOCKS5 server
├── network/ # Checksums, packet parsing
└── utils/ # Debug logging
│ ├── tunnel.go # TCP tunnel core
│ ├── endpoint.go # Virtual NIC
│ └── rawsocket_{linux,darwin,windows}.go # Raw socket (exit node), per-OS
├── socks5/ # SOCKS5 server
├── network/ # Checksums, packet parsing
├── utils/ # Logging
├── ios-app/ # SwiftUI iOS client (XcodeGen), links liboflux.a
├── build_ios.sh # Build the iOS static library (liboflux.a)
├── build_ios_app.sh # Build + archive + export the iOS app IPA
└── build_android.sh # Build the Android client binary
```

## Build (desktop client / exit-node binary)
Expand Down Expand Up @@ -66,7 +72,26 @@ export XCODE_PATH="<your Xcode.app path>" # optional, defaults to /Applications/
1. You must have root access on exit node machine;
2. Only legacy Yandex document editor is supported (you can toggle this setting from the interface).

Setup commands for exit node:
The exit node's TCP connections live in a userspace stack (gvisor), so the
kernel has no socket for them and would send an RST on every reply, tearing
the tunnel down. That RST must be suppressed — but do it **scoped**, not
host-wide. A blanket `-j DROP` on all outbound RSTs makes every closed port
answer with silence (scanners see `filtered` instead of `closed`) and stops
the host from resetting unrelated connections.

Recommended (scoped to a dedicated egress IP):
```bash
# give the box a second/alias IP for the tunnel, e.g. 203.0.113.10
sudo iptables -A OUTPUT -p tcp --tcp-flags RST RST -s 203.0.113.10 -j DROP
sudo ./universal-bypass-tool --exit-node --local-ip 203.0.113.10 \
--url "YOUR_YANDEX_DOC_URL" --debug
```
Even cleaner: run the exit node in its own network namespace / container so the
rule never touches the host's main services. Note that `-m owner --uid-owner`
does **not** work here — the tunnel-breaking RSTs are generated by the kernel
with no owning socket, so the owner match never fires.

Host-wide fallback (only on a single-purpose box, understanding the trade-off):
```bash
sudo iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP
sudo ./universal-bypass-tool --exit-node --url "YOUR_YANDEX_DOC_URL" --debug
Expand Down
50 changes: 38 additions & 12 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,25 @@ TCP-пакеты передаются через Transport. На данный м
## Структура

```
universal-bypass-tool/
├── main.go
OpenFlux/
├── main.go # Точка входа CLI (клиент / выходная нода)
├── export_ios.go # cgo-мост для статической библиотеки iOS (build tag: ios)
├── transport/
│ ├── transport.go # Transport interface
│ └── yandex/ # Yandex Docs backend
│ └── oneme/ # MAX Messenger backend
│ ├── transport.go # Интерфейс Transport
│ ├── compressor.go # Обёртка сжатия
│ ├── yandex/ # Бэкенд Yandex Docs
│ └── oneme/ # Бэкенд MAX Messenger
├── tunnel/
│ ├── tunnel.go # TCP tunnel core
│ ├── endpoint.go # Virtual NIC
│ └── rawsocket.go # Raw socket (exit node)
├── socks5/ # SOCKS5 server
├── network/ # Checksums, packet parsing
└── utils/ # Debug logging
│ ├── tunnel.go # Ядро TCP-тоннеля
│ ├── endpoint.go # Виртуальный NIC
│ └── rawsocket_{linux,darwin,windows}.go # Raw-сокет (выходная нода), по ОС
├── socks5/ # SOCKS5-сервер
├── network/ # Контрольные суммы, разбор пакетов
├── utils/ # Логирование
├── ios-app/ # iOS-клиент на SwiftUI (XcodeGen), линкует liboflux.a
├── build_ios.sh # Сборка статической библиотеки iOS (liboflux.a)
├── build_ios_app.sh # Сборка + архив + экспорт IPA приложения iOS
└── build_android.sh # Сборка клиентского бинарника Android
```

## Сборка (бинарник десктоп-клиента / выходной ноды)
Expand Down Expand Up @@ -66,7 +72,27 @@ export XCODE_PATH="<путь до вашего Xcode.app>" # опциональ
1. У вас должен быть root-доступ выходной ноде;
2. Поддерживается только устаревший редактор документов Yandex (переключается в настройках интерфейса).

Команды для настройки выходной ноды:
TCP-соединения выходной ноды живут в userspace-стеке (gvisor), у ядра нет для
них сокета, и оно слало бы RST на каждый ответный пакет — туннель бы рвался.
Этот RST надо подавить, но **точечно**, не на весь хост. Глухое
`-j DROP` на все исходящие RST превращает закрытые порты в «молчащие»
(сканер видит `filtered` вместо `closed`) и мешает хосту нормально сбрасывать
посторонние соединения.

Рекомендуется (сужение по выделенному egress-IP):
```bash
# повесьте на машину второй/алиас IP под туннель, напр. 203.0.113.10
sudo iptables -A OUTPUT -p tcp --tcp-flags RST RST -s 203.0.113.10 -j DROP
sudo ./universal-bypass-tool --exit-node --local-ip 203.0.113.10 \
--url "YOUR_YANDEX_DOC_URL" --debug
```
Ещё чище — запускать ноду в отдельном network namespace / контейнере, тогда
правило вообще не трогает сервисы хоста. `-m owner --uid-owner` тут **не
работает**: рвущие туннель RST генерит ядро без сокета-владельца, и owner-матч
не срабатывает.

Запасной вариант на весь хост (только на однозадачной машине, с пониманием
последствий):
```bash
sudo iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP
sudo ./universal-bypass-tool --exit-node --url "YOUR_YANDEX_DOC_URL" --debug
Expand Down
116 changes: 116 additions & 0 deletions USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Usage

How to run an exit node and point a client at it. See [README](README.md) for
build instructions.

```
Client (iOS app / SOCKS5) ──▶ Transport (Yandex.Docs) ──▶ Exit node ──▶ Internet
```

The client and the exit node talk **through a shared Yandex document**: the
client writes tunnel data into the document's editor channel, the exit node
reads it, forwards it to the real Internet, and writes the replies back.

## 1. Prepare a document

1. Create a document on Yandex.Docs / Yandex.Disk (a collaborative editor
document, not a plain file).
2. Make it **publicly accessible with edit rights** (anyone with the link can
edit) and copy the share link, e.g. `https://disk.yandex.ru/i/XXXXXXXXXXXX`.
3. Match the document type to the transport:
- `yandex` (classic) — a standard collaborative document.
- `volga` / `vyandex` — a document served through the office-online editor.

The same link is used on **both** the exit node and the client.

## 2. Run the exit node

The exit node needs root (raw sockets) and a Linux host with a public IP.

```bash
sudo ./universal-bypass-tool \
--exit-node \
--transport yandex \
--url "https://disk.yandex.ru/i/XXXXXXXXXXXX"
```

The exit node forwards raw traffic, so the kernel's own RST/ICMP replies would
tear down tunneled connections. Scope the drop rules to a dedicated egress IP:

```bash
# assign an alias IP, then run the node with --local-ip <ip> and:
sudo iptables -A OUTPUT -p tcp --tcp-flags RST RST -s <ip> -j DROP
sudo iptables -A OUTPUT -p icmp --icmp-type port-unreachable -s <ip> -j DROP
```

A healthy start prints `OPENFLUX_READY transport=… mode=exit-node` and, per
document, `[YDOCS] WebSocket connected …`.

## 3. Configure the client

### iOS app

1. **Transport** — pick the same transport the exit node runs (`yandex` or
`volga`). They are not interchangeable.
2. **Document URL** — paste the same link the exit node uses.
3. Connect.

Instead of typing the fields by hand you can paste a single **`OFLUX1:`**
string (a compact base64url bundle of the transport type and document URL) and
tap **Import** — it fills in the fields for you.

Optional: choose a **DNS-over-TLS** resolver, and toggle **UDP** (QUIC/HTTP3)
forwarding.

### Desktop (SOCKS5)

```bash
./universal-bypass-tool \
--client \
--transport yandex \
--url "https://disk.yandex.ru/i/XXXXXXXXXXXX" \
--socks5 127.0.0.1:1080
```

Then point your application at the SOCKS5 proxy on `127.0.0.1:1080`.

## 4. Multiple documents (throughput / failover)

`--url` accepts a **comma-separated list** of documents. The client stripes
flows across them (one TCP flow stays on one document, in order) and keeps
working if one document's channel drops.

```bash
--url "https://disk.yandex.ru/i/AAAA,https://disk.yandex.ru/i/BBBB"
```

In the iOS app, paste the same comma-separated list (no spaces) in the document
field.

## Rules that must hold (client ↔ exit node)

These are the common causes of a stuck **`connecting`** state:

| Rule | Why |
|------|-----|
| **Same document list** on both sides — exact same URLs, same order. | With a multi-document exit node, reply traffic is spread across all documents; a client listening on fewer documents never receives the replies routed to the ones it is missing. |
| **Same transport** on both sides. | `yandex` and `volga` use different document channels and are not wire-compatible. |
| **Same build version** on both sides. | The wire codec is symmetric; a client and exit node on different versions can produce incompatible frames. |

If the client is stuck on `connecting`, check those three first, then confirm
the exit node log shows `WebSocket connected` for every document.

## Advanced: batch tuning

Outgoing packets are coalesced into one compressed frame per channel message.
The batch size can be tuned at runtime via environment variables (defaults in
parentheses):

| Variable | Meaning | Default |
|----------|---------|---------|
| `OPENFLUX_BATCH_BYTES` | max bytes per batch | `8192` |
| `OPENFLUX_BATCH_COUNT` | max packets per batch | `64` |
| `OPENFLUX_BATCH_LINGER_MS` | how long to wait for stragglers | `5` |

Larger batches cut the message count further but add latency; match them to the
channel's per-message limits.
24 changes: 2 additions & 22 deletions build_ios.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,32 +38,12 @@ echo "Building for iOS (arm64)..."
# Build static library
if go build \
-buildmode=c-archive \
-ldflags="-s -w" \
-ldflags="-w" \
-trimpath \
-o "$OUTPUT_DIR/$LIBRARY_NAME.a" \
. ; then

# Create header if not auto-generated
if [ ! -f "$OUTPUT_DIR/$LIBRARY_NAME.h" ]; then
cat > "$OUTPUT_DIR/$LIBRARY_NAME.h" << 'HEADEREOF'
#ifndef LIBTUNNEL_H
#define LIBTUNNEL_H

#ifdef __cplusplus
extern "C" {
#endif

void RunMain(void);
void RunMainClient(char* url);
void RunMainExitNode(void);

#ifdef __cplusplus
}
#endif

#endif /* LIBTUNNEL_H */
HEADEREOF
fi
# Header liboflux.h is generated automatically by cgo from //export directives.

echo "Build complete: $OUTPUT_DIR/$LIBRARY_NAME.a"
ls -lh "$OUTPUT_DIR/$LIBRARY_NAME.a"
Expand Down
54 changes: 54 additions & 0 deletions build_ios_app.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/bin/bash
# Full OpenFlux iOS pipeline: build Go lib -> generate Xcode project ->
# archive -> export App Store IPA -> (optional) upload to TestFlight.
#
# Requirements: Xcode, xcodegen (brew install xcodegen), Go 1.26+.
# Signing: automatic; your Apple ID must be logged into Xcode
# (Xcode > Settings > Accounts) and belong to team 8GQH8GQ252.
set -e

ROOT="$(cd "$(dirname "$0")" && pwd)"
APP_DIR="$ROOT/ios-app"
TEAM_ID="8GQH8GQ252"

echo "==> [1/5] Building Go static library (arm64, iOS)"
"$ROOT/build_ios.sh"

echo "==> [2/5] Syncing library into the app"
mkdir -p "$APP_DIR/Lib"
cp "$ROOT/output/ios/liboflux.a" "$APP_DIR/Lib/liboflux.a"
cp "$ROOT/output/ios/liboflux.h" "$APP_DIR/Lib/liboflux.h"

echo "==> [3/5] Generating Xcode project"
cd "$APP_DIR"
xcodegen generate

echo "==> [4/5] Archiving (Release)"
rm -rf build/OpenFlux.xcarchive
xcodebuild -project OpenFlux.xcodeproj -scheme OpenFlux -configuration Release \
-destination 'generic/platform=iOS' \
-archivePath build/OpenFlux.xcarchive \
-allowProvisioningUpdates \
clean archive

echo "==> [5/5] Exporting App Store IPA"
rm -rf build/export
xcodebuild -exportArchive \
-archivePath build/OpenFlux.xcarchive \
-exportPath build/export \
-exportOptionsPlist ExportOptions.plist \
-allowProvisioningUpdates

echo ""
echo "IPA ready: $APP_DIR/build/export/OpenFlux.ipa"
echo ""
echo "To upload to TestFlight, first create the app record in App Store Connect"
echo "(My Apps > + > New App, bundle id com.p1neapplexpress-saharev.openflux), then run:"
echo ""
echo " # Option A - app-specific password (appleid.apple.com > App-Specific Passwords):"
echo " xcrun altool --upload-app -f build/export/OpenFlux.ipa -t ios \\"
echo " -u YOUR_APPLE_ID -p xxxx-xxxx-xxxx-xxxx"
echo ""
echo " # Option B - App Store Connect API key (.p8 in ~/.appstoreconnect/private_keys/):"
echo " xcrun altool --upload-app -f build/export/OpenFlux.ipa -t ios \\"
echo " --apiKey KEY_ID --apiIssuer ISSUER_ID"
Loading