SmsForwarder's FRP (Reverse Proxy) Integration for NAT Traversal: A Technical Deep Dive

SmsForwarder embeds the FRP (Fast Reverse Proxy) client library to establish outbound TCP tunnels through NAT firewalls, exposing local Android services to the public internet via a reverse proxy connection maintained by a foreground service.

SmsForwarder is an open-source Android application (available at pppscn/SmsForwarder) that forwards SMS messages to various destinations. To overcome NAT traversal challenges and allow remote access to devices behind corporate or home firewalls, the project integrates a complete FRP (Fast Reverse Proxy) client implementation that wraps the native frpclib binary, enabling seamless reverse proxy connectivity without manual port forwarding.

What is FRP and Why NAT Traversal Matters

FRP (Fast Reverse Proxy) is a popular open-source tool that exposes local servers behind NAT or firewalls to the public internet. Traditional port forwarding requires router configuration, but FRP works by establishing an outbound TCP connection from the client to a public FRP server (frps). Because the connection originates from inside the network, it traverses NAT automatically. The public server then forwards inbound traffic to the device, creating a secure reverse proxy tunnel.

In SmsForwarder, this integration allows the app to expose local web services or API endpoints from Android devices that are typically unreachable due to carrier-grade NAT or restrictive firewalls.

Core Architecture Components

The FRP integration in SmsForwarder follows a layered architecture that combines native binaries, Room database persistence, and Android foreground services.

The Native frpclib Library (app/libs/frpclib.aar)

At the foundation lies the pre-built native library frpclib.aar, packaged in app/libs/. This binary contains the complete FRP client implementation compiled for Android architectures. The app loads this library at runtime and exposes three critical static methods:

  • Frpclib.runContent(uid, config) – Initializes and starts the FRP client with a specific configuration
  • Frpclib.isRunning(uid) – Checks if a specific FRP profile is currently active
  • Frpclib.close(uid) – Terminates the running FRP client instance

Configuration Management (Frpc Entity)

Configurations are persisted using the Frpc entity defined in app/src/main/kotlin/cn/ppps/forwarder/database/entity/Frpc.kt. This Room database entity stores:

  • uid: Unique identifier for the profile
  • name: Human-readable label
  • config: Raw TOML configuration string (typically mirroring frpc.toml structure)
  • autorun: Flag indicating whether the profile should start automatically when the app launches
  • time: Creation timestamp

The entity includes a computed status property that queries Frpclib.isRunning(uid) to reflect real-time connection state in the UI.

User Interface (FrpcFragment)

The FrpcFragment located at app/src/main/kotlin/cn/ppps/forwarder/fragment/FrpcFragment.kt serves as the primary management interface. It displays saved profiles, handles start/stop actions, and coordinates with the background service. When a user initiates a connection, the fragment:

  1. Validates that App.FrpclibInited is true (native library loaded)
  2. Ensures ForegroundService is running
  3. Calls Frpclib.isRunning(uid) to determine whether to start or stop
  4. Dispatches events via LiveEventBus to trigger the actual connection

Background Execution (ForegroundService)

To prevent Android from killing the FRP process during memory pressure, SmsForwarder implements ForegroundService in app/src/main/kotlin/cn/ppps/forwarder/service/ForegroundService.kt. This service maintains a persistent notification and hosts the actual native client execution. It listens for INTENT_FRPC_APPLY_FILE broadcast events, which signal the service to invoke Frpclib.runContent() with the specified configuration.

How the FRP Client Lifecycle Works

The integration follows a precise orchestration between UI components and background execution:

  1. Configuration Phase: Users create profiles using the default template from app/src/main/res/raw/frpc.toml, which defines the remote frps endpoint, authentication tokens, and tunneling rules.

  2. Service Initialization: When a user enables a profile, FrpcUtils.waitService() (defined in app/src/main/kotlin/cn/ppps/forwarder/utils/FrpcUtils.kt) ensures the foreground service is running before proceeding.

  3. Connection Establishment: The UI posts a LiveEventBus event with INTENT_FRPC_APPLY_FILE, which the foreground service receives. The service then calls Frpclib.runContent(uid, config), spawning the native frpc client process.

  4. NAT Traversal: The native client establishes an outbound TCP connection to the configured frps server. Because this connection originates from the device, it bypasses inbound firewall restrictions and NAT boundaries.

  5. Status Monitoring: The UI observes LiveEventBus events such as EVENT_FRPC_RUNNING_SUCCESS and EVENT_FRPC_RUNNING_ERROR to update connection status indicators in real-time.

  6. Auto-Start Behavior: Profiles with autorun = 1 are automatically initiated when the foreground service starts, ensuring persistent connectivity across device reboots or app restarts.

Key Source Files and Implementation Details

