API to parse DHCP option 121 returned from DHCPInfoGetOptionData

I am trying to fetch static routes added on my mac machine by DHCP server. I am able to get DHCP info using SCDynamicStoreCopyDHCPInfo and then get DHCP options using DHCPInfoGetOptionData. However, I could not find any API that can help me parse the raw data returned by DHCPInfoGetOptionData. Since macOS is parsing and adding these static routes and also displaying them in AdditionalRoutes of service, is there any API available to developers to parse this raw data into destination, mask and gateway?

Accepted Reply

is there any API available to developers to parse this raw data into destination, mask and gateway?

No. You’re right that macOS has this code, but it’s not exposed as an API.

Having said that, the structure defined in RFC 3442 isn’t to too hard to parse. I’ve included a snippet below that should do the trick.

Note This code relies on the helpers from this post.

Share and Enjoy

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

func parseDCHPOption121(_ input: Data) throws -> [DHCPOption121Entry] {
    var result: [DHCPOption121Entry] = []
    var residual = input
    while !residual.isEmpty {
        guard let prefix = residual.parseBigEndian(UInt8.self) else {
            throw DHCPOptionError.prefixExpected
        }
        let prefixByteCount: Int
        switch prefix {
        case 0: prefixByteCount = 0
        case 1...8: prefixByteCount = 1
        case 9...16: prefixByteCount = 2
        case 17...24: prefixByteCount = 3
        case 25...32: prefixByteCount = 4
        default:
            throw DHCPOptionError.prefixOutOfRange
        }
        guard var networkBytes = residual.parseBytes(count: prefixByteCount) else {
            throw DHCPOptionError.desinationExpected
        }
        networkBytes.append(contentsOf: .init(repeating: 0, count: 4 - prefixByteCount))
        let network = networkBytes.parseBigEndian(UInt32.self)!
        guard var routerBytes = residual.parseBytes(count: 4) else {
            throw DHCPOptionError.routerExpected
        }
        let router = routerBytes.parseBigEndian(UInt32.self)!
        result.append(.init(prefix: prefix, network: .init(s_addr: network.bigEndian), router: .init(s_addr: router.bigEndian)))
    }
    return result
}

struct DHCPOption121Entry: CustomDebugStringConvertible {
    var prefix: UInt8
    var network: in_addr
    var router: in_addr
    
    var debugDescription: String {
        "\(String(cString: inet_ntoa(self.network)))/\(self.prefix) -> \(String(cString: inet_ntoa(self.router)))"
    }
}

enum DHCPOptionError: Error {
    case prefixExpected
    case prefixOutOfRange
    case desinationExpected
    case routerExpected
}

let input = Data([
 // destination N           router N
    0,                      1, 2, 3, 4,
    8, 10,                  4, 5, 6, 7,
    24, 10, 0, 0,           8, 9, 10, 11,
    16, 10, 17,             12, 13, 14, 15,
    24, 10, 27, 129,        16, 17, 18, 19,
    25, 10, 229, 0, 128,    20, 21, 22, 23,
    32, 10, 198, 122, 47,   24, 25, 26, 27,
])

func test() throws {
    let result = try parseDCHPOption121(input)
    for r in result {
        print(r)
    }
    // prints:
    //
    // 0.0.0.0/0 -> 1.2.3.4
    // 10.0.0.0/8 -> 4.5.6.7
    // 10.0.0.0/24 -> 8.9.10.11
    // 10.17.0.0/16 -> 12.13.14.15
    // 10.27.129.0/24 -> 16.17.18.19
    // 10.229.0.128/25 -> 20.21.22.23
    // 10.198.122.47/32 -> 24.25.26.27
}
  • Thanks for your reply and the snippet. I just wanted to make sure I wasn't implementing something for which an API already exists.

Add a Comment

Replies

is there any API available to developers to parse this raw data into destination, mask and gateway?

No. You’re right that macOS has this code, but it’s not exposed as an API.

Having said that, the structure defined in RFC 3442 isn’t to too hard to parse. I’ve included a snippet below that should do the trick.

Note This code relies on the helpers from this post.

Share and Enjoy

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

func parseDCHPOption121(_ input: Data) throws -> [DHCPOption121Entry] {
    var result: [DHCPOption121Entry] = []
    var residual = input
    while !residual.isEmpty {
        guard let prefix = residual.parseBigEndian(UInt8.self) else {
            throw DHCPOptionError.prefixExpected
        }
        let prefixByteCount: Int
        switch prefix {
        case 0: prefixByteCount = 0
        case 1...8: prefixByteCount = 1
        case 9...16: prefixByteCount = 2
        case 17...24: prefixByteCount = 3
        case 25...32: prefixByteCount = 4
        default:
            throw DHCPOptionError.prefixOutOfRange
        }
        guard var networkBytes = residual.parseBytes(count: prefixByteCount) else {
            throw DHCPOptionError.desinationExpected
        }
        networkBytes.append(contentsOf: .init(repeating: 0, count: 4 - prefixByteCount))
        let network = networkBytes.parseBigEndian(UInt32.self)!
        guard var routerBytes = residual.parseBytes(count: 4) else {
            throw DHCPOptionError.routerExpected
        }
        let router = routerBytes.parseBigEndian(UInt32.self)!
        result.append(.init(prefix: prefix, network: .init(s_addr: network.bigEndian), router: .init(s_addr: router.bigEndian)))
    }
    return result
}

struct DHCPOption121Entry: CustomDebugStringConvertible {
    var prefix: UInt8
    var network: in_addr
    var router: in_addr
    
    var debugDescription: String {
        "\(String(cString: inet_ntoa(self.network)))/\(self.prefix) -> \(String(cString: inet_ntoa(self.router)))"
    }
}

enum DHCPOptionError: Error {
    case prefixExpected
    case prefixOutOfRange
    case desinationExpected
    case routerExpected
}

let input = Data([
 // destination N           router N
    0,                      1, 2, 3, 4,
    8, 10,                  4, 5, 6, 7,
    24, 10, 0, 0,           8, 9, 10, 11,
    16, 10, 17,             12, 13, 14, 15,
    24, 10, 27, 129,        16, 17, 18, 19,
    25, 10, 229, 0, 128,    20, 21, 22, 23,
    32, 10, 198, 122, 47,   24, 25, 26, 27,
])

func test() throws {
    let result = try parseDCHPOption121(input)
    for r in result {
        print(r)
    }
    // prints:
    //
    // 0.0.0.0/0 -> 1.2.3.4
    // 10.0.0.0/8 -> 4.5.6.7
    // 10.0.0.0/24 -> 8.9.10.11
    // 10.17.0.0/16 -> 12.13.14.15
    // 10.27.129.0/24 -> 16.17.18.19
    // 10.229.0.128/25 -> 20.21.22.23
    // 10.198.122.47/32 -> 24.25.26.27
}
  • Thanks for your reply and the snippet. I just wanted to make sure I wasn't implementing something for which an API already exists.

Add a Comment