Valider les adresses IP iOS : familles et normalisation

Valider les adresses IP iOS : familles et normalisation

Valider les adresses IP iOS : familles et normalisation

Vous avez modifié la configuration de connexion sous Windows, puis confié la validation à un Mac cloud. Vous découvrez alors que l’adresse IPv6 d’un même appareil est traitée comme deux destinations distinctes : à un endroit, tous les groupes de zéros sont écrits en entier ; à un autre, ils sont remplacés par ::. Si la liste des connexions, la déduplication et les règles d’accès comparent chacune les chaînes brutes, leurs résultats divergent. Voici comment mettre en place des vérifications hors ligne avec Swift et les fonctions d’analyse du système, sans serveur réel et sans dépendre de la disponibilité du réseau.

Définir d’abord les entrées acceptées

Cet exemple accepte uniquement des adresses IP littérales, et non des noms de domaine, des ports ou des URL complètes. Les adresses IPv4 doivent comporter quatre groupes décimaux ; les adresses IPv6 peuvent utiliser les notations standard prises en charge par le système. Tout caractère d’espacement en début ou en fin de chaîne entraîne un rejet, sans suppression silencieuse. On évite ainsi tout écart entre la valeur affichée dans l’interface et celle réellement utilisée pour la connexion.

Les crochets qui entourent une adresse IPv6 font partie de la représentation de l’hôte dans une URL, pas de l’adresse elle-même. Cet analyseur ne prend pas non plus en charge les identifiants de zone tels que %en0. Pour se connecter à une adresse de lien local, une autre couche doit recevoir explicitement l’adresse et les informations d’interface. Il ne faut pas simplement supprimer le signe de pourcentage et ce qui le suit, puis poursuivre la connexion.

Une analyse réussie indique uniquement que le texte peut représenter une adresse. Elle ne signifie ni que l’accès à cette adresse est autorisé, ni que l’appareil correspondant est en ligne.

Les adresses réservées à la documentation présentes dans les exemples servent uniquement à l’analyse locale. Le script n’établit aucune connexion vers elles.

Stocker le résultat sous forme de famille d’adresses et d’octets

Dans le répertoire du projet sur le Mac cloud, créez le fichier ip-contract.swift. Vérifiez d’abord que xcrun swiftc --version affiche les informations du compilateur, puis ajoutez le code ci-dessous. Aucune fonction susceptible de résoudre un nom d’hôte n’est utilisée ici, afin de ne pas mêler le comportement du DNS à la validation des adresses littérales.

import Darwin
import Foundation

struct IPAddress: Equatable {
    let family: Int32
    let bytes: [UInt8]
}

func parseIP(_ text: String) -> IPAddress? {
    guard !text.isEmpty,
          text.utf8.count <= 45,
          !text.utf8.contains(0) else {
        return nil
    }

    var v4 = in_addr()
    let result4 = text.withCString {
        inet_pton(AF_INET, $0, &v4)
    }
    if result4 == 1 {
        return IPAddress(
            family: AF_INET,
            bytes: withUnsafeBytes(of: &v4) { Array($0) }
        )
    }

    var v6 = in6_addr()
    let result6 = text.withCString {
        inet_pton(AF_INET6, $0, &v6)
    }
    if result6 == 1 {
        return IPAddress(
            family: AF_INET6,
            bytes: withUnsafeBytes(of: &v6) { Array($0) }
        )
    }

    return nil
}

func require(_ condition: Bool, _ message: String) {
    if !condition {
        fatalError(message)
    }
}

let good = [
    "192.0.2.1",
    "2001:db8::1",
    "2001:0DB8:0000:0000:0000:0000:0000:0001",
    "::",
    "::1",
    "::ffff:192.0.2.1"
]

for text in good {
    require(parseIP(text) != nil, "Rejected valid literal")
}

let bad = [
    "",
    " 192.0.2.1",
    "192.0.2.1\n",
    "192.0.2.256",
    "192.0.2",
    "192.0.02.1",
    "0xC0000201",
    "[2001:db8::1]",
    "fe80::1%en0",
    "2001:::1",
    "192.0.2.1\u{0}ignored"
]

for text in bad {
    require(parseIP(text) == nil, "Accepted invalid literal")
}

let short = parseIP("2001:db8::1")!
let expanded = parseIP(
    "2001:0DB8:0000:0000:0000:0000:0000:0001"
)!
require(short == expanded, "Equivalent IPv6 mismatch")
require(short.bytes.count == 16, "Invalid IPv6 length")

let v4 = parseIP("192.0.2.1")!
let mapped = parseIP("::ffff:192.0.2.1")!
require(v4.bytes.count == 4, "Invalid IPv4 length")
require(v4 != mapped, "Address families collapsed")

La limite de longueur correspond au budget en octets fixé par ce contrat d’analyse. Ce n’est pas une limite universelle applicable à toutes les adresses comportant un identifiant de zone. Les octets de l’adresse sont extraits directement des structures du système et conservent l’ordre des octets du réseau. Ne réinterprétez pas ensuite le bloc entier comme un entier dans l’ordre natif de la machine pour effectuer les comparaisons.

