stun 1.7.1
stun: ^1.7.1 copied to clipboard
Complete STUN (RFC 5389/5780) implementation for NAT traversal, public IP discovery, and NAT type detection. Supports IPv4/IPv6 dual-stack, P2P applications, and WebRTC.
import 'dart:io';
import 'package:singleton_manager/singleton_manager.dart';
import 'package:stun/src/factories/dual_stun_registry_wiring.dart';
import 'package:stun/stun.dart';
void main() async {
print('=== STUN Example ===\n');
// Pattern 1: Using external socket (traditional approach)
print('--- Pattern 1: External Socket Management ---\n');
await _exampleWithExternalSocket();
print('\n--- Pattern 2: Internal Socket Management ---\n');
// Pattern 2: Let StunHandler manage the socket internally
await _exampleWithInternalSocket();
print('\n--- Pattern 3: DI-based Singleton ---\n');
await _exampleWithDI();
print('\n=== Example Complete ===');
}
/// Example 1: Traditional approach with external socket management
Future<void> _exampleWithExternalSocket() async {
// Create a UDP socket manually
final socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0);
print('Local socket created on port: ${socket.port}\n');
// Configure STUN handler with external socket
final handler = StunHandler(
socket,
address: 'stun.l.google.com',
port: 19302,
);
try {
// 1. Get local network information
print('1. Getting local network information...');
final localInfo = await handler.performLocalRequest();
print(' Local IP: ${localInfo.localIpv4}');
print(' Local Port: ${localInfo.localPortIpv4}\n');
// 2. Perform STUN request to get public IP
print('2. Performing STUN request...');
final response = await handler.performStunRequest();
print(' ✅ Success!');
print(' Public IP: ${response.publicIp(InternetAddressType.IPv4)}');
print(' Public Port: ${response.publicPort(InternetAddressType.IPv4)}');
print(' IP Version: IPv4');
print(' Port Mapping: ${localInfo.localPortIpv4} → ${response.publicPort(InternetAddressType.IPv4)}');
// 3. Try different STUN server with NEW socket
// (RawDatagramSocket streams are single-subscription, so create a new socket)
print('\n3. Trying different STUN server with new socket...');
handler.close();
final socket2 = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0);
final handler2 = StunHandler(
socket2,
address: 'stun1.l.google.com',
port: 19302,
);
final response2 = await handler2.performStunRequest();
print(' Public IP from second server: ${response2.publicIp(InternetAddressType.IPv4)}');
// Verify both servers report the same IP
if (response.publicIp(InternetAddressType.IPv4) == response2.publicIp(InternetAddressType.IPv4)) {
print(' ✅ Both servers agree on public IP');
}
handler2.close();
} catch (e) {
print('Error: $e');
} finally {
handler.close();
}
}
/// Example 3: DI-based singleton using `main_injection.dart` and
/// `RegistryManager` from `singleton_manager`
Future<void> _exampleWithDI() async {
try {
const injector = DualStunInjector();
const key = 'example';
// RawDatagramSocket isn't `@dependencyInjectable` itself, so DualStunInjector
// wires it in via connectDualStunHandlerSockets (called from its
// beforeRegisterAllSingletonsStunAsync override) before the generated
// factories build IStunHandler (ipv4/ipv6). DI-resolved handlers use the
// STUN config defaults for the server; call setStunServer afterwards for
// a custom one.
//
// Connects every @dependencyInjectable class (DualStunHandler,
// DualStunHandlerMigratable, StunHandler, StunHandlerMigratable) to
// RegistryManager.instance under `key`.
await injector.registerAllSingletonsStunAsync(key: key);
print('✅ DI container initialized\n');
// Retrieve the singleton from the registry
final stun = RegistryManager.instance.getInstance<IDualStunHandler>(
key: key,
);
// Perform requests through the injected singleton
print('1. Performing STUN request via DI singleton...');
final response = await stun.performStunRequest();
if (response.publicIp(InternetAddressType.IPv4) != null) {
print(' IPv4 Public IP: ${response.publicIp(InternetAddressType.IPv4)}');
}
if (response.publicIp(InternetAddressType.IPv6) != null) {
print(' IPv6 Public IP: ${response.publicIp(InternetAddressType.IPv6)}');
}
print('2. Performing local request...');
final localInfo = await stun.performLocalRequest();
if (localInfo.localIpv4 != null) {
print(' IPv4 Local: ${localInfo.localIpv4}:${localInfo.localPortIpv4}');
}
if (localInfo.localIpv6 != null) {
print(' IPv6 Local: ${localInfo.localIpv6}:${localInfo.localPortIpv6}');
}
stun.close();
} catch (e) {
print('Error: $e');
}
}
/// Example 2: Modern approach with internal socket management
Future<void> _exampleWithInternalSocket() async {
try {
// Create handler with internal socket management (factory method)
// Socket is created automatically and can be recreated on network errors
final handler = await StunHandler.withoutSocket(
address: 'stun.l.google.com',
port: 19302,
type: InternetAddressType.IPv4, // Set to InternetAddressType.IPv6 for IPv6
);
print('✅ Handler created with automatic socket management\n');
// 1. Get local network information
print('1. Getting local network information...');
final localInfo = await handler.performLocalRequest();
print(' Local IP: ${localInfo.localIpv4}');
print(' Local Port: ${localInfo.localPortIpv4}\n');
// 2. Ping STUN server
print('2. Pinging STUN server...');
final isReachable = await handler.pingStunServer();
print(' Server reachable: $isReachable\n');
// 3. Perform STUN request (socket auto-recreates on network errors)
print('3. Performing STUN request...');
final response = await handler.performStunRequest();
print(' ✅ Success!');
print(' Public IP: ${response.publicIp(InternetAddressType.IPv4)}');
print(' Public Port: ${response.publicPort(InternetAddressType.IPv4)}');
print(' IP Version: IPv4');
print(' Port Mapping: ${localInfo.localPortIpv4} → ${response.publicPort(InternetAddressType.IPv4)}');
handler.close();
} catch (e) {
print('Error: $e');
}
}