host address retrieving through assigned subnet information and broadcast message to learn host address
This commit is contained in:
@@ -19,6 +19,7 @@ import io.flutter.plugin.common.MethodChannel
|
|||||||
import java.net.BindException
|
import java.net.BindException
|
||||||
import java.net.DatagramPacket
|
import java.net.DatagramPacket
|
||||||
import java.net.DatagramSocket
|
import java.net.DatagramSocket
|
||||||
|
import java.net.Inet4Address
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
@@ -167,11 +168,18 @@ class MlrsNetworkPlugin(
|
|||||||
val callback = object : ConnectivityManager.NetworkCallback() {
|
val callback = object : ConnectivityManager.NetworkCallback() {
|
||||||
override fun onAvailable(network: Network) {
|
override fun onAvailable(network: Network) {
|
||||||
currentNetwork = network
|
currentNetwork = network
|
||||||
val gateway = gatewayAddressOf(connectivityManager.getLinkProperties(network))
|
val linkProperties = connectivityManager.getLinkProperties(network)
|
||||||
|
val gateway = gatewayAddressOf(linkProperties)
|
||||||
|
val broadcast = broadcastAddressOf(linkProperties)
|
||||||
val ssid = ssidOf(connectivityManager.getNetworkCapabilities(network))
|
val ssid = ssidOf(connectivityManager.getNetworkCapabilities(network))
|
||||||
if (ssid != null) reportedSsid = ssid
|
if (ssid != null) reportedSsid = ssid
|
||||||
eventSink?.success(
|
eventSink?.success(
|
||||||
mapOf("type" to "available", "gatewayAddress" to gateway, "ssid" to ssid),
|
mapOf(
|
||||||
|
"type" to "available",
|
||||||
|
"gatewayAddress" to gateway,
|
||||||
|
"broadcastAddress" to broadcast,
|
||||||
|
"ssid" to ssid,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,6 +233,32 @@ class MlrsNetworkPlugin(
|
|||||||
return route?.gateway?.hostAddress
|
return route?.gateway?.hostAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Berechnet die Subnetz-Broadcast-Adresse (z.B. 192.168.4.255 fuer
|
||||||
|
* 192.168.4.42/24) aus der dem Geraet auf diesem Netz zugewiesenen
|
||||||
|
* IPv4-Adresse + Praefixlaenge. Dient als Bootstrap-Ziel fuer die erste
|
||||||
|
* MSP-Anfrage, wenn die tatsaechliche IP der mLRS-Bruecke (noch) nicht
|
||||||
|
* bekannt ist (Doku Kommunikationsschicht v2, Abschnitt 4/9:
|
||||||
|
* "Peer-Learning" versagt, wenn die Bruecke kein initiales Paket
|
||||||
|
* schickt und die geratene Standard-IP falsch liegt) - eine an die
|
||||||
|
* Broadcast-Adresse gesendete Anfrage erreicht die Bruecke unabhaengig
|
||||||
|
* von ihrer konkreten IP, ihre (unicast) Antwort verrät dann die echte
|
||||||
|
* Adresse.
|
||||||
|
*/
|
||||||
|
private fun broadcastAddressOf(linkProperties: LinkProperties?): String? {
|
||||||
|
val linkAddress = linkProperties?.linkAddresses
|
||||||
|
?.firstOrNull { it.address is Inet4Address } ?: return null
|
||||||
|
val ipBytes = linkAddress.address.address
|
||||||
|
val prefixLength = linkAddress.prefixLength
|
||||||
|
val broadcastBytes = ByteArray(4)
|
||||||
|
for (i in 0 until 4) {
|
||||||
|
val hostBitsInByte = (prefixLength - i * 8).coerceIn(0, 8)
|
||||||
|
val hostMaskByte = 0xFF shr hostBitsInByte
|
||||||
|
broadcastBytes[i] = (ipBytes[i].toInt() or hostMaskByte).toByte()
|
||||||
|
}
|
||||||
|
return InetAddress.getByAddress(broadcastBytes).hostAddress
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ermittelt die tatsaechliche SSID des verbundenen Netzes. Erfordert
|
* Ermittelt die tatsaechliche SSID des verbundenen Netzes. Erfordert
|
||||||
* normalerweise ACCESS_FINE_LOCATION, ausser fuer die anfragende App
|
* normalerweise ACCESS_FINE_LOCATION, ausser fuer die anfragende App
|
||||||
@@ -260,6 +294,10 @@ class MlrsNetworkPlugin(
|
|||||||
// hier erkennbar mit BindException scheitert (Doku 3/9: "ist
|
// hier erkennbar mit BindException scheitert (Doku 3/9: "ist
|
||||||
// 14550 belegt, ausweichen, aber protokollieren").
|
// 14550 belegt, ausweichen, aber protokollieren").
|
||||||
newSocket.reuseAddress = false
|
newSocket.reuseAddress = false
|
||||||
|
// Ohne dies wirft send() an eine Broadcast-Adresse
|
||||||
|
// (broadcastAddressOf()) eine SocketException - noetig fuer
|
||||||
|
// den Bootstrap-Fallback in UdpTransport.connect().
|
||||||
|
newSocket.broadcast = true
|
||||||
var fallbackFromPort: Int? = null
|
var fallbackFromPort: Int? = null
|
||||||
try {
|
try {
|
||||||
newSocket.bind(InetSocketAddress(requestedPort))
|
newSocket.bind(InetSocketAddress(requestedPort))
|
||||||
|
|||||||
@@ -10,14 +10,21 @@ sealed class MlrsNetworkEvent {
|
|||||||
/// Android hat eine zuvor bewilligte exakte SSID still erneut verbunden).
|
/// Android hat eine zuvor bewilligte exakte SSID still erneut verbunden).
|
||||||
/// [gatewayAddress] ist die Gateway-Adresse des Netzes, falls ermittelbar -
|
/// [gatewayAddress] ist die Gateway-Adresse des Netzes, falls ermittelbar -
|
||||||
/// dient als Vorbelegung der Zieladresse, bevor eine Gegenstelle aus dem
|
/// dient als Vorbelegung der Zieladresse, bevor eine Gegenstelle aus dem
|
||||||
/// Verkehr gelernt wurde (Doku Abschnitt 3). [ssid] ist die tatsaechliche
|
/// Verkehr gelernt wurde (Doku Abschnitt 3). [broadcastAddress] ist die aus
|
||||||
/// SSID des verbundenen Netzes, falls ermittelbar - wird gemerkt, damit
|
/// der zugewiesenen IPv4-Adresse + Praefixlaenge berechnete Subnetz-
|
||||||
/// kuenftige Verbindungen exakt (statt per Praefix) angefragt werden koennen
|
/// Broadcast-Adresse (z.B. 192.168.4.255) - robusterer Bootstrap-Fallback
|
||||||
/// (siehe [MlrsNetworkController.requestNetwork]).
|
/// als eine geratene feste Standard-IP, falls die mLRS-Bruecke kein
|
||||||
|
/// initiales Paket sendet, aus dem die App ihre Adresse lernen koennte: eine
|
||||||
|
/// an die Broadcast-Adresse gesendete Anfrage erreicht die Bruecke
|
||||||
|
/// unabhaengig von ihrer konkreten IP. [ssid] ist die tatsaechliche SSID des
|
||||||
|
/// verbundenen Netzes, falls ermittelbar - wird gemerkt, damit kuenftige
|
||||||
|
/// Verbindungen exakt (statt per Praefix) angefragt werden koennen (siehe
|
||||||
|
/// [MlrsNetworkController.requestNetwork]).
|
||||||
class MlrsNetworkAvailable extends MlrsNetworkEvent {
|
class MlrsNetworkAvailable extends MlrsNetworkEvent {
|
||||||
const MlrsNetworkAvailable({this.gatewayAddress, this.ssid});
|
const MlrsNetworkAvailable({this.gatewayAddress, this.broadcastAddress, this.ssid});
|
||||||
|
|
||||||
final String? gatewayAddress;
|
final String? gatewayAddress;
|
||||||
|
final String? broadcastAddress;
|
||||||
final String? ssid;
|
final String? ssid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +167,7 @@ class MethodChannelMlrsNetworkController implements MlrsNetworkController {
|
|||||||
return switch (map['type']) {
|
return switch (map['type']) {
|
||||||
'available' => MlrsNetworkAvailable(
|
'available' => MlrsNetworkAvailable(
|
||||||
gatewayAddress: map['gatewayAddress'] as String?,
|
gatewayAddress: map['gatewayAddress'] as String?,
|
||||||
|
broadcastAddress: map['broadcastAddress'] as String?,
|
||||||
ssid: map['ssid'] as String?,
|
ssid: map['ssid'] as String?,
|
||||||
),
|
),
|
||||||
'ssid' => MlrsNetworkSsidUpdated(map['ssid']! as String),
|
'ssid' => MlrsNetworkSsidUpdated(map['ssid']! as String),
|
||||||
|
|||||||
@@ -165,15 +165,24 @@ class UdpTransport implements LinkTransport {
|
|||||||
// Reihenfolge: explizit konfigurierter Host > vom Netz gemeldete
|
// Reihenfolge: explizit konfigurierter Host > vom Netz gemeldete
|
||||||
// Gateway-Adresse (bei der hier verwendeten lokalen, internetlosen
|
// Gateway-Adresse (bei der hier verwendeten lokalen, internetlosen
|
||||||
// WifiNetworkSpecifier-Anfrage in der Praxis meist gar nicht gesetzt,
|
// WifiNetworkSpecifier-Anfrage in der Praxis meist gar nicht gesetzt,
|
||||||
// da solche Netze i.d.R. keine Default-Route haben) > fest hinterlegte
|
// da solche Netze i.d.R. keine Default-Route haben) > Subnetz-
|
||||||
// Standard-IP der mLRS-Bruecke. Diese Vorbelegung wird durch das erste
|
// Broadcast-Adresse (Bootstrap-Fallback, siehe unten) > fest
|
||||||
// tatsaechlich empfangene Paket in [_onPacket] ueberschrieben - sie
|
// hinterlegte Standard-IP der mLRS-Bruecke als allerletzter Rueckfall.
|
||||||
// greift also nur, solange (noch) kein Paket gelernt wurde, z.B. wenn
|
// Diese Vorbelegung wird durch das erste tatsaechlich empfangene Paket
|
||||||
// die Bruecke ihr erstes Paket nicht zuverlaessig sendet oder es durch
|
// in [_onPacket] ueberschrieben - sie greift also nur, solange (noch)
|
||||||
// die unten beschriebene Startup-Race verpasst wuerde.
|
// kein Paket gelernt wurde.
|
||||||
|
//
|
||||||
|
// Die Broadcast-Adresse ist der eigentlich robuste Fallback: sendet
|
||||||
|
// die App ihre erste MSP-Anfrage dorthin statt an eine geratene feste
|
||||||
|
// Unicast-IP, erreicht sie die Bruecke unabhaengig davon, welche IP
|
||||||
|
// diese tatsaechlich hat - deren (unicast) Antwort lernt dann ueber
|
||||||
|
// [_onPacket] die echte Adresse. Das schliesst genau die Luecke, die
|
||||||
|
// entsteht, wenn die Bruecke selbst nie unaufgefordert sendet UND die
|
||||||
|
// geratene Standard-IP falsch liegt (dann kaeme ohne Broadcast
|
||||||
|
// ueberhaupt kein Paket an, aus dem gelernt werden koennte).
|
||||||
final prefillHost = (configuredHost != null && configuredHost.isNotEmpty)
|
final prefillHost = (configuredHost != null && configuredHost.isNotEmpty)
|
||||||
? configuredHost
|
? configuredHost
|
||||||
: (available.gatewayAddress ?? _defaultMlrsHost);
|
: (available.gatewayAddress ?? available.broadcastAddress ?? _defaultMlrsHost);
|
||||||
final parsed = InternetAddress.tryParse(prefillHost);
|
final parsed = InternetAddress.tryParse(prefillHost);
|
||||||
if (parsed != null) {
|
if (parsed != null) {
|
||||||
_peerAddress = parsed;
|
_peerAddress = parsed;
|
||||||
|
|||||||
@@ -189,8 +189,24 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'falls back to the default mLRS bridge IP when neither a host is '
|
'uses the reported subnet broadcast address as initial peer when no '
|
||||||
'configured nor a gateway address is reported',
|
'host is configured and no gateway address is reported',
|
||||||
|
() async {
|
||||||
|
final fake = FakeMlrsNetworkController();
|
||||||
|
final transport = await connectWithFake(
|
||||||
|
fake,
|
||||||
|
event: const MlrsNetworkAvailable(broadcastAddress: '192.168.4.255'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(transport.peerDescription, startsWith('192.168.4.255'));
|
||||||
|
|
||||||
|
transport.dispose();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'falls back to the default mLRS bridge IP when neither a host, a '
|
||||||
|
'gateway address, nor a broadcast address is reported',
|
||||||
() async {
|
() async {
|
||||||
final fake = FakeMlrsNetworkController();
|
final fake = FakeMlrsNetworkController();
|
||||||
final transport = await connectWithFake(fake);
|
final transport = await connectWithFake(fake);
|
||||||
|
|||||||
Reference in New Issue
Block a user