diff --git a/CHANGELOG.md b/CHANGELOG.md index 85ec69c16..0c33380c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added +* [PostIt/Calls] Allow directly assigned public IPv4 addresses for direct SRTP calls, alongside LAN/VPN and loopback addresses. Share unicast validation between interface discovery, media and server signaling; continue rejecting unspecified, link-local, multicast, reserved and IPv6 addresses. Keep explicit address-disclosure consent and no automatic NAT traversal. * [PostIt] Update bundled settings with the default podcast title, camera/microphone preferences and French language selection. ## [1.0.8-rc34] - unstable diff --git a/doc/architecture/audio-video-calls.md b/doc/architecture/audio-video-calls.md index 4c4ee0f26..789c587ac 100644 --- a/doc/architecture/audio-video-calls.md +++ b/doc/architecture/audio-video-calls.md @@ -5,7 +5,7 @@ Ce document décrit l'architecture cible et l'implémentation commencée. La préférence de confidentialité est persistée et éditable dans le profil. La signalisation est intégrée au chat, désactivée par défaut. Un premier -transport natif **Android et Desktop Linux sur LAN/VPN** est raccordé : +transport natif **Android et Desktop Linux en IPv4 directe (LAN/VPN ou IP publique)** est raccordé : captures Podcast existantes et SRTP direct, sans libwebrtc ni nouvelle couche JNI WebRTC. Il ne réalise pas encore la cible Internet, TURN ou groupe. Il complète la [topologie de PostIt](postit.md) et les @@ -18,7 +18,17 @@ La cible couvre : - les appels de groupe ; - les clients Browser, Android et Desktop. -## Première implémentation Android et Desktop Linux : SRTP sur LAN/VPN +## Première implémentation Android et Desktop Linux : SRTP direct IPv4 + +Les adresses IPv4 publiques directement assignees a une interface locale active +sont egalement disponibles, avec la meme validation unicast cote client, +media et signalisation serveur. Les adresses non specifiees, link-local, +multicast, reservees (240/4), IPv6 et les formes non canoniques sont refusees. +Il faut autoriser en entree les deux ports UDP consecutifs choisis et assurer +la joignabilite entre les pairs. Ce support n'ajoute ni ICE/TURN ni traversee +NAT automatique : l'adresse publique d'un routeur ne remplace pas une adresse +assignee a la machine. Le consentement explicite au partage d'IP et le refus +du mode direct pour les comptes masquant leur IP restent inchanges. Cette tranche réutilise la capture caméra/microphone et les encodeurs des Podcasts, mais **pas leur segmentation HLS ni leurs envois de fichiers** : @@ -67,7 +77,7 @@ de fichier média temporaire ou de moteur WebRTC Android ajouté. H.264/AAC. La version Debian 13 a été utilisée pour les essais Linux. Les deux comptes doivent être présents dans la même salle existante et ne pas se bloquer mutuellement. -3. Dans « Appel direct (LAN/VPN) », actualiser les périphériques, +3. Dans « Appel direct IPv4 (LAN/VPN ou IP publique) », actualiser les périphériques, sélectionner la caméra, le microphone et une IPv4 privée locale accessible depuis le pair. Les adresses RFC1918 et de bouclage sont acceptées ; pas de DNS, IPv6 ou adresse publique. Le bouclage sert aux @@ -79,7 +89,7 @@ de fichier média temporaire ou de moteur WebRTC Android ajouté. adresse doivent utiliser des paires distinctes. Aucun pare-feu n'est modifié automatiquement. 5. Saisir le pseudo exact du destinataire, confirmer la divulgation de - l'adresse LAN/VPN, puis appeler. Le destinataire choisit ses propres + l'adresse IPv4 locale (LAN/VPN ou publique), puis appeler. Le destinataire choisit ses propres périphériques et son adresse, confirme et accepte explicitement. Aucun accès à la caméra/microphone ne commence avant l'acceptation ; l'émission attend également la destination validée du pair. @@ -140,7 +150,8 @@ l'ajout d'un moteur WebRTC ou d'une cryptographie propre au projet. Il n'y a aucun repli direct contournant cette préférence. - Pas d'ICE, de STUN, de TURN, de traversée NAT automatique, de groupe, d'interopérabilité Browser, de négociation de codecs ni - d'adaptation de débit. Ce mode exige un LAN/VPN routable entre les pairs. + d'adaptation de débit. Ce mode exige une connectivite IPv4 directe entre les pairs + (LAN/VPN ou adresse publique assignee), avec les ports UDP ouverts. - H.264 à 30 images/s, cible 1 200 kbit/s ; AAC mono 48 kHz, 96 kbit/s. Ces réglages ne constituent pas une garantie de latence ni de débit. Le retour local n'émet pas le son du microphone. diff --git a/src/PostIt/PostIt.Tests/LanCallViewModelTests.cs b/src/PostIt/PostIt.Tests/LanCallViewModelTests.cs index 316997292..9de24bbcb 100644 --- a/src/PostIt/PostIt.Tests/LanCallViewModelTests.cs +++ b/src/PostIt/PostIt.Tests/LanCallViewModelTests.cs @@ -114,6 +114,18 @@ public sealed class LanCallViewModelTests return vm; } + [AvaloniaFact] + public async Task Public_ipv4_can_be_selected_for_an_explicitly_consented_direct_call() + { + var signaling = new Signaling(); + await using var vm = Configure(signaling, (_, _) => throw new Exception("No media before acceptance")); + vm.Addresses.Add("8.8.8.8"); + vm.Address = "8.8.8.8"; + await vm.InviteCommand.ExecuteAsync(null); + Assert.Equal(1, signaling.Invites); + Assert.True(vm.Busy); + } + [AvaloniaFact] public async Task No_capture_starts_before_explicit_acceptance_and_endpoint_exchange() { diff --git a/src/PostIt/PostIt/Services/LibVlcLanCallMedia.cs b/src/PostIt/PostIt/Services/LibVlcLanCallMedia.cs index da7488a46..785838c02 100644 --- a/src/PostIt/PostIt/Services/LibVlcLanCallMedia.cs +++ b/src/PostIt/PostIt/Services/LibVlcLanCallMedia.cs @@ -35,7 +35,7 @@ public sealed class LibVlcLanCallMedia : IAsyncDisposable Endpoint = new(localAddress, port, Convert.ToHexString(RandomNumberGenerator.GetBytes(16)), Convert.ToHexString(RandomNumberGenerator.GetBytes(14))); if (!Endpoint.IsValid()) - throw new ArgumentException("Adresse IPv4 privee et port UDP pair (1024-65534) requis."); + throw new ArgumentException("Adresse IPv4 unicast et port UDP pair (1024-65534) requis."); using (var reservation = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) using (var rtcp = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) { diff --git a/src/PostIt/PostIt/ViewModels/Chat/LanCallViewModel.cs b/src/PostIt/PostIt/ViewModels/Chat/LanCallViewModel.cs index a2055f913..88340b853 100644 --- a/src/PostIt/PostIt/ViewModels/Chat/LanCallViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/Chat/LanCallViewModel.cs @@ -116,7 +116,7 @@ public partial class LanCallViewModel : ObservableObject, IAsyncDisposable foreach (var address in NetworkInterface.GetAllNetworkInterfaces() .Where(n => n.OperationalStatus == OperationalStatus.Up) .SelectMany(n => n.GetIPProperties().UnicastAddresses) - .Select(a => a.Address.ToString()).Where(LanCallNetwork.IsPrivateIPv4) + .Select(a => a.Address.ToString()).Where(LanCallNetwork.IsUnicastIPv4) .Distinct().OrderBy(a => IPAddress.IsLoopback(IPAddress.Parse(a)))) Addresses.Add(address); Address = preferred == null ? Addresses.FirstOrDefault() : Addresses.FirstOrDefault(a => a == preferred); @@ -181,7 +181,7 @@ public partial class LanCallViewModel : ObservableObject, IAsyncDisposable Status = Cameras.Count == 0 ? "Aucune camera detectee. Verifiez les peripheriques puis actualisez." : Incoming && _call != null ? $"{_call.Caller} propose un appel dans {_call.RoomName}. Selectionnez vos peripheriques puis acceptez ou refusez." - : "Selectionnez le pair, l'adresse LAN/VPN et les peripheriques. Utilisez un casque."; + : "Selectionnez le pair, l'adresse IPv4 locale ou publique et les peripheriques. Utilisez un casque."; if (_settings != null) { _settings.PodcastCameras = Cameras.ToArray(); @@ -200,10 +200,10 @@ public partial class LanCallViewModel : ObservableObject, IAsyncDisposable private void ValidateSelection() { if (!AllowAddressDisclosure) - throw new InvalidOperationException("Confirmez le partage de votre adresse LAN/VPN avec le pair."); + throw new InvalidOperationException("Confirmez le partage de votre adresse IPv4 avec le pair."); if (Camera == null || Microphone == null) throw new InvalidOperationException("Une camera et un microphone sont requis."); - if (Address == null || !Addresses.Contains(Address) || !LanCallNetwork.IsPrivateIPv4(Address) || + if (Address == null || !Addresses.Contains(Address) || !LanCallNetwork.IsUnicastIPv4(Address) || !int.TryParse(Port, out var port) || port is < 1024 or > 65534 || port % 2 != 0) throw new InvalidOperationException("Selectionnez une adresse locale et un port UDP pair (1024-65534)."); _activeCamera = Camera.Value; diff --git a/src/PostIt/PostIt/Views/Chat/ChatPage.axaml b/src/PostIt/PostIt/Views/Chat/ChatPage.axaml index d9e513f97..92a7ed7e7 100644 --- a/src/PostIt/PostIt/Views/Chat/ChatPage.axaml +++ b/src/PostIt/PostIt/Views/Chat/ChatPage.axaml @@ -103,16 +103,16 @@ Content="Envoyer" /> - -