Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.go text eol=lf
*.sh text eol=lf
*.html text eol=lf
34 changes: 20 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<a href="https://github.com/AminMGMT/BackPack/releases"><img alt="Total downloads across all releases" src="https://img.shields.io/github/downloads/AminMGMT/BackPack/total?logo=github&label=total%20downloads&color=orange"></a>
</p>

**Backpack** is a high-performance **reverse tunnel** engine written entirely in
**Backpack** is a high-performance **reverse tunnel and direct-forward** engine written entirely in
**Go**, purpose-built for Iran ⇄ abroad (kharej) server setups. It ships as a
single self-contained binary with an interactive CLI **and** a secured web
dashboard — so you can run and manage everything with or without a terminal.
Expand All @@ -23,18 +23,20 @@ dashboard — so you can run and manage everything with or without a terminal.

## Architecture

<p align="center"><img src="img/architecture.svg" alt="Backpack architecture: end users reach a forwarded port on the Iran server, the engine carries it through one transport to the kharej client, which forwards it to the real service. The client dials the server." width="100%"></p>
<p align="center"><img src="img/architecture.svg" alt="Backpack architecture: end users reach a forwarded port on the Iran server, and the selected transport carries traffic to the real service on Kharej." width="100%"></p>

An end user connects to a **forwarded port** on the Iran server; the engine
carries it through **one transport** to the kharej client, which forwards it to
the **real service**. The tunnel is always dialed **by the client**
(kharej → Iran), so the far side needs no open inbound port.
carries it through **one selected transport** to Kharej, which forwards it to
the **real service**. In Direct mode Iran dials Kharej; in legacy Reverse mode
Kharej dials Iran.

---

## Why Backpack?

- **Multi-transport** — nine transports across TCP, UDP and WebSocket, so you
- **Kernel direct forwarding** — dual-stack TCP/UDP DNAT with iptables,
transactional rule generations, ownership-safe cleanup and persistent counters.
- **Multi-transport** — ten production transports across TCP, UDP and WebSocket, so you
match the route instead of fighting it.
- **Automatic rollback** — an update or edit that breaks a tunnel reverts itself,
so you are never left with a dead tunnel.
Expand Down Expand Up @@ -130,7 +132,7 @@ after it.
| Server | Where | Menu option | Why |
|--------|-------|-------------|-----|
| **Iran server** | entry point | **Setup Server** | It exposes the ports; users connect to the **Iran IP** (fast, unfiltered for local users). |
| **Abroad (kharej)** | exit / origin | **Setup Client** | It dials the Iran server and forwards traffic to the real service (VPN panel, etc.). |
| **Abroad (kharej)** | exit / origin | **Setup Client** | It accepts Direct or dials Iran for Reverse, then forwards traffic to the real service (VPN panel, etc.). |

```
end users ──▶ Iran server (SERVER, exposes ports) ──tunnel──▶ Kharej (CLIENT, real service)
Expand All @@ -145,25 +147,29 @@ after it.
sudo backpack → 1. Setup Server
```

Pick the transport family (TCP / UDP / WebSocket) and then the variant, the
tunnel port and the exposed ports, accept the suggested **64-character token**
(press Enter), and choose a performance preset — **Turbo** is the recommended
default. Copy the token; you'll need it on the client.
Pick the transport family and its concrete variant, then choose the connection
mode. **Direct** keeps that exact transport but makes Iran initiate the tunnel
toward Kharej; **Reverse** keeps the legacy direction where Kharej initiates
toward Iran. In both modes accept the suggested **64-character token** (press
Enter), copy it to the other side, and configure the same transport and tunnel
port there. **Turbo** is the recommended performance preset.

### 2) On the abroad (kharej) server — create the Client tunnel

```bash
sudo backpack → 2. Setup Client
```

Enter the **Iran server IP**, the tunnel port and the **same token**. Done.
Choose the same transport and mode. For Direct, choose the local tunnel listen
port; for Reverse, enter the Iran server IP and tunnel port. Enter the **same
token** on both sides. Done.

---

## Features

