N2nBindings 2.4.0

dotnet add package N2nBindings --version 2.4.0
                    
NuGet\Install-Package N2nBindings -Version 2.4.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="N2nBindings" Version="2.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="N2nBindings" Version="2.4.0" />
                    
Directory.Packages.props
<PackageReference Include="N2nBindings" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add N2nBindings --version 2.4.0
                    
#r "nuget: N2nBindings, 2.4.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package N2nBindings@2.4.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=N2nBindings&version=2.4.0
                    
Install as a Cake Addin
#tool nuget:?package=N2nBindings&version=2.4.0
                    
Install as a Cake Tool

N2nBindings - N2N VPN Client for .NET MAUI Android

Version: 2.5.0 Last Updated: April 2026 .NET Version: 9.0


Table of Contents

  1. Overview
  2. Architecture
  3. Technology Stack
  4. Prerequisites
  5. Getting Started
  6. Building the Native Library
  7. Configuration
  8. API Reference
  9. Usage Examples
  10. VpnService Integration
  11. Troubleshooting
  12. Project Structure
  13. Contributing
  14. Changelog
  15. Native Library Source

Overview

N2nBindings is a .NET MAUI Android library that provides a clean, managed interface to the n2n peer-to-peer VPN client. It enables Android applications to create virtual private networks without requiring root access.

Key Features

  • Pure P/Invoke Architecture - Direct C# to native C calls, no Java/JNI complexity
  • VpnService Integration - Works with Android's VpnService API for non-rooted devices
  • Latest n2n v3 - Built from the latest n2n source code
  • Async Support - Full async/await pattern for connection management
  • Event-Driven - Status changes, errors, and logs delivered via events
  • Thread-Safe - Safe to call from any thread with GCHandle pinning and double-checked locking
  • Connection Cancellation - Cancel ongoing connection attempts programmatically

How It Works

graph TB
    subgraph "C# MAUI Application"
        App[Your MAUI App]
        N2nEdge[N2nEdge Class]
        VpnService[N2nVpnService]
    end

    subgraph "Native Layer"
        PInvoke[P/Invoke Bindings]
        LibN2n[libn2n_android.so]
    end

    subgraph "Android System"
        AndroidVpn[Android VpnService API]
        TunDevice[TUN Device]
    end

    subgraph "Network"
        Supernode[Supernode]
        Peers[Other Edge Nodes]
    end

    App --> N2nEdge
    N2nEdge --> VpnService
    VpnService --> PInvoke
    PInvoke --> LibN2n
    VpnService --> AndroidVpn
    AndroidVpn --> TunDevice
    LibN2n --> TunDevice
    LibN2n --> Supernode
    LibN2n --> Peers

Architecture

High-Level Architecture

flowchart TB
    subgraph Application["Application Layer"]
        direction LR
        MAUI["MAUI Application"]
        Config["N2nConfiguration"]
    end

    subgraph Bindings["N2nBindings Library"]
        direction LR
        Edge["N2nEdge"]
        Native["N2nNative (P/Invoke)"]
        Service["N2nVpnService"]
    end

    subgraph NativeLib["Native Library (libn2n_android.so)"]
        direction LR
        Android["n2n_android.c"]
        TunTap["tuntap_android.c"]
        Core["n2n Core"]
    end

    Application --> Bindings
    Bindings --> NativeLib

Component Interaction

sequenceDiagram
    participant App as MAUI App
    participant Edge as N2nEdge
    participant VPN as N2nVpnService
    participant Native as libn2n_android.so
    participant Android as Android OS

    App->>Edge: Start(config, tunFd)
    Edge->>Native: n2n_android_start()

    Note over VPN,Android: VPN Permission Flow
    App->>Android: VpnService.Prepare()
    Android-->>App: Permission Intent (if needed)
    App->>VPN: StartService(config)
    VPN->>Android: VpnBuilder.Establish()
    Android-->>VPN: TUN File Descriptor
    VPN->>Native: n2n_android_start(config, fd)

    Native->>Native: Initialize edge
    Native->>Native: Start edge thread
    Native-->>Edge: Status: Connecting
    Edge-->>App: StatusChanged event

    Native->>Native: Register with supernode
    Native-->>Edge: Status: Connected
    Edge-->>App: StatusChanged event

