N2nBindings 2.4.0
dotnet add package N2nBindings --version 2.4.0
NuGet\Install-Package N2nBindings -Version 2.4.0
<PackageReference Include="N2nBindings" Version="2.4.0" />
<PackageVersion Include="N2nBindings" Version="2.4.0" />
<PackageReference Include="N2nBindings" />
paket add N2nBindings --version 2.4.0
#r "nuget: N2nBindings, 2.4.0"
#:package N2nBindings@2.4.0
#addin nuget:?package=N2nBindings&version=2.4.0
#tool nuget:?package=N2nBindings&version=2.4.0
N2nBindings - N2N VPN Client for .NET MAUI Android
Version: 2.5.0 Last Updated: April 2026 .NET Version: 9.0
Table of Contents
- Overview
- Architecture
- Technology Stack
- Prerequisites
- Getting Started
- Building the Native Library
- Configuration
- API Reference
- Usage Examples
- VpnService Integration
- Troubleshooting
- Project Structure
- Contributing
- Changelog
- 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
MacAddressis left empty, the native library generates a new random MAC on every call ton2n_android_start(). Each reconnect or retry therefore looks like a brand new device to the supernode, growing its peer table unboundedly.RandomFixedis 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:
- Ensure native libraries are in correct paths:
lib/arm64-v8a/libn2n_android.solib/armeabi-v7a/libn2n_android.so
- Check that libraries are included in the build:
<AndroidNativeLibrary Include="lib\arm64-v8a\libn2n_android.so" Abi="arm64-v8a" /> - Verify library was built for the correct architecture
Connection Timeout
Problem: Status stays at "Connecting"
Solutions:
- Verify supernode address and port are correct
- Check network connectivity
- Ensure firewall allows UDP traffic
- Try a different supernode
VPN Permission Denied
Problem: VpnService.Prepare() returns non-null Intent repeatedly
Solutions:
- User must explicitly grant VPN permission
- Cannot be automated - requires user interaction
- Show clear UI explaining why VPN is needed
TUN Device Error
Problem: "Failed to establish VPN interface"
Solutions:
- Only one VPN can be active at a time
- Ensure no other VPN apps are running
- 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:
- All peers must use the same encryption algorithm - Check that every device in your n2n network uses the same encryption setting
- Verify the encryption setting is being passed correctly:
var config = new N2nConfiguration { // ... other settings ... Encryption = N2nNative.N2nEncryption.Aes // Must match all peers }; - Common encryption IDs:
None= 1Twofish= 2 (default)AES= 3ChaCha20= 4Speck= 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 fromn2n_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
- Clone the native library repository:
git clone https://github.com/NakanoMiku13/n2n-android.git git checkout v1.0.0 - Build the native library:
cd n2n-android ./build.sh - Copy libraries to N2nBindings/lib/
- 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:
RegisterIntervaldefault: 20 → 30 (fast-retry every 3s, normal renewal every 30s)RegisterTtldefault: 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 topologiesMtudefault: 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
RegisterIntervalto 2 - Root cause: the native edge loop uses
register_interval / 10(integer division) for the fast-retry cadence whilesn_wait=1. With a value of 2,2/10 = 0, making the conditionnow > last_register_req + 0always 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_DFLinn2n_define.handn2n_android_config_defaults()in the native layer, which gives a 2s fast-retry (20/10) and a 20s normal renewal
MAC Address Management:
- New
MacAddressModeenum with three modes:RandomFixed(default),FullRandom,Device - New
MacModeproperty onN2nConfiguration - New
N2nConfiguration.GenerateRandomMac()static helper (locally administered unicast, safe for virtual interfaces) - New intent extra
ExtraMacAddressModeforN2nVpnService - Bug fix: previously, leaving
MacAddressempty caused the native library to generate a new random MAC on everyn2n_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 viaSharedPreferences Devicemode reads hardware MAC from/sys/class/net/{wlan0,eth0,wlan1}/address(sysfs) with.NET NetworkInterfaceas fallback; silently falls back toRandomFixedon 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
IsConnectingstatic 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.WriteLinetoAndroid.Util.Logfor proper logcat integration
VpnService Reliability (N2nVpnService):
- Android API synchronization with
_androidApiLockto prevent race conditions during service destruction Java.Lang.IllegalStateExceptionhandling in all callback paths for when service is destroyed mid-operation- Static event sender fix:
StatusChangedandErrorOccurredevents now provide the service instance as sender instead of null - Instance tracking via
_currentInstancewith proper lock synchronization
Configuration Improvements:
RegisterIntervaldefault changed from 20 to 2 seconds (reverted in v2.5.0 — see below)MacAddress,RegisterInterval, andRegisterTtlnow passed through Intent extras to VpnService- New Intent extras:
ExtraMacAddress,ExtraRegisterInterval,ExtraRegisterTtl
Startup Cancellation Flow:
StartVpnAsync()now accepts aCancellationTokenwith 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
IpModeproperty inN2nConfiguration(Static or Auto) - New
RequestIpAsync()method to request IP from supernode - New
GetAssignedIp()method to retrieve assigned IP - New
IpAssignedevent for async IP assignment notification - New factory method
N2nConfiguration.CreateAutoIp()for quick setup
- New
API Additions:
N2nNative.N2nIpModeenum (Static=0, Auto=1)N2nNative.IpAssignedCallbackdelegateN2nNative.n2n_android_request_ip()P/InvokeN2nNative.n2n_android_set_ip_assigned_callback()P/InvokeN2nNative.n2n_android_get_assigned_ip()P/InvokeN2nEdge.IpAssignedeventN2nEdge.RequestIpAsync()methodN2nEdge.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:
- "TUN not ready X times in 5s" warnings in logs
- Packets received from network but not delivered to apps
- 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 freesencrypt_keyinternally - 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 | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0-android is compatible. net9.0-android35.0 is compatible. net10.0-android was computed. |
-
net9.0-android35.0
- Microsoft.Maui.Controls (>= 9.0.51)
- Microsoft.Maui.Essentials (>= 9.0.120)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
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