Find IP address of Apple TV

Everything on my LAN is setup for DHCP from the home router. I can ping any device, including the Apple TV in my office, and resolve its IP address. Of course, DHCP IP numbers will change over time, so I wish to implement something like this...

let ip = ipFromString("office.local")
// returns "192.168.0.36"

I don't wish to open a connection, I only need the IP address.

So, I'm asking if there's a way to code this Swift?

Depending on where you are, sure. The easiest simple answer is to use SwiftNIO's SocketAddress.makeAddressResolvingHost(_:port:) and just place a dummy port in there. This only gives a partial answer though: there may be more than one IP address for a given hostname, and this will only provide one. If you need full generality you can call getaddrinfo directly, though the API for that is a bit of a bear.

1 Like

I don't wish to open a connection, I only need the IP address.

What do you plan to do with this IP address?

It’s hard to answer this question without knowing that because Apple devices can have many network interfaces, each with multiple IP addresses, with an arbitrary mix of IPv4 and IPv6. There is no one single ‘right’ IP address; it varies by context.

lukasa wrote:

If you need full generality you can call getaddrinfo directly,
though the API for that is a bit of a bear.

Indeed [1]. See this DevForums post for one way to tackle this.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

[1] Although the jury is still out as to exactly whether it’s a “bear” or a “pig” (-:

1 Like

Store it for use later. The address is passed from the Mac to an RPi server, which has the ability to power on/off another RPi which talks to a satellite tuner over USB. The tuner has closed firmware which requires a destination IP4 address to stream the video. My plan is to allow the Mac UI to select destinations for the stream - eg ATV1, ATV2 or the Mac (all capable of running VLC).

PS. if you want to know what I'm doing, my CAT 21 presentation is on YouTube

Store it for use later.

Hmmm, that seems quite brittle. How can you guarantee that the Apple TV won’t change IP address?

Would the following work?

  1. Get the Apple TV’s .local DNS name.

  2. Pass that to the RPi cluster.

  3. When the RPi starts the USB tuner, have it resolve the DNS name, select the IPv4 address (assuming there is one), and pass that to the USB tuner.

That way you’re guaranteed not to get stale info, at least at the start of the stream. (The Apple TV could, in theory, change IPv4 address while the stream is in progress, but that’s pretty likely.)

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

That's exactly what I'm intending. I know the office ATV is office.local. Another is lounge.local and my Mac is iMac-2015.local - I promise to buy a new one this year!.

The Mac UI will present those names to choose from and pass to the RPi each time the tuner restarts - this happens each time a new receive parameter is requested, which can be many times each session.

I have this working, but it returns the IP6 number.

func ipFromString(_ string: String) -> String {
    var ip = ""
    do {
        let sockAddress = try SocketAddress.makeAddressResolvingHost(string, port: 0)
        ip = sockAddress.ipAddress ?? ""
    } catch {
        print("Unable to resolve ip address for \(string)")
    }
    return ip
}

Do you happen to know how to get the IP4? I'm studying the source, but its somewhat above me.

This is the core loop:

        var info: UnsafeMutablePointer<addrinfo>?

        /* FIXME: this is blocking! */
        if getaddrinfo(host, String(port), nil, &info) != 0 {
            throw SocketAddressError.unknown(host: host, port: port)
        }

        defer {
            if info != nil {
                freeaddrinfo(info)
            }
        }

        if let info = info, let addrPointer = info.pointee.ai_addr {
            let addressBytes = UnsafeRawPointer(addrPointer)
            switch NIOBSDSocket.AddressFamily(rawValue: info.pointee.ai_family) {
            case .inet:
                return .v4(.init(address: addressBytes.load(as: sockaddr_in.self), host: host))
            case .inet6:
                return .v6(.init(address: addressBytes.load(as: sockaddr_in6.self), host: host))
            default:
                throw SocketAddressError.unsupported
            }
        } else {
            /* this is odd, getaddrinfo returned NULL */
            throw SocketAddressError.unsupported
        }

You should be able to copy this out fairly easily. To make it return only IPv4 addresses you can pass an appropriate "hint" by changing the call to getaddrinfo to:

var hints = addrinfo()
hints.ai_family = AF_INET

if getaddrinfo(host, String(port), &hints, &info) != 0 {
    throw SocketAddressError.unknown(host: host, port: port)
}

1 Like

@lukasa @eskimo I gave up with SocketAddress.makeAddressResolvingHost(_:port:) and resorted to writing yet another bash script.

#!/bin/bash

# usage: pingonce.sh office.local
# returns: a valid IP4 Address or not
TXT=$(ping -c1 $1)
A=${TXT#*(}
B=${A%%)*}
echo -n $B