**Transports** — TCP, TCP Mux, TCP + Stealth, UDP, UDP + KCP, WS, WS Mux,
WSS and WSS Mux, with connection pooling.
**Transports** — TCP, TCP Mux, TCP + Stealth, UDP, UDP + KCP, UDP + QUIC, WS,
WS Mux, WSS and WSS Mux, with connection pooling where applicable.

- **TCP + Stealth** — a TCP tunnel wrapped in a Noise layer with **no
fingerprint**; on the wire it looks like random bytes, so there is nothing for
Expand Down
33 changes: 17 additions & 16 deletions README_FA.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<a href="https://github.com/AminMGMT/BackPack/releases"><img alt="Total downloads across all releases" src="https://img.shields.io/github/downloads/AminMGMT/BackPack/total?logo=github&label=total%20downloads&color=orange"></a>
</p>

**بک‌پک** یک هسته‌ی تونل معکوس (Reverse Tunnel) با کارایی بالاست که کاملاً با **Go**
**بک‌پک** یک هسته‌ی تونل Direct و Reverse با کارایی بالاست که کاملاً با **Go**
نوشته شده و برای ست‌آپ سرور ایران ⇄ خارج طراحی شده. یک باینری واحد است با یک منوی
تعاملی CLI **و** یک پنل وب امن — یعنی همه‌چیز را با ترمینال یا بدون ترمینال می‌توانی
مدیریت کنی.
Expand All @@ -25,18 +25,17 @@

## معماری

<p align="center"><img src="img/architecture.svg" alt="معماری بک‌پک: کاربر به پورت forward‌شده روی سرور ایران وصل می‌شود، انجین آن را از یک ترنسپورت به کلاینت خارج می‌برد و کلاینت به سرویس واقعی می‌رساند. کلاینت به سرور dial می‌کند." width="100%"></p>
<p align="center"><img src="img/architecture.svg" alt="معماری بک‌پک: کاربر به پورت forward‌شده روی سرور ایران وصل می‌شود و ترنسپورت انتخابی ترافیک را به سرویس واقعی در خارج می‌رساند." width="100%"></p>

کاربر به یک **پورت forward‌شده** روی سرور ایران وصل می‌شود؛ انجین آن را از طریق
**یک ترنسپورت** به کلاینت خارج می‌برد و کلاینت آن را به **سرویس واقعی** می‌رساند.
خودِ تونل همیشه **توسط کلاینت** برقرار می‌شود (خارج → ایران)، پس سمت خارج نیازی به
پورت ورودی باز ندارد.
کاربر به یک **پورت forward‌شده** روی سرور ایران وصل می‌شود؛ انجین آن را با
**ترنسپورت انتخاب‌شده** به **سرویس واقعی** در خارج می‌رساند. در حالت Direct اتصال
تونل از ایران به خارج شروع می‌شود؛ در حالت قدیمی Reverse از خارج به ایران.

---

## چرا بک‌پک؟

- **چند-ترنسپورت (Multi-transport):** نه ترنسپورت روی TCP، UDP و WebSocket — به‌جای
- **چند-ترنسپورت (Multi-transport):** ده ترنسپورت production روی TCP، UDP و WebSocket — به‌جای
جنگیدن با مسیر، با آن هماهنگ می‌شوی.
- **بازگشت خودکار (Automatic rollback):** آپدیت یا ویرایشی که تونل را خراب کند
خودش برمی‌گردد عقب، پس هیچ‌وقت با تونل مرده تنها نمی‌مانی.
Expand Down Expand Up @@ -144,7 +143,7 @@ sudo backpack
| سرور | نقش | گزینه‌ی منو | چرا |
|------|-----|------------|-----|
| **سرور ایران** | ورودی | **Setup Server** | پورت‌ها را expose می‌کند؛ کاربر به **IP ایران** وصل می‌شود (سریع و بدون فیلتر). |
| **سرور خارج** | خروجی | **Setup Client** | به سرور ایران dial می‌کند و ترافیک را به سرویس واقعی می‌رساند. |
| **سرور خارج** | خروجی | **Setup Client** | در Direct اتصال ایران را می‌پذیرد و در Reverse به ایران dial می‌کند. |

