Notice: Due to size constraints and loading performance considerations, scripts referenced in blog posts are not attached directly. To request access, please complete the following form: Script Request Form Note: A Google account is required to access the form.
Disclaimer: I do not accept responsibility for any issues arising from scripts being run without adequate understanding. It is the user's responsibility to review and assess any code before execution. More information

Building an iOS App to Discover macOS Caching Server

I wanted to build an iOS application that could discover macOS caching servers on the local network, I thought it would be straightforward. I was wrong. 

What followed was a deep dive into Apple's content caching discovery mechanisms, network protocols, and the many ways assumptions can lead you down the wrong path.

The iOS Application

I needed an iOS app that could:

  • Request local network permissions at startup
  • Discover macOS caching servers on the same Wi-Fi network
  • Display results professionally with server status
  • Allow manual IP address scanning as a fallback

The challenge was understanding how macOS caching servers actually work in modern versions of macOS, versus outdated documentation and assumptions.

Lets take a look at the visuals, first we have the main application screen:


Then when you enter an IP address if will then scan that server for a MacOS Content Caching service:

When a server is found you will get a confirmation as you can see below, which will show the port and IP:


If there are no caching servers then you will be informed of that in the application as below that "no caching servers found"

The First Pitfall: Bonjour Assumptions

My initial approach assumed that macOS caching servers would advertise themselves via Bonjour service discovery, like many network services do.

// WRONG APPROACH - Don't do this
let browser = NWBrowser(for: .bonjourWithTXTRecord(type: "_http._tcp", domain: nil), using: .tcp)

Modern macOS caching servers don't use traditional Bonjour service advertising, whereas older versions of MacOS did - this approach led nowhere and wasted considerable development time.

The Second Pitfall: Standard HTTP Ports

Next, I assumed caching servers would listen on standard HTTP ports like 80 or 443.

// WRONG ASSUMPTION - Caching servers don't use these ports
let standardPorts = [80, 443, 8080, 8443]

macOS caching servers listen on ephemeral ports in the range 49152-65535. These ports are assigned dynamically at startup, making discovery more challenging.

Understanding the Real Discovery Mechanism

Through research, I discovered that macOS caching server discovery works through a sophisticated process:

  1. The caching server registers with Apple's cloud service at lcdn-registration.apple.com
  2. Client devices contact Apple's locator service at lcdn-locator.apple.com
  3. Apple matches clients to registered caching servers based on public IP addresses
  4. Results are stored in system files and used by applications like the App Store

The official command-line tool AssetCacheLocatorUtil performs this discovery:

AssetCacheLocatorUtil

This tool revealed that caching servers appear with entries like:

192.168.1.100:49159, rank 1, guid <guid>..., supports shared caching: yes

The Third Pitfall: Automatic Discovery Complexity

I attempted to replicate Apple's discovery protocol within the iOS app:

private func contactAppleLocatorService(localIP: String, publicIP: String) async throws -> [CachingServer] {
    let url = URL(string: "https://lcdn-locator.apple.com/lcdn/locator")!
    // ... complex implementation
}

While this approach is theoretically correct, it's overly complex for most use cases. The manual IP scanning approach proved more reliable and practical.

Manual IP Scanning with Proper Verification

The final solution focuses on what actually works: scanning specific IP addresses for caching servers on ephemeral ports.

Setting Up the Xcode Project

  1. Create a new iOS project:

    • File → New → Project → iOS → App

    • Interface: SwiftUI
    • Language: Swift|


  2. Add required permissions in project settings:

    • Target → Info → Custom iOS Target Properties
    • Add Privacy - Local Network Usage Description


    • Add Bonjour services array with _aaplcache._tcp

The Core of the iOS Application : Network Manager

class NetworkManager: ObservableObject {
    @Published var hasLocalNetworkPermission = false
    @Published var isScanning = false
    @Published var discoveredServers: [CachingServer] = []
    @Published var scanningStatus = ""
    
    func scanIPForCachingServer(_ ipAddress: String) {
        guard hasLocalNetworkPermission else { return }
        guard isValidIPAddress(ipAddress) else { return }
        
        Task {
            await scanEphemeralPorts(for: ipAddress)
        }
    }
}

Port Scanning Discovery

The key insight was understanding which ports to scan:

