Content Filter remoteEndpoint from Chrome

I've developed a network content filter extension for macOS.

When overriding the handleNewFlow method, I want to examine the hostname for the given flow. I can do this for browsers like Safari, Firefox, and DuckDuckGo using flow.url?.host (WebKit flows) or (flow as? NEFilterSocketFlow)?.remoteHostname (Firefox flows).

However, for Google Chrome, these properties return nil, and I only get an outgoing IP address using socketFlow.remoteEndpoint as? NWHostEndpoint. How can I retrieve the outgoing domain for flows from Google Chrome?

I've tried resolving the IP to a domain name, but in most cases, I'm unable to retrieve the domain name using the following functions I found on forum posts:

func reverseDNS(ip: String) -> String {
        var results: UnsafeMutablePointer<addrinfo>? = nil
        defer {
            if let results = results {
                freeaddrinfo(results)
            }
        }
        let error = getaddrinfo(ip, nil, nil, &results)
        if (error != 0) {
            NSLog("Unable to reverse ip: \(ip)")
            return ip
        }

        for addrinfo in sequence(first: results, next: { $0?.pointee.ai_next }) {
            guard let pointee = addrinfo?.pointee else {
                NSLog("Unable to reverse ip: \(ip)")
                return ip
            }

            let hname = UnsafeMutablePointer<Int8>.allocate(capacity: Int(NI_MAXHOST))
            defer {
                hname.deallocate()
            }
            let error = getnameinfo(pointee.ai_addr, pointee.ai_addrlen, hname, socklen_t(NI_MAXHOST), nil, 0, 0)
            if (error != 0) {
                continue
            }
            return String(cString: hname)
        }

        return ip
    }
func resolveIP(_ ipAddress: String) -> String? {
        var hints = addrinfo(
            ai_flags: AI_NUMERICHOST,
            ai_family: AF_UNSPEC,
            ai_socktype: SOCK_STREAM,
            ai_protocol: 0,
            ai_addrlen: 0,
            ai_canonname: nil,
            ai_addr: nil,
            ai_next: nil
        )
        
        var res: UnsafeMutablePointer<addrinfo>? = nil
        let status = getaddrinfo(ipAddress, nil, &hints, &res)
        
        guard status == 0, let result = res else {
            print("Error: \(String(cString: gai_strerror(status)))")
            return nil
        }
        
        var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST))
        if let addr = result.pointee.ai_addr {
            let addrLen = socklen_t(result.pointee.ai_addrlen)
            if getnameinfo(addr, addrLen, &hostBuffer, socklen_t(hostBuffer.count), nil, 0, 0) == 0 {
                freeaddrinfo(res)
                return String(cString: hostBuffer)
            }
        }
        
        freeaddrinfo(res)
        return nil
    }

I know that Little Snitch can block and display domain name requests using a content filter, even in Google Chrome, so I'm certain it's possible. However, I'm unsure how to accomplish this. Can anyone assist me in resolving IP addresses to hostnames for most IP addresses, or in obtaining the hostnames directly from the flow on macOS?

Answered by DTS Engineer in 796839022
I only get an outgoing IP address using socketFlow.remoteEndpoint as? NWHostEndpoint.

Right. This is fallout from the app using the resolve-then-connect approach rather than one of our connect-by-name APIs. I talk about this in some detail in TN3151 Choosing the right networking API, specifically in the Connect by name section. You should feel free to imagine that “It causes interoperability problems with content filters” is another bullet in the list of reasons why resolve-then-connect is an anti-pattern )-:

I've tried resolving the IP to a domain name, but in most cases, I'm unable to retrieve the domain name

Right. Reverse DNS has always been problematic but, even if you do get a result, it’s unlikely to be useful. Many website use CDNs, and the reverse DNS of the CDN server IPs has nothing to do with the address used to find it. For example:

% host easydns.org
easydns.org has address 216.220.40.250
…
% host 216.220.40.250
250.40.220.216.in-addr.arpa domain name pointer 250.40.220-216.q9.net.

You can make a little progress here when the connection uses TLS. The trick is to allow the Client Hello to go by and look at the DNS name in the SNI. Of course, that’ll fail about once Encrypted Client Hello is deployed widely.

In situations like this it’s common for me to suggest that you file a bug report requesting a better solution. However, that’s not appropriate here. There’s nothing that macOS can do about this. If the app chooses to resolve-then-connect, the system only sees the IP address. Moreover, it’s not uncommon for apps like this to use their own DNS resolver [1], so the system isn’t even involved in the DNS resolution.


Oh, one more: If you continue doing the reverse DNS thing, don’t use the synchronous blocking APIs. Rather, use the DNS-SD API.

Note that DNS-SD has a dedicated forward DNS API, DNSServiceGetAddrInfo, but not dedicated reverse DNS API. You have to use the lower-level DNSServiceQueryRecord.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Another anti-pattern that I call out in TN3151.

Accepted Answer
I only get an outgoing IP address using socketFlow.remoteEndpoint as? NWHostEndpoint.

Right. This is fallout from the app using the resolve-then-connect approach rather than one of our connect-by-name APIs. I talk about this in some detail in TN3151 Choosing the right networking API, specifically in the Connect by name section. You should feel free to imagine that “It causes interoperability problems with content filters” is another bullet in the list of reasons why resolve-then-connect is an anti-pattern )-:

I've tried resolving the IP to a domain name, but in most cases, I'm unable to retrieve the domain name

Right. Reverse DNS has always been problematic but, even if you do get a result, it’s unlikely to be useful. Many website use CDNs, and the reverse DNS of the CDN server IPs has nothing to do with the address used to find it. For example:

% host easydns.org
easydns.org has address 216.220.40.250
…
% host 216.220.40.250
250.40.220.216.in-addr.arpa domain name pointer 250.40.220-216.q9.net.

You can make a little progress here when the connection uses TLS. The trick is to allow the Client Hello to go by and look at the DNS name in the SNI. Of course, that’ll fail about once Encrypted Client Hello is deployed widely.

In situations like this it’s common for me to suggest that you file a bug report requesting a better solution. However, that’s not appropriate here. There’s nothing that macOS can do about this. If the app chooses to resolve-then-connect, the system only sees the IP address. Moreover, it’s not uncommon for apps like this to use their own DNS resolver [1], so the system isn’t even involved in the DNS resolution.


Oh, one more: If you continue doing the reverse DNS thing, don’t use the synchronous blocking APIs. Rather, use the DNS-SD API.

Note that DNS-SD has a dedicated forward DNS API, DNSServiceGetAddrInfo, but not dedicated reverse DNS API. You have to use the lower-level DNSServiceQueryRecord.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Another anti-pattern that I call out in TN3151.

Content Filter remoteEndpoint from Chrome
 
 
Q