</div>

Expand All @@ -154,8 +153,8 @@ sudo backpack

<div dir="rtl">

**همیشه اول سرور ایران (Server) را ست کن**، بعد سرور خارج (Client) را — کلاینت به
آدرس ایران و توکنی که سرور می‌سازد نیاز دارد.
اول سرور ایران (Server) را ست کن و mode، ترنسپورت، پورت تونل و توکن را یادداشت کن؛
بعد همان مقادیر را روی سرور خارج (Client) وارد کن.

### ۱) روی سرور ایران — ساخت تونل Server

Expand All @@ -167,9 +166,10 @@ sudo backpack → 1. Setup Server

<div dir="rtl">

اول خانواده‌ی ترنسپورت (TCP / UDP / WebSocket) و بعد نوعش را انتخاب کن، پورت تونل
و پورت‌های expose را بده، **توکن ۶۴ کاراکتری** پیشنهادی را با Enter بپذیر، و یک
پریست انتخاب کن — **Turbo** پیشنهاد پیش‌فرض است. توکن را کپی کن (برای کلاینت لازم است).
اول خانواده و سپس نوع دقیق ترنسپورت را انتخاب کن. قبل از سؤال پورت‌ها، روش اتصال
را انتخاب می‌کنی: **Direct** یعنی ایران به خارج متصل شود؛ **Reverse** یعنی روش قدیمی
خارج به ایران. پورت تونل و پورت‌های expose را بده، **توکن ۶۴ کاراکتری** پیشنهادی را
با Enter بپذیر و یک پریست انتخاب کن — **Turbo** پیشنهاد پیش‌فرض است.

### ۲) روی سرور خارج — ساخت تونل Client

Expand All @@ -181,14 +181,15 @@ sudo backpack → 2. Setup Client

<div dir="rtl">

**IP سرور ایران**، پورت تونل، و **همان توکن** را وارد کن. تمام.
همان ترنسپورت و mode را انتخاب کن. در Direct پورت listen سرور خارج را بده؛ در
Reverse، IP و پورت سرور ایران را وارد کن. در هر دو حالت **همان توکن** را وارد کن.

---

## امکانات

**ترنسپورت‌ها** — TCP، TCP Mux، TCP + Stealth، UDP، UDP + KCP، WS، WS Mux،
WSS و WSS Mux، با Connection Pool.
**ترنسپورت‌ها** — TCP، TCP Mux، TCP + Stealth، UDP، UDP + KCP، UDP + QUIC، WS،
WS Mux، WSS و WSS Mux، با Connection Pool در موارد قابل استفاده.

- **TCP + Stealth** — یک تونل TCP در لایه‌ی Noise با **بدون fingerprint**؛ روی سیم
شبیه بایت تصادفی است، پس چیزی برای تطبیق DPI نیست. برای جایی که فیلترینگ سنگین
Expand Down
113 changes: 28 additions & 85 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,18 @@ package cmd

import (
"context"
"path/filepath"
"strings"
"time"

"github.com/backpack/backpack/internal/metrics"

"github.com/backpack/backpack/config"
"github.com/backpack/backpack/internal/client"

"github.com/backpack/backpack/internal/server"
"github.com/backpack/backpack/internal/engine"
"github.com/backpack/backpack/internal/utils"
"github.com/backpack/backpack/internal/utils/handlers"

"github.com/BurntSushi/toml"
)

var (
logger = utils.NewLogger("info")
)

// tunnelNameFromPath derives a tunnel's name from its config path, which is
// how the rest of the tool identifies it.
func tunnelNameFromPath(configPath string) string {
base := filepath.Base(configPath)
return strings.TrimSuffix(base, filepath.Ext(base))
}