private func scanEphemeralPorts(for ipAddress: String) async {
    let commonCachingPorts = [
        49152, 49153, 49154, 49155, 49156, 49157, 49158, 49159, 49160,
        49200, 49268, 49300, 49313, 49400, 49500, 49558, 49600, 
        50000, 51000, 51858, 52000, 52613, 53000, 60000, 60093
    ]
    
    for port in commonCachingPorts {
        let isResponding = try await testConnection(ip: ipAddress, port: port)
        if isResponding {
            // Add to discovered servers
        }
    }
}

The Fourth Pitfall: TCP Connection vs. HTTP Verification

Initially, I assumed that any open TCP port in the ephemeral range was a caching server:

// TOO SIMPLISTIC - Just checks if port accepts connections
private func testConnection(ip: String, port: Int) async throws -> Bool {
    // TCP connection test only
    return portIsOpen
}

This led to false positives from other services running on those ports.

Optimized Scanning : Apple-Specific HTTP Header Verification

The breakthrough came when I tested the actual caching server manually:

curl -v http://localhost:49159/

This revealed that caching servers return HTTP 400 Bad Request with a distinctive header:

< HTTP/1.1 400 Bad Request
< X-Apple-Cache-Session: VLXIhAoVV7Ij

The final verification function:

private func verifyCachingServer(ip: String, port: Int) async throws -> Bool {
    let url = URL(string: "http://\(ip):\(port)/")!
    let (_, response) = try await URLSession.shared.data(from: url)
    
    if let httpResponse = response as? HTTPURLResponse {
        // Check for Apple Cache specific headers - the smoking gun!
        if let cacheSession = httpResponse.value(forHTTPHeaderField: "X-Apple-Cache-Session") {
            return true // Definitely a caching server
        }
        
        // Accept 400 Bad Request as valid (caching servers often return this)
        if httpResponse.statusCode == 400 {
            return true
        }
    }
    
    return false
}

Adding App Icons

I thought it was worth designing a new icon for this application, however, I’m not a graphic designer, but I did find a very good logo generator that took my human words and converted them logos - which I simply adapted for icon files, this is the icon I have chosen to use:

Before distributing via Test Flight, an app icon is required:

  1. Generate a single 1024x1024 icon using an icon generation utility
  2. The icon must be in PNG format (no transparency, square dimensions)
  3. In Xcode Project Navigator (left sidebar): Click on Assets.xcassets


  4. Click on "AppIcon" in the Assets list (or create "New iOS App Icon" if missing)
  5. Drag your 1024x1024 PNG file into the single App Store 1024pt slot



  6. Xcode automatically generates all other required sizes

Publishing to Test Flight

Note : Your application cannot be published to Test Flight unless it has an icon

  1. Set deployment target to "Any iOS Device (arm64)"
  2. Product menu → Archive
  3. In Organizer: Click "Distribute App"


  4. Select "App Store Connect"
  5. Upload to TestFlight






  6. Add external testers via App Store Connect













  7. Install TestFlight app on target devices
  8. Install your app via TestFlight




Key Lessons Learned

Test on Real Hardware

Network discovery features must be tested on physical devices. The iOS Simulator cannot properly test local network functionality.

Understand the Actual Protocol

The breakthrough came from understanding that caching servers return HTTP 400 with Apple-specific headers, not the HTTP 200 you might expect from a web server.

Iterative Problem Solving

Each failure taught us something important:

  • Bonjour isn't used → Led to port scanning approach
  • Standard ports don't work → Discovered ephemeral port ranges
  • TCP isn't enough → Required HTTP verification
  • Generic HTTP checks give false positives → Apple-specific headers are the solution

The Final Application : Caching Checker

The finished app successfully:

  • Requests and handles local network permissions properly
  • Scans ephemeral port ranges where caching servers actually operate
  • Verifies discovered services are genuine Apple caching servers
  • Provides professional UI with proper error handling and status updates
  • Dismisses keyboard appropriately when interacting with the interface

Verification Results

Caching Server ON:

🍎 APPLE CACHE SERVER DETECTED! Session: VLXIhAoVV7Ij
Found: 192.168.1.100:49159

Caching Server OFF:

No caching servers found

The application now reliably distinguishes between active caching servers and other network services, providing network administrators with an accurate tool for auditing their Apple content caching infrastructure.

Previous Post Next Post

نموذج الاتصال