Technology Stack

Component Technology Purpose
Runtime .NET 9.0 Application framework
UI Framework MAUI Cross-platform UI
Native Bindings P/Invoke C# to C interop
VPN Protocol n2n v3 Peer-to-peer VPN
Encryption Twofish/AES/ChaCha20 Data encryption
Android API VpnService TUN interface management

Supported Architectures

Architecture ABI Devices
ARM64 arm64-v8a Modern phones/tablets
ARM32 armeabi-v7a Older devices
x86_64 x86_64 Emulators
x86 x86 Emulators

Prerequisites

For Using the Library

Requirement Version Purpose
.NET SDK 9.0+ Build MAUI applications
Android SDK API 21+ Android development
Visual Studio / Rider Latest IDE

For Building Native Library

Requirement Version Purpose
Android NDK r21+ Cross-compilation
CMake 3.10+ Build system
Linux/macOS Any Build environment

Getting Started

1. Add the Library Reference

Add the N2nBindings project to your solution:

dotnet add reference ../N2nBindings/N2nBindings.csproj

Or copy the compiled library to your project.

2. Add Required Permissions

In your AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />

3. Register the VpnService

The N2nVpnService is automatically registered via attributes, but ensure your app declares the service in the manifest.

4. Copy Native Libraries

Copy the compiled native libraries to your project:

YourApp/
├── lib/
│   ├── arm64-v8a/
│   │   └── libn2n_android.so
│   └── armeabi-v7a/
│       └── libn2n_android.so

Building the Native Library

The native library source is available at: https://github.com/NakanoMiku13/n2n-android

Option 1: Using the Build Script

# Clone the n2n-android repository
git clone https://github.com/NakanoMiku13/n2n-android.git
cd n2n-android

# Checkout the stable version
git checkout v1.0.0

# Set NDK path (if not already set)
export ANDROID_NDK=$HOME/Android/Sdk/ndk/25.2.9519653

# Build all architectures
./build.sh

# Or build specific architecture
./build.sh arm64-v8a

Option 2: Manual CMake Build

git clone https://github.com/NakanoMiku13/n2n-android.git
cd n2n-android
mkdir build && cd build

# Configure for ARM64
cmake \
    -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
    -DANDROID_ABI=arm64-v8a \
    -DANDROID_PLATFORM=android-21 \
    -DCMAKE_BUILD_TYPE=Release \
    ..

# Build
cmake --build . --config Release

# Library will be at: build/libn2n_android.so

Build Output

After building, copy the libraries:

