native_mdns_scanner 1.3.0
native_mdns_scanner: ^1.3.0 copied to clipboard
Dart FFI bindings for macOS mDNS/Bonjour services. Provides native access to multicast DNS functionality on macOS.
native_mdns_scanner #
Dart FFI bindings for macOS mDNS/Bonjour services. This library provides native access to multicast DNS functionality on macOS through FFI bindings to Objective-C code.
Features #
- π Simple mDNS scanning - Discover devices by service type
- π― Simultaneous multi-service scanning - Scan multiple service types at once
- π Periodic scanning - Run queries at regular intervals
- β±οΈ Timing analysis - Analyze discovery patterns and performance
- π₯οΈ CLI tool - Command-line interface for quick testing
- π Rich device information - IP, port, TXT records, and discovery metadata
Platform Support #
- β macOS (arm64 + x86_64)
- β iOS (not tested)
- β Windows (not supported)
- β Linux (not supported)
Installation #
Add this to your pubspec.yaml:
dependencies:
native_mdns_scanner: ^1.0.0
Or install directly from GitHub:
dependencies:
native_mdns_scanner:
git:
url: https://github.com/changyy/ffi-mdns-macos-dart.git
Quick Start #
Basic Usage #
import 'package:native_mdns_scanner/native_mdns_scanner.dart';
void main() async {
final scanner = NativeMdnsScanner();
try {
// Scan for Chromecast devices
scanner.startScan('_googlecast._tcp');
// Wait for 10 seconds
await Future.delayed(Duration(seconds: 10));
scanner.stopScan();
// Get results
final devices = scanner.foundDevices;
for (final device in devices) {
print('Found: \\${device.name} at \\${device.ip}:\\${device.port}');
}
} finally {
scanner.dispose();
}
}
Simultaneous Multi-Service Scanning #
final scanner = NativeMdnsScanner();
try {
final devices = await scanner.scanMultipleServices([
'_googlecast._tcp', // Chromecast
'_airplay._tcp', // AirPlay
'_raop._tcp', // Remote Audio Output Protocol
], timeout: Duration(seconds: 15));
print('Found \\${devices.length} devices');
// Group by service type
final devicesByType = scanner.getDevicesByServiceType();
for (final serviceType in devicesByType.keys) {
final typeDevices = devicesByType[serviceType]!;
print('\\${serviceType}: \\${typeDevices.length} devices');
}
} finally {
scanner.dispose();
}
Periodic Scanning #
final scanner = NativeMdnsScanner();
try {
final devices = await scanner.scanMultipleServicesWithPeriodic([
'_googlecast._tcp',
],
timeout: Duration(seconds: 30), // Total scan time
queryInterval: Duration(seconds: 5), // Query every 5 seconds
);
// Analyze timing patterns
TimingAnalyzer.analyzeTimings(devices);
} finally {
scanner.dispose();
}
Event Callback & Custom Scan Duration #
You can receive device events in real time using the JSON callback interface. This is useful for UI updates or streaming results as they arrive.
Using startScanJson #
final scanner = NativeMdnsScanner();
final foundDevices = <DeviceInfo>[];
scanner.startScanJson('_googlecast._tcp', (json) {
if (json['type'] == 'device') {
foundDevices.add(DeviceInfo(
name: json['name'] ?? '',
ip: json['ip'] ?? '',
port: json['port'] ?? 0,
serviceType: json['type_name'] ?? '',
txtRecords: Map<String, String>.from(json['txt'] ?? {}),
));
print('Found device: \\${json['name']} at \\${json['ip']}:\\${json['port']}');
} else if (json['type'] == 'error') {
print('Error: \\${json['message']}');
}
}, debug: 2);
// Wait for 10 seconds
await Future.delayed(Duration(seconds: 10));
scanner.stopScan();
Using startPeriodicScanJsonWithDone (recommended new usage) #
final scanner = NativeMdnsScanner();
final foundDevices = <DeviceInfo>[];
await scanner.startPeriodicScanJsonWithDone(
'_googlecast._tcp',
(json) {
if (json['type'] == 'device') {
foundDevices.add(DeviceInfo(
name: json['name'] ?? '',
ip: json['ip'] ?? '',
port: json['port'] ?? 0,
serviceType: json['type_name'] ?? '',
txtRecords: Map<String, String>.from(json['txt'] ?? {}),
queryNumber: json['queryNumber'] ?? 0,
));
print('Found device: \\${json['name']} (query #\\${json['queryNumber']})');
} else if (json['type'] == 'error') {
print('Error: \\${json['message']}');
}
},
queryIntervalMs: 3000, // Query every 3 seconds
totalDurationMs: 12000, // Run for 12 seconds
debug: 2,
);
CLI Tool #
The package includes a command-line tool for quick testing and debugging:
# Install globally
dart pub global activate native_mdns_scanner
# Or run directly
dart run bin/mdns_cli.dart
CLI Examples #
# Simple scan
dart run bin/mdns_cli.dart scan _googlecast._tcp
# Scan multiple services
dart run bin/mdns_cli.dart multi _googlecast._tcp _airplay._tcp _raop._tcp
# Periodic scanning
dart run bin/mdns_cli.dart periodic _googlecast._tcp --interval 5 --duration 30
# Timing analysis
dart run bin/mdns_cli.dart timing _googlecast._tcp _airplay._tcp --timeout 20
CLI Options #
--timeout <seconds>: Scan timeout (default: 15)--interval <seconds>: Query interval for periodic scan (default: 5)--duration <seconds>: Total duration for periodic scan (default: 30)--help,-h: Show help message
Common Service Types #
| Service Type | Description | Example Devices |
|---|---|---|
_googlecast._tcp |
Google Cast | Chromecast, Google Home |
_airplay._tcp |
Apple AirPlay | Apple TV, AirPort Express |
_raop._tcp |
Remote Audio Output | AirPort Express, HomePod |
_http._tcp |
HTTP services | Web servers, cameras |
_ssh._tcp |
SSH servers | Raspberry Pi, servers |
_printer._tcp |
Network printers | HP, Canon printers |
_ipp._tcp |
Internet Printing | Modern network printers |
API Reference #
MdnsFfi Class #
The main class for mDNS operations is now recommended to be used as NativeMdnsScanner (a type alias for MdnsFfi).
Methods
startScan(String serviceType)- Start scanning for a service typestartPeriodicScan(String serviceType, {int queryIntervalMs, int totalDurationMs})- Start periodic scanningstopScan()- Stop all active scansscanMultipleServices(List<String> serviceTypes, {Duration timeout})- Scan multiple services simultaneouslyscanMultipleServicesWithPeriodic(List<String> serviceTypes, {Duration timeout, Duration queryInterval})- Periodic multi-service scanningisScanning()- Check if any scan is activefoundDevices- Get list of discovered devicesgetDevicesByServiceType()- Get devices grouped by service typedispose()- Clean up resources
DeviceInfo Class #
Represents a discovered mDNS service.
Properties
String name- Service nameString ip- IP addressint port- Port numberString serviceType- Service type (e.g., '_googlecast._tcp')Map<String, String> txtRecords- TXT record key-value pairsDateTime foundAt- Discovery timestampint queryNumber- Which query discovered this device
TimingAnalyzer Class #
Utility for analyzing discovery timing patterns.
Methods
static void analyzeTimings(List<DeviceInfo> devices)- Print detailed timing analysisstatic Map<String, dynamic> getStatistics(List<DeviceInfo> devices)- Get timing statisticsstatic String formatTime(DateTime time)- Format time as HH:mm:ss.SSS
Building from Source #
Prerequisites #
- macOS (for building the native library)
- Xcode command line tools
- Dart SDK 3.0+
Build Steps #
- Clone the repository:
git clone https://github.com/changyy/ffi-mdns-macos-dart.git
cd ffi-mdns-macos-dart
- Build the native library:
cd native
./build.sh
- Install Dart dependencies:
dart pub get
- Run examples:
dart run example/native_mdns_scanner_example.dart
dart run example/timing_test_example.dart
Project Structure #
ffi-mdns-macos-dart/
βββ lib/
β βββ native_mdns_scanner.dart # Main export
β βββ src/
β βββ mdns_bindings.dart # FFI bindings
β βββ device_info.dart # Device model
β βββ timing_analyzer.dart # Timing utilities
βββ native/
β βββ mdns_ffi.h # C header
β βββ mdns_ffi.m # Objective-C implementation
β βββ build.sh # Build script
β βββ libmdns_ffi.dylib # Compiled library
βββ example/
β βββ native_mdns_scanner_example.dart # Basic usage example
β βββ timing_test_example.dart # Timing analysis example
βββ bin/
β βββ mdns_cli.dart # CLI tool
βββ test/
βββ mdns_test.dart # Unit tests
Performance Notes #
- Simultaneous scanning: This library can scan multiple service types simultaneously, unlike many other mDNS libraries that scan sequentially
- Event processing: Uses a timer-based approach to process mDNS events efficiently
- Memory management: Properly manages native resources and prevents memory leaks
Troubleshooting #
Common Issues #
- Library not found: Make sure
libmdns_ffi.dylibis in thenative/directory - Permission denied: On macOS, you might need to allow network access in System Preferences
- No devices found: Some devices may not respond immediately; try increasing the timeout
Debug Mode #
Enable verbose logging by setting debug flags in your code:
// This will print detailed discovery information
final scanner = NativeMdnsScanner(debugLevel: 2); // 0=quiet, 1=error/result, 2=normal, 3=verbose
// Logs are automatically printed to console
Contributing #
- Fork the repository
- Create your feature branch:
git checkout -b feature/amazing-feature - Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
License #
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments #
- Built using Dart FFI for native interoperability
- Uses macOS Bonjour/mDNS APIs through Objective-C
- Inspired by the need for simultaneous multi-service mDNS scanning
Related Projects #
- multicast_dns - Pure Dart mDNS implementation
- bonsoir - Cross-platform service discovery
- nsd - Network Service Discovery plugin