Compiler, exécuter et vérifier le comportement en cas d’échec

Compilez le script en un programme autonome. Vous pourrez ainsi l’intégrer aux tâches de compilation tout en évitant que différents modes de lancement n’influencent son exécution interprétée :

set -eu
mkdir -p .ip-check
xcrun swiftc ip-contract.swift -o .ip-check/ip-contract
.ip-check/ip-contract

Si toutes les vérifications réussissent, le programme ne produit aucune sortie et son code de retour est nul. Toute violation du contrat entraîne un arrêt avec un code non nul. Des vérifications explicites sont utilisées plutôt que assert, car les options d’optimisation peuvent supprimer les assertions.

Remplacez temporairement la comparaison d’égalité par une comparaison d’inégalité, vérifiez que la tâche échoue bien, puis rétablissez le code initial. Ce test négatif permet de repérer les pipelines qui ignorent le code de retour et affichent un succès alors qu’ils se contentent d’enregistrer les journaux. Ne vous contentez pas de constater que le script « a été exécuté une fois ».

Séparer normalisation et politique d’accès

Ne pas élargir arbitrairement la relation d’équivalence

Les formes compressée et complète d’une même adresse IPv6 doivent être considérées comme égales. En revanche, dans ce modèle, une adresse IPv4 et une adresse IPv6 mappant une adresse IPv4 appartiennent toujours à des familles distinctes. Si l’application doit les traiter comme une même destination logique, ajoutez une conversion explicite et testez les règles d’accès avant et après cette conversion. Supprimer un préfixe de chaîne ne suffit pas.

Pour l’affichage, inet_ntop peut produire une représentation textuelle uniforme. La déduplication et les décisions d’autorisation doivent, elles, continuer à s’appuyer sur le modèle issu de l’analyse. Ne considérez pas la forme textuelle produite par une version donnée du système comme un format de stockage immuable d’une version à l’autre.

Examiner les noms de domaine séparément

Le fait qu’un nom de domaine soit rejeté par cette fonction ne signifie pas qu’il est invalide : il ne correspond simplement pas au type d’entrée attendu. Si le produit prend aussi en charge les noms de domaine, prévoyez un point d’entrée distinct et, après résolution, vérifiez l’ensemble des adresses auxquelles la connexion doit réellement être établie. Contrôler uniquement le texte de l’hôte saisi par l’utilisateur ne permet pas de couvrir les changements des résultats de résolution.

Les adresses de bouclage, privées ou de lien local autorisées doivent être définies selon l’usage du produit. Un outil de débogage d’appareils et un service de récupération de ressources publiques sur Internet n’ont pas les mêmes règles. Ils ne devraient donc pas partager un booléen « adresse sûre » au sens mal défini.

Conserver les cas d’échec pour les tests de régression

Une fois l’analyseur intégré au code source partagé, exécutez les mêmes vecteurs de test dans les vérifications en ligne de commande sous macOS et dans la cible de tests iOS. Les premières permettent de repérer rapidement les erreurs dans un environnement distant ; la seconde confirme le comportement sur le système cible. La réussite des tests sur la machine hôte ne remplace pas la validation sur la plateforme cible.

À chaque correction, ajoutez l’entrée d’origine, la famille d’adresses attendue et le résultat attendu. Pour les octets nuls intégrés à une entrée, conservez impérativement les octets d’origine : l’affichage des journaux peut les masquer. Les chaînes C se terminent au premier octet nul ; sans contrôle à l’entrée, la validation risque donc de ne porter que sur un préfixe valide.

Pour finir, vérifiez trois points : des textes différents sont-ils bien reconnus comme une même adresse ? Les familles d’adresses distinctes restent-elles séparées ? Les entrées invalides sont-elles rejetées avant toute connexion ? Tant que ces contrats restent stables, vous disposez d’une base de vérification réexécutable pour ajouter ensuite des contrôles dans l’interface ou de nouvelles règles de connexion.

Questions fréquentes

Deux chaînes IPv6 différentes désignent-elles forcément deux adresses ?

Non. La compression des zéros, les zéros initiaux et la casse peuvent varier. Comparez la famille et les octets en ordre réseau après analyse.

Une adresse acceptée par inet_pton peut-elle être autorisée directement ?

Non. Les adresses de boucle locale, privées, de lien local et mappées exigent des règles distinctes. Vérifiez également les adresses issues de la résolution DNS.

Confiez vos tâches à un Mac cloud dédié

Louez un Mac mini physique dédié et choisissez la région et la durée de facturation adaptées à votre activité. Après activation, consultez les informations de connexion dans la console. En cas de problème, ouvrez un ticket depuis la console.

Choisir une offre Mac cloud

Choisissez une région adaptée à votre activité

Vérifiez la configuration et la durée, puis activez votre Mac mini physique dédié.