# From n2n-android/output/lib/
cp -r output/lib/* /path/to/N2nBindings/lib/

Configuration

N2nConfiguration Properties

Property Type Default Description
Community string Required Virtual network name (max 19 chars)
SecretKey string "" Encryption password
Supernode string Required Supernode address "host:port"
Supernode2 string? null Backup supernode
IpAddress string Required* Local VPN IP address (*not required for Auto IP mode)
Netmask string "255.255.255.0" Network mask
MacAddress string? Auto Resolved automatically by MacMode; can be set manually to bypass mode resolution
MacMode enum RandomFixed MAC address assignment mode. See MAC Mode Options.
Mtu int 1200 Maximum transmission unit. 1200 provides safe headroom for n2n/encryption/UDP/IP overhead on mobile networks.
LocalPort int 0 (auto) Local UDP port
Encryption enum Twofish Encryption algorithm
IpMode enum Static IP assignment mode (Static or Auto)
AllowP2P bool true Enable peer-to-peer
AllowRouting bool false Accept routed packets
HeaderEncryption bool false Encrypt packet headers
Compression bool false Enable compression
RegisterInterval int 30 Supernode registration interval in seconds (1-3600). Controls normal renewal period and fast-retry cadence (interval / 10). Must be ≥ 10 to avoid integer truncation.
RegisterTtl int 64 IP TTL for UDP registration packets (1-255). Controls how many router hops the packet can traverse. Value of 1 drops the packet at the first router — only valid if the supernode is on the same local subnet.

IP Mode Options

Value Description
Static Use the IpAddress property (user-defined). Default mode.
Auto Request IP address automatically from supernode. Use RequestIpAsync() before starting.

MAC Mode Options

Value Description
RandomFixed (Default) Generates a random MAC once, persists it globally in SharedPreferences, and reuses it on every connection and app restart. Prevents supernode peer table churn on reconnects.
FullRandom Generates a brand new random MAC on every connection attempt. Maximum anonymity, but causes supernode churn on retries — use intentionally.
Device Reads the hardware MAC from the device (sysfs primary, .NET NetworkInterface fallback). On Android 10+ the OS restricts hardware MAC access; automatically falls back to RandomFixed if the real MAC cannot be read.

Why this matters: when MacAddress is left empty, the native library generates a new random MAC on every call to n2n_android_start(). Each reconnect or retry therefore looks like a brand new device to the supernode, growing its peer table unboundedly. RandomFixed is the safe default that gives you a stable virtual identity without exposing real hardware.

Encryption Options

Value Algorithm Notes
None No encryption Not recommended
Twofish Twofish Default, good balance
Aes AES-CBC Strong, widely supported
ChaCha20 ChaCha20 Fast on mobile
Speck Speck Lightweight

API Reference

N2nEdge Class

The main interface for managing the n2n edge client.

Properties
// Current connection status
N2nNative.N2nStatus Status { get; }

// Whether edge is running
bool IsRunning { get; }

// Whether connected to supernode
bool IsConnected { get; }

// Last error message
string LastError { get; }

// Library version
static string Version { get; }
Methods
// Start the edge client
int Start(N2nConfiguration config, int tunFd);

// Start asynchronously (waits for connection)
Task<bool> StartAsync(N2nConfiguration config, int tunFd, CancellationToken ct = default);

// Stop the edge client
int Stop();

// Stop asynchronously
Task StopAsync(CancellationToken ct = default);

// Get runtime statistics
N2nStatistics GetStatistics();

// Set log level (0-4)
void SetLogLevel(int level);

// Request IP from supernode (auto IP mode)
Task<(string IpAddress, string Netmask)> RequestIpAsync(N2nConfiguration config, CancellationToken ct = default);

// Get last assigned IP (auto IP mode)
bool GetAssignedIp(out string? ipAddress, out string? netmask);
Events
// Status changed
event EventHandler<StatusChangedEventArgs> StatusChanged;

// Error occurred
event EventHandler<ErrorEventArgs> ErrorOccurred;

// Log message received
event EventHandler<LogEventArgs> LogReceived;

// Socket created (protect with VpnService.Protect())
event EventHandler<SocketCreatedEventArgs> SocketCreated;

// IP assigned by supernode (auto IP mode)
event EventHandler<IpAssignedEventArgs> IpAssigned;

N2nVpnService Static Methods

// Create intent to start VPN
static Intent CreateStartIntent(Context context, N2nConfiguration config);

// Create intent to stop VPN
static Intent CreateStopIntent(Context context);

// Cancel an ongoing connection attempt (e.g., stuck waiting for supernode)
static void CancelConnection(Context context);

// Check if a connection attempt is in progress
static bool IsConnecting { get; }

// Check if VPN permission is granted
static bool HasVpnPermission(Context context);

// Get intent to request VPN permission
static Intent? GetVpnPermissionIntent(Context context);

// Check if the app is exempt from battery optimizations
static bool IsIgnoringBatteryOptimizations(Context context);

// Get intent to request battery optimization exemption
static Intent? GetBatteryOptimizationIntent(Context context);

// Current status
static N2nNative.N2nStatus CurrentStatus { get; }

// Status changed event
static event EventHandler<N2nNative.N2nStatus> StatusChanged;

// Error event
static event EventHandler<string> ErrorOccurred;

Usage Examples

Basic Connection (Static IP)

using N2nBindings;

// Create configuration with static IP
var config = N2nConfiguration.Create(
    community: "mynetwork",
    supernode: "supernode.example.com:7654",
    ipAddress: "10.0.0.100",
    secretKey: "mysecretpassword"
);

// Validate
var error = config.Validate();
if (error != null)
{
    Console.WriteLine($"Config error: {error}");
    return;
}

// Start via VpnService
var intent = N2nVpnService.CreateStartIntent(context, config);
context.StartService(intent);

Auto IP Mode (DHCP-like)

using N2nBindings;

// Create configuration for auto IP assignment
var config = N2nConfiguration.CreateAutoIp(
    community: "mynetwork",
    supernode: "supernode.example.com:7654",
    secretKey: "mysecretpassword"
);

// Create edge instance
using var edge = new N2nEdge();

// Handle socket creation for VPN protection
edge.SocketCreated += (s, e) =>
{
    // IMPORTANT: Protect the socket from VPN routing
    vpnService.Protect(e.SocketFd);
};

// Request IP from supernode
var (assignedIp, assignedNetmask) = await edge.RequestIpAsync(config);

Console.WriteLine($"Assigned IP: {assignedIp}/{assignedNetmask}");

// Update config with assigned IP
config.IpAddress = assignedIp;
config.Netmask = assignedNetmask;

// Now create VPN interface with the assigned IP and start edge
// ... VpnService.Builder setup with assignedIp ...
await edge.StartAsync(config, tunFd);

MAC Address Modes

// Default — RandomFixed: stable virtual identity, generated once and persisted globally.
// No extra setup needed; this is the recommended mode for most use cases.
var config = N2nConfiguration.Create("mynetwork", "supernode:7654", "10.0.0.100");
// config.MacMode == MacAddressMode.RandomFixed by default

// Full random: new identity on every connection attempt (anonymity mode).
// Be aware this causes supernode churn on retries.
config.MacMode = MacAddressMode.FullRandom;

// Device MAC: use the hardware MAC of this device.
// Falls back silently to RandomFixed on Android 10+ if hardware MAC is restricted.
config.MacMode = MacAddressMode.Device;

When using N2nVpnService via intents, pass the mode as an int extra:

intent.PutExtra(N2nVpnService.ExtraMacAddressMode, (int)MacAddressMode.FullRandom);

To reset the persisted RandomFixed MAC (e.g., for a fresh identity), clear the app's SharedPreferences entry n2n_fixed_mac from preference file n2n_prefs, or switch to FullRandom for one connection then back to RandomFixed.

Cancelling a Connection

using N2nBindings;

// Check if a connection is in progress
if (N2nVpnService.IsConnecting)
{
    // Cancel the ongoing connection attempt
    N2nVpnService.CancelConnection(context);
}

Using N2nEdge Directly

using N2nBindings;

public class VpnManager
{
    private N2nEdge? _edge;

    public async Task<bool> ConnectAsync(N2nConfiguration config, int tunFd)
    {
        _edge = new N2nEdge();

        _edge.StatusChanged += (s, e) =>
        {
            Console.WriteLine($"Status: {e.Status}");
        };

        _edge.ErrorOccurred += (s, e) =>
        {
            Console.WriteLine($"Error: {e.Message}");
        };

        return await _edge.StartAsync(config, tunFd);
    }

    public async Task DisconnectAsync()
    {
        if (_edge != null)
        {
            await _edge.StopAsync();
            _edge.Dispose();
            _edge = null;
        }
    }

    public N2nStatistics? GetStats()
    {
        return _edge?.GetStatistics();
    }
}

MAUI Page Example

public partial class VpnPage : ContentPage
{
    private const int VpnPermissionRequestCode = 100;

    public VpnPage()
    {
        InitializeComponent();
        N2nVpnService.StatusChanged += OnVpnStatusChanged;
    }

    private async void OnConnectClicked(object sender, EventArgs e)
    {
        var context = Platform.CurrentActivity;

        // Check VPN permission
        var permissionIntent = N2nVpnService.GetVpnPermissionIntent(context);
        if (permissionIntent != null)
        {
            context.StartActivityForResult(permissionIntent, VpnPermissionRequestCode);
            return;
        }

        // Start VPN
        var config = new N2nConfiguration
        {
            Community = CommunityEntry.Text,
            SecretKey = PasswordEntry.Text,
            Supernode = SupernodeEntry.Text,
            IpAddress = IpAddressEntry.Text
        };

        var intent = N2nVpnService.CreateStartIntent(context, config);
        context.StartService(intent);
    }

    private void OnDisconnectClicked(object sender, EventArgs e)
    {
        var context = Platform.CurrentActivity;

        // Cancel if still connecting, otherwise stop
        if (N2nVpnService.IsConnecting)
        {
            N2nVpnService.CancelConnection(context);
        }
        else
        {
            var intent = N2nVpnService.CreateStopIntent(context);
            context.StartService(intent);
        }
    }

    private void OnVpnStatusChanged(object? sender, N2nNative.N2nStatus status)
    {
        MainThread.BeginInvokeOnMainThread(() =>
        {
            StatusLabel.Text = status.ToString();
            ConnectButton.IsEnabled = status == N2nNative.N2nStatus.Stopped;
            DisconnectButton.IsEnabled = status == N2nNative.N2nStatus.Connected;
        });
    }
}

VpnService Integration

VPN Permission Flow

flowchart TD
    A[User taps Connect] --> B{Has VPN Permission?}
    B -->|Yes| C[Start VpnService]
    B -->|No| D[Request Permission]
    D --> E{User Grants?}
    E -->|Yes| C
    E -->|No| F[Show Error]
    C --> G[Establish TUN Interface]
    G --> H[Start n2n Edge]
    H --> I[Connect to Supernode]

Handling Permission Request

// In your Activity
protected override void OnActivityResult(int requestCode, Result resultCode, Intent? data)
{
    base.OnActivityResult(requestCode, resultCode, data);

    if (requestCode == VpnPermissionRequestCode)
    {
        if (resultCode == Result.Ok)
        {
            // Permission granted, start VPN
            StartVpn();
        }
        else
        {
            // Permission denied
            ShowError("VPN permission required");
        }
    }
}

Troubleshooting

Common Issues

Library Not Found

Problem: DllNotFoundException: n2n_android

Solutions:

  1. Ensure native libraries are in correct paths:
    • lib/arm64-v8a/libn2n_android.so
    • lib/armeabi-v7a/libn2n_android.so
  2. Check that libraries are included in the build:
    <AndroidNativeLibrary Include="lib\arm64-v8a\libn2n_android.so" Abi="arm64-v8a" />
    
  3. Verify library was built for the correct architecture
Connection Timeout

Problem: Status stays at "Connecting"

Solutions:

  1. Verify supernode address and port are correct
  2. Check network connectivity
  3. Ensure firewall allows UDP traffic
  4. Try a different supernode
VPN Permission Denied

Problem: VpnService.Prepare() returns non-null Intent repeatedly

Solutions:

  1. User must explicitly grant VPN permission
  2. Cannot be automated - requires user interaction
  3. Show clear UI explaining why VPN is needed
TUN Device Error

Problem: "Failed to establish VPN interface"

Solutions:

  1. Only one VPN can be active at a time
  2. Ensure no other VPN apps are running
  3. Check Android logs for specific errors
Connection Drops After ~10 Seconds

Problem: VPN connects successfully, pings work for a few seconds, then "Destination Host Unreachable"

Symptoms in logs:

[N2N-edge] TUN not ready 17 times in 5s, sock_ready=1, tun_fd=224
[N2N-edge] WARNING: select() error: Interrupted system call (errno=4)

Solution: Ensure the VPN interface uses blocking mode. In N2nVpnService.cs:

var builder = new Builder(this)
    .SetSession("N2N VPN")
    .SetMtu(config.Mtu)
    .AddAddress(config.IpAddress, prefixLength)
    .SetBlocking(true);  // Must be true, not false!

Explanation: Android's VPN framework has issues with non-blocking TUN file descriptors. The n2n native code uses select() which doesn't work correctly with non-blocking TUN on Android.

Encryption Mismatch

Problem: VPN shows "Connected" but peers cannot communicate. Logs show:

WARNING: invalid transop ID: expected Twofish (2), got AES (3)

Solutions:

  1. All peers must use the same encryption algorithm - Check that every device in your n2n network uses the same encryption setting
  2. Verify the encryption setting is being passed correctly:
    var config = new N2nConfiguration
    {
        // ... other settings ...
        Encryption = N2nNative.N2nEncryption.Aes // Must match all peers
    };
    
  3. Common encryption IDs:
    • None = 1
    • Twofish = 2 (default)
    • AES = 3
    • ChaCha20 = 4
    • Speck = 5

Debugging

C# Logging

Enable verbose logging in C#:

var edge = new N2nEdge();
edge.SetLogLevel(4); // Debug level

edge.LogReceived += (s, e) =>
{
    Debug.WriteLine($"[N2N] {e.Message}");
};
Native Logging (Logcat)

The native library outputs detailed logs to Android logcat. Use these commands to view them:

# View all n2n related logs
adb logcat -s N2N:* N2N-native:* N2N-edge:*

# Or filter by tags individually:
adb logcat -s N2N:V        # C# VpnService logs
adb logcat -s N2N-native:V # Native API logs (n2n_android.c)
adb logcat -s N2N-edge:V   # n2n core trace output (stdout redirect)

# Clear logcat and start fresh
adb logcat -c && adb logcat -s N2N:* N2N-native:* N2N-edge:*

Log tags explained:

  • N2N - High-level logs from C# N2nVpnService (VPN interface setup, configuration)
  • N2N-native - Native library logs from n2n_android.c (init, start, stop, callbacks)
  • N2N-edge - Core n2n trace output (supernode registration, peer discovery, packet flow)

Project Structure

N2nBindings/
├── N2nNative.cs           # P/Invoke declarations for n2n_android
├── N2nConfiguration.cs    # Configuration class with validation
├── N2nEdge.cs             # High-level edge wrapper with events
├── N2nVpnService.cs       # Android VpnService implementation
├── N2nBindings.csproj     # Project file
├── README.md              # This file
└── lib/                   # Native libraries (libn2n_android.so)
    ├── arm64-v8a/         # 64-bit ARM (modern devices)
    ├── armeabi-v7a/       # 32-bit ARM (older devices)
    ├── x86/               # x86 emulators
    └── x86_64/            # x86_64 emulators

# Native source: https://github.com/NakanoMiku13/n2n-android

Contributing

Building from Source

  1. Clone the native library repository:
    git clone https://github.com/NakanoMiku13/n2n-android.git
    git checkout v1.0.0
    
  2. Build the native library:
    cd n2n-android
    ./build.sh
    
  3. Copy libraries to N2nBindings/lib/
  4. Build the C# project:
    cd N2nBindings
    dotnet build
    

Code Style

  • Follow C# naming conventions
  • Use XML documentation for public APIs
  • Keep P/Invoke declarations synchronized with n2n_android.h

Testing

Test on both ARM64 and ARM32 devices/emulators:

  • Physical device (arm64-v8a)
  • Emulator with ARM system image (armeabi-v7a)

Changelog

v2.5.0 (April 2026)

Default configuration tuned for zero packet loss on mobile networks:

  • RegisterInterval default: 20 → 30 (fast-retry every 3s, normal renewal every 30s)
  • RegisterTtl default: 1 → 64 — this was a significant bug: TTL=1 means UDP registration packets cannot cross any router, so any supernode more than one hop away would never receive registrations. 64 is the standard OS default and works across all real-world topologies
  • Mtu default: 1400 → 1200 — provides safe headroom for n2n header + encryption + UDP/IP overhead on LTE/5G paths where effective MTU is often 1280–1350

Bug fix — RegisterInterval default reverted to 20:

  • Reverts the v2.4.0 change that set RegisterInterval to 2
  • Root cause: the native edge loop uses register_interval / 10 (integer division) for the fast-retry cadence while sn_wait=1. With a value of 2, 2/10 = 0, making the condition now > last_register_req + 0 always true — the edge hammers the supernode on every main loop iteration instead of every 2 seconds as intended
  • Correct default is 20, matching REGISTER_SUPER_INTERVAL_DFL in n2n_define.h and n2n_android_config_defaults() in the native layer, which gives a 2s fast-retry (20/10) and a 20s normal renewal

MAC Address Management:

  • New MacAddressMode enum with three modes: RandomFixed (default), FullRandom, Device
  • New MacMode property on N2nConfiguration
  • New N2nConfiguration.GenerateRandomMac() static helper (locally administered unicast, safe for virtual interfaces)
  • New intent extra ExtraMacAddressMode for N2nVpnService
  • Bug fix: previously, leaving MacAddress empty caused the native library to generate a new random MAC on every n2n_android_start() call. Each reconnect or retry looked like a new device to the supernode, growing its peer table unboundedly. RandomFixed (new default) eliminates this by generating once and persisting globally via SharedPreferences
  • Device mode reads hardware MAC from /sys/class/net/{wlan0,eth0,wlan1}/address (sysfs) with .NET NetworkInterface as fallback; silently falls back to RandomFixed on Android 10+ where hardware MAC access is OS-restricted

v2.4.0 (February 2026)

Connection Cancellation:

  • New CancelConnection(Context) static method to cancel ongoing connection attempts
  • New IsConnecting static property to check if a connection attempt is in progress
  • Useful when connections are stuck (e.g., waiting for supernode IP assignment)

Callback Robustness (N2nEdge):

  • GCHandle pinning for all callback delegates to prevent garbage collection during native calls
  • Thread-safe callbacks with double-checked locking pattern (_callbackLock)
  • Safe string marshalling via SafeMarshalString() to handle invalid/freed pointers gracefully
  • No-op callbacks during disposal instead of passing null to native code, preventing null pointer dereference
  • Early disposed checks in all callbacks to prevent use-after-free scenarios
  • Switched from Debug.WriteLine to Android.Util.Log for proper logcat integration

VpnService Reliability (N2nVpnService):

  • Android API synchronization with _androidApiLock to prevent race conditions during service destruction
  • Java.Lang.IllegalStateException handling in all callback paths for when service is destroyed mid-operation
  • Static event sender fix: StatusChanged and ErrorOccurred events now provide the service instance as sender instead of null
  • Instance tracking via _currentInstance with proper lock synchronization

Configuration Improvements:

  • RegisterInterval default changed from 20 to 2 seconds (reverted in v2.5.0 — see below)
  • MacAddress, RegisterInterval, and RegisterTtl now passed through Intent extras to VpnService
  • New Intent extras: ExtraMacAddress, ExtraRegisterInterval, ExtraRegisterTtl

Startup Cancellation Flow:

  • StartVpnAsync() now accepts a CancellationToken with cancellation checks at every critical step
  • Auto IP timeout uses linked cancellation tokens (user cancel + timeout)
  • Proper distinction between user cancellation and timeout in error messages

v2.3.0 (December 2024)

New Features:

  • Auto IP Mode: Added support for automatic IP assignment from supernode (DHCP-like)
    • New IpMode property in N2nConfiguration (Static or Auto)
    • New RequestIpAsync() method to request IP from supernode
    • New GetAssignedIp() method to retrieve assigned IP
    • New IpAssigned event for async IP assignment notification
    • New factory method N2nConfiguration.CreateAutoIp() for quick setup

API Additions:

  • N2nNative.N2nIpMode enum (Static=0, Auto=1)
  • N2nNative.IpAssignedCallback delegate
  • N2nNative.n2n_android_request_ip() P/Invoke
  • N2nNative.n2n_android_set_ip_assigned_callback() P/Invoke
  • N2nNative.n2n_android_get_assigned_ip() P/Invoke
  • N2nEdge.IpAssigned event
  • N2nEdge.RequestIpAsync() method
  • N2nEdge.GetAssignedIp() method

Usage:

// Auto IP mode
var config = N2nConfiguration.CreateAutoIp("community", "supernode:7654", "secret");
var (ip, netmask) = await edge.RequestIpAsync(config);
config.IpAddress = ip;
await edge.StartAsync(config, tunFd);

v2.2.0 (December 2024)

Bug Fixes:

  • Critical: Fixed TUN interface becoming unresponsive after ~10 seconds
    • Changed VPN interface from non-blocking to blocking mode
    • Non-blocking mode caused select() to incorrectly report TUN as "not ready"
    • Symptoms: Connection works initially, then "Destination Host Unreachable" after ~10s
    • Root cause: Android VPN framework has issues with non-blocking TUN file descriptors

Technical Details: The n2n native code uses select() to monitor both the UDP socket and TUN interface. With non-blocking mode (SetBlocking(false)), Android's VPN stack would intermittently report the TUN fd as not ready for reading, even when data was available. This caused:

  1. "TUN not ready X times in 5s" warnings in logs
  2. Packets received from network but not delivered to apps
  3. Eventually the edge loop would exit cleanly

v2.1.0 (December 2024)

Bug Fixes:

  • Fixed encryption parameter not being passed through Android Intent extras
  • Fixed double-free crash in n2n_android_stop() - edge_term_conf() already frees encrypt_key internally
  • Fixed null callback crash during N2nEdge.Dispose() when callbacks were cleared

Improvements:

  • Added native stdout redirect to logcat for n2n trace output (tag: N2N-edge)
  • Added comprehensive debug logging throughout native library
  • Improved VPN interface setup logging

Documentation:

  • Added troubleshooting section for encryption mismatch errors
  • Added native logging (logcat) debugging guide

v2.0.0 (December 2024)

  • Initial release with full n2n v3 support
  • P/Invoke bindings for Android
  • VpnService integration
  • Async/await support
  • Event-driven status updates

License

This project is licensed under the MIT License.

MIT License

Copyright (c) 2024-2026 NakanoMiku13

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Native Library Source

The native libn2n_android.so library is built from a custom fork of n2n optimized for Android:

Repository: https://github.com/NakanoMiku13/n2n-android

Current Version: v1.0.0

This fork includes Android-specific modifications:

  • TUN device integration for Android VpnService
  • Socket callback mechanism for VPN protection
  • Logcat integration for debugging
  • P/Invoke compatible API (n2n_android.h)

To build the native library from source, see the repository's build instructions.


Acknowledgments

  • n2n - The core peer-to-peer VPN protocol by ntop
Product Compatible and additional computed target framework versions.
.NET net9.0-android is compatible.  net9.0-android35.0 is compatible.  net10.0-android was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.4.0 99 5/18/2026
2.3.0 291 12/19/2025 2.3.0 is deprecated because it has critical bugs.

v2.3.0 - December 2024
- Added Auto IP mode for automatic IP assignment from supernode
- New RequestIpAsync() method and IpAssigned event
- New IpMode configuration property (Static/Auto)
- New CreateAutoIp() factory method

v2.2.0 - December 2024
- Fixed TUN interface becoming unresponsive after ~10 seconds

See full changelog at: https://github.com/NakanoMiku13/N2nBindings