// startMetrics records what the tunnel carries so the CLI can show it later.
// It is best-effort: a tunnel must never fail because diagnostics could not be
// written.
func startMetrics(ctx context.Context, configPath, transport, role string) {
name := tunnelNameFromPath(configPath)
if name == "" {
return
}
c := metrics.NewCollector(filepath.Dir(configPath), name, transport, role, nil, nil)
go func() {
done := make(chan struct{})
go func() { <-ctx.Done(); close(done) }()
_ = c.Write() // an immediate first reading, so the file exists right away
c.Run(done, 30*time.Second)
}()
}

// Run keeps one tunnel running from a configuration file, restarting it in
// place whenever the file changes. See reload.go for why the file is watched at
// all, and for the two rules that keep watching it from being a liability: a
Expand Down Expand Up @@ -80,7 +48,9 @@ func Run(configPath string, ctx context.Context) {
done := make(chan struct{})
go func() {
defer close(done)
runEngine(&running, runCtx, configPath, applyTuning)
if err := runEngine(&running, runCtx, configPath, applyTuning); err != nil {
logger.Fatalf("engine failed: %v", err)
}
}()

next := awaitConfigChange(ctx, configPath, cfg)
Expand All @@ -98,64 +68,37 @@ func Run(configPath string, ctx context.Context) {
}

// runEngine runs one tunnel until ctx ends.
func runEngine(cfg *config.Config, ctx context.Context, configPath string, applyTuning bool) {
configType := ""
if cfg.Server.BindAddr != "" {
configType = "server"
} else if cfg.Client.RemoteAddr != "" {
configType = "client"
} else {
logger.Fatalf("neither server nor client configuration is properly set.")
func runEngine(cfg *config.Config, ctx context.Context, configPath string, applyTuning bool) error {
provider, err := engine.Resolve(cfg)
if err != nil {
return err
}

// Determine whether to run as a server or client
switch configType {
case "server":
// Apply temporary TCP optimizations at startup
if applyTuning && !cfg.Server.SkipOptz {
ApplyTCPTuning()
}

startMetrics(ctx, configPath, string(cfg.Server.Transport), "server")

srv := server.NewServer(&cfg.Server, ctx) // server
reportZeroCopy(ctx)
go srv.Start()

// Wait for shutdown signal
<-ctx.Done()
srv.Stop()
logger.Println("shutting down server...")
case "client":
// Apply temporary TCP optimizations at startup
if applyTuning && !cfg.Client.SkipOptz {
ApplyTCPTuning()
if cfg.EffectiveEngine() == config.EngineReverse || cfg.EffectiveEngine() == config.EngineForward {
if cfg.HasServer() {
// Apply temporary TCP optimizations at startup
if applyTuning && !cfg.Server.SkipOptz {
ApplyTCPTuning()
}
} else {
// Apply temporary TCP optimizations at startup
if applyTuning && !cfg.Client.SkipOptz {
ApplyTCPTuning()
}
}

startMetrics(ctx, configPath, string(cfg.Client.Transport), "client")

clnt := client.NewClient(&cfg.Client, ctx) // client
reportZeroCopy(ctx)
go clnt.Start()

// Wait for shutdown signal
<-ctx.Done()
clnt.Stop()
logger.Println("shutting down client...")

default:
logger.Fatalf("neither server nor client configuration is properly set.")

go func() {
select {
case <-ctx.Done():
case <-time.After(100 * time.Millisecond):
reportZeroCopy(ctx)
}
}()
}
return provider.Run(ctx, engine.Request{ConfigPath: configPath, Config: cfg})
}

// loadConfig loads and parses the TOML configuration file.
func loadConfig(configPath string) (*config.Config, error) {
var cfg config.Config
if _, err := toml.DecodeFile(configPath, &cfg); err != nil {
return &cfg, err
}
return &cfg, nil
return config.LoadFile(configPath)
}

// reportZeroCopy says, periodically and in the tunnel's own journal, whether
Expand Down
3 changes: 3 additions & 0 deletions cmd/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ const ( // Default values
)

func applyDefaults(cfg *config.Config) {
if cfg.EffectiveEngine() != config.EngineReverse && cfg.EffectiveEngine() != config.EngineForward {
return
}
// Token
if cfg.Server.Token == "" {
cfg.Server.Token = defaultToken
Expand Down
Loading