File Path Purpose
app/src/main/res/raw/frpc.toml Default configuration template for new FRP profiles
app/src/main/kotlin/cn/ppps/forwarder/database/entity/Frpc.kt Room entity storing FRP configuration and metadata
app/src/main/kotlin/cn/ppps/forwarder/fragment/FrpcFragment.kt Main UI for managing FRP profiles and connection states
app/src/main/kotlin/cn/ppps/forwarder/fragment/FrpcEditFragment.kt Configuration editor for raw TOML settings
app/src/main/kotlin/cn/ppps/forwarder/service/ForegroundService.kt Foreground service hosting the native FRP process
app/src/main/kotlin/cn/ppps/forwarder/utils/FrpcUtils.kt Helper utilities for service coordination and resource reading
app/libs/frpclib.aar Native FRP client library binary

Practical Implementation Examples

The following Kotlin examples demonstrate how SmsForwarder interacts with the FRP integration:

Starting a FRP Profile from the UI

// Check library initialization before attempting connection
if (!App.FrpclibInited) {
    XToastUtils.error(String.format(getString(R.string.frpclib_download_title), FRPC_LIB_VERSION))
    return
}

// Toggle connection state based on current status
if (Frpclib.isRunning(profile.uid)) {
    Frpclib.close(profile.uid)  // Stop existing connection
} else {
    // Trigger start via ForegroundService through event bus
    LiveEventBus.get<String>(INTENT_FRPC_APPLY_FILE)
        .postAcrossProcess(profile.uid)
}

Frpc Entity Definition

@Entity(tableName = "Frpc")
data class Frpc(
    @PrimaryKey @ColumnInfo(name = "uid") var uid: String = "",
    @ColumnInfo(name = "name") var name: String = "",
    @ColumnInfo(name = "config") var config: String = "",
    @ColumnInfo(name = "autorun", defaultValue = "0") var autorun: Int = 0,
    @ColumnInfo(name = "time") var time: Date = Date(),
    @Ignore var connecting: Boolean = false
) {
    // Computed status checks native library state
    val status: Int
        get() = if (connecting || (App.FrpclibInited && Frpclib.isRunning(uid)))
            STATUS_ON else STATUS_OFF
}

Service Coordination Helper

fun startFrpcWhenReady(uid: String, context: Context) {
    FrpcUtils.waitService(ForegroundService::class.java.name, context)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(object : CompletableObserver {
            override fun onSubscribe(d: Disposable) {}
            override fun onComplete() {
                // Service confirmed running; trigger FRP start
                LiveEventBus.get<String>(INTENT_FRPC_APPLY_FILE)
                    .postAcrossProcess(uid)
            }
            override fun onError(e: Throwable) { 
                // Handle service initialization failure
            }
        })
}

Summary

  • SmsForwarder integrates FRP through a native frpclib.aar wrapper that exposes runContent(), isRunning(), and close() methods.
  • Configurations are stored as Room database entities in the Frpc table, supporting auto-start functionality via the autorun column.
  • The ForegroundService maintains persistent connections by hosting the native process in a foreground notification, preventing Android system termination.
  • NAT traversal is achieved through outbound TCP connections to remote frps servers, eliminating the need for router port forwarding.
  • Real-time status updates flow through LiveEventBus events, keeping the UI synchronized with background connection states.

Frequently Asked Questions

What is the frpclib.aar file in SmsForwarder?

The frpclib.aar file in app/libs/ is the pre-compiled Android library containing the native FRP (Fast Reverse Proxy) client implementation. This binary provides the core tunneling functionality that allows SmsForwarder to establish reverse proxy connections through NAT firewalls. The Kotlin wrapper class Frpclib loads this native library and exposes managed methods for starting, stopping, and querying connection status.

How does SmsForwarder maintain FRP connections when the app is backgrounded?

SmsForwarder uses a ForegroundService (ForegroundService.kt) that runs with a persistent notification, keeping the app in the foreground service state. This prevents the Android system from killing the process when the app is not visible. The service hosts the native frpc client process, ensuring the reverse proxy tunnel remains active even when the user switches to other applications or the device enters doze mode.

Can FRP profiles auto-start when the app launches?

Yes, SmsForwarder supports auto-start functionality through the autorun field in the Frpc entity (defined in app/src/main/kotlin/cn/ppps/forwarder/database/entity/Frpc.kt). When the foreground service initializes, it checks for profiles where autorun = 1 and automatically initiates connections for those configurations. This ensures critical tunnels are re-established after device reboots or app restarts without manual intervention.

Where is the FRP configuration stored in SmsForwarder?

FRP configurations are stored in SQLite via Room in the Frpc table, with the raw TOML configuration string saved in the config column. Additionally, a default template is included as a raw resource at app/src/main/res/raw/frpc.toml, which serves as the baseline when users create new profiles through the FrpcEditFragment interface.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →