diff --git a/Multiplex/Models/ViewportReach.swift b/Multiplex/Models/ViewportReach.swift index 614087a4..2c4f2d98 100644 --- a/Multiplex/Models/ViewportReach.swift +++ b/Multiplex/Models/ViewportReach.swift @@ -56,6 +56,7 @@ enum ViewportReach: Equatable { if host == "localhost" || host.hasSuffix(".localhost") { return true } if host == "0.0.0.0" || host == "::" || host == "::1" { return true } if let octets = ipv4Octets(host) { return octets[0] == 127 } + if let octets = ipv4MappedOctets(host) { return octets[0] == 127 } return false } @@ -67,6 +68,13 @@ enum ViewportReach: Equatable { if octets[0] == 169, octets[1] == 254 { return true } return false } + if let octets = ipv4MappedOctets(host) { + if octets[0] == 10 { return true } + if octets[0] == 192, octets[1] == 168 { return true } + if octets[0] == 172, (16...31).contains(octets[1]) { return true } + if octets[0] == 169, octets[1] == 254 { return true } + return false + } // IPv6 unique-local (fc00::/7) and link-local (fe80::/10). if host.hasPrefix("fc") || host.hasPrefix("fd") || host.hasPrefix("fe80:") { return host.contains(":") @@ -78,6 +86,14 @@ enum ViewportReach: Equatable { return false } + /// The IPv4 payload of the common `::ffff:w.x.y.z` mapped form, or nil. + private static func ipv4MappedOctets(_ host: String) -> [Int]? { + guard let separator = host.lastIndex(of: ":"), + host[.. [Int]? { diff --git a/MultiplexTests/ViewportReachTests.swift b/MultiplexTests/ViewportReachTests.swift index e91256e2..efabf056 100644 --- a/MultiplexTests/ViewportReachTests.swift +++ b/MultiplexTests/ViewportReachTests.swift @@ -38,6 +38,13 @@ final class ViewportReachTests: XCTestCase { } } + func testIPv4MappedPrivateAddressesKeepTheirIPv4Reach() { + XCTAssertEqual(classify("http://[::ffff:192.168.1.68]:5173/"), .lan) + XCTAssertEqual(classify("http://[::ffff:10.0.0.5]/"), .lan) + XCTAssertEqual(classify("http://[::ffff:127.0.0.1]:8080/"), .remoteLoopback) + XCTAssertEqual(classify("http://[::ffff:8.8.8.8]/"), .internet) + } + func testUnqualifiedSingleLabelIsLAN() { // A bare machine name resolves through the local network's search // domains — LAN by construction. diff --git a/README.md b/README.md index a4c13c00..9977d23e 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,13 @@ Some features require Multiplex Pro; see [`docs/store-metadata.md`](docs/store-m ## Building -Requires Xcode with the visionOS SDK and [XcodeGen](https://github.com/yonaskolb/XcodeGen). The project is generated, so edit `project.yml`, not the `.xcodeproj`. +Requires Xcode with the visionOS SDK and [XcodeGen](https://github.com/yonaskolb/XcodeGen). Install the command-line prerequisites with [Homebrew](https://brew.sh/): + +```sh +brew install xcodegen swiftlint +``` + +The Xcode project is generated, so edit `project.yml`, not the `.xcodeproj`. Regenerate it after project configuration changes: ```sh xcodegen generate