|
| 1 | +# Discovering devices on your local network (Mac) |
| 2 | + |
| 3 | +## DNS Resolvers |
| 4 | + |
| 5 | +Find which DNS servers your macOS system is querying for website lookups and how network traffic is being routed |
| 6 | + |
| 7 | +``` |
| 8 | +scutil --dns |
| 9 | +``` |
| 10 | + |
| 11 | +sample output |
| 12 | + |
| 13 | +``` |
| 14 | +DNS configuration |
| 15 | +
|
| 16 | +resolver #1 |
| 17 | + nameserver[0] : 2405:201:1006:e906::c0a8:1d01 |
| 18 | + nameserver[1] : 192.168.29.1 |
| 19 | + if_index : 12 (en0) |
| 20 | + flags : Request A records, Request AAAA records |
| 21 | + reach : 0x00020002 (Reachable,Directly Reachable Address) |
| 22 | +
|
| 23 | +resolver #2 |
| 24 | + domain : local |
| 25 | + options : mdns |
| 26 | + timeout : 5 |
| 27 | + flags : Request A records, Request AAAA records |
| 28 | + reach : 0x00000000 (Not Reachable) |
| 29 | + order : 300000 |
| 30 | +
|
| 31 | +resolver #3 |
| 32 | + domain : 254.169.in-addr.arpa |
| 33 | + options : mdns |
| 34 | + timeout : 5 |
| 35 | + flags : Request A records, Request AAAA records |
| 36 | + reach : 0x00000000 (Not Reachable) |
| 37 | + order : 300200 |
| 38 | +
|
| 39 | +resolver #4 |
| 40 | + domain : 8.e.f.ip6.arpa |
| 41 | + options : mdns |
| 42 | + timeout : 5 |
| 43 | + flags : Request A records, Request AAAA records |
| 44 | + reach : 0x00000000 (Not Reachable) |
| 45 | + order : 300400 |
| 46 | +
|
| 47 | +resolver #5 |
| 48 | + domain : 9.e.f.ip6.arpa |
| 49 | + options : mdns |
| 50 | + timeout : 5 |
| 51 | + flags : Request A records, Request AAAA records |
| 52 | + reach : 0x00000000 (Not Reachable) |
| 53 | + order : 300600 |
| 54 | +
|
| 55 | +resolver #6 |
| 56 | + domain : a.e.f.ip6.arpa |
| 57 | + options : mdns |
| 58 | + timeout : 5 |
| 59 | + flags : Request A records, Request AAAA records |
| 60 | + reach : 0x00000000 (Not Reachable) |
| 61 | + order : 300800 |
| 62 | +
|
| 63 | +resolver #7 |
| 64 | + domain : b.e.f.ip6.arpa |
| 65 | + options : mdns |
| 66 | + timeout : 5 |
| 67 | + flags : Request A records, Request AAAA records |
| 68 | + reach : 0x00000000 (Not Reachable) |
| 69 | + order : 301000 |
| 70 | +
|
| 71 | +DNS configuration (for scoped queries) |
| 72 | +
|
| 73 | +resolver #1 |
| 74 | + nameserver[0] : 2405:201:1006:e906::c0a8:1d01 |
| 75 | + nameserver[1] : 192.168.29.1 |
| 76 | + if_index : 12 (en0) |
| 77 | + flags : Scoped, Request A records, Request AAAA records |
| 78 | + reach : 0x00020002 (Reachable,Directly Reachable Address) |
| 79 | +
|
| 80 | +``` |
| 81 | + |
| 82 | +- `resolver #1` points to IP address of the router that will be queried for outside traffic, you can see this address change when you connect to work VPN or your personal device hotspot. |
| 83 | +- The local devices (`resolver #2`) which are reversed looked using mdns are part of non-scoped queries (which by definition seem like will be used for outside queries, however that's not the case). |
| 84 | + - Unlike resolver #1, Resolver #2 has a strict rule: `domain : local`. It catches anything ending in `.local` and tells the system to use the mDNS protocol instead of traditional DNS. |
| 85 | + - By keeping Resolver #2 non-scoped, mac ensures that if you switch from Wi-Fi to a wired Ethernet cable, local device discovery keeps working seamlessly without restarting your apps (separation of concerns). |
| 86 | + |
| 87 | +## DNS discovery using `dns-sd` |
| 88 | + |
| 89 | +### Watch for printers |
| 90 | + |
| 91 | +``` |
| 92 | +dns-sd -B _printer._tcp local. |
| 93 | +``` |
| 94 | +or |
| 95 | +``` |
| 96 | +dns-sd -B _http._tcp local. |
| 97 | +``` |
| 98 | + |
| 99 | +sample output |
| 100 | + |
| 101 | +``` |
| 102 | +Browsing for _printer._tcp.local. |
| 103 | +DATE: ---Tue 09 Jun 2026--- |
| 104 | +13:26:57.770 ...STARTING... |
| 105 | +Timestamp A/R Flags if Domain Service Type Instance Name |
| 106 | +13:26:57.771 Add 2 12 local. _printer._tcp. Canon XXXX series |
| 107 | +13:27:21.554 Rmv 0 12 local. _printer._tcp. Canon XXXX series |
| 108 | +``` |
| 109 | + |
| 110 | +### Watch for shared computers/file sharing |
| 111 | + |
| 112 | +``` |
| 113 | +dns-sd -B _smb._tcp local. |
| 114 | +``` |
| 115 | + |
| 116 | +sample output |
| 117 | + |
| 118 | +``` |
| 119 | +Browsing for _smb._tcp.local. |
| 120 | +DATE: ---Tue 09 Jun 2026--- |
| 121 | +13:26:28.548 ...STARTING... |
| 122 | +Timestamp A/R Flags if Domain Service Type Instance Name |
| 123 | +13:26:28.549 Add 2 12 local. _smb._tcp. Some Device |
| 124 | +13:27:21.554 Rmv 0 12 local. _smb._tcp. Some Device |
| 125 | +13:27:53.038 Add 2 12 local. _smb._tcp. Some Device |
| 126 | +13:27:55.302 Rmv 0 12 local. _smb._tcp. Some Device |
| 127 | +``` |
| 128 | + |
| 129 | +## Fun Stuff |
| 130 | + |
| 131 | +### Creating a fake printer |
| 132 | + |
| 133 | +``` |
| 134 | +dns-sd -R "Fake Office Printer" _printer._tcp local 9100 |
| 135 | +``` |
| 136 | + |
| 137 | +This might not be visible to other devices on the network if AP Isolation is enabled. |
| 138 | + |
| 139 | +For the sake of this infutile exercise, I connected my mac to my phone's hotspot (android, realme) |
| 140 | + |
| 141 | +1. Start a demo http server |
| 142 | + ``` |
| 143 | + sudo python3 -m http.server 631 |
| 144 | + ``` |
| 145 | +2. In another term window start a printer |
| 146 | + |
| 147 | + ``` |
| 148 | + dns-sd -R "Narzo Test Printer" _ipp._tcp local 631 txtvers=1 pdl=application/pdf |
| 149 | + ``` |
| 150 | +3. Download the [Service Browser App](https://play.google.com/store/apps/details?id=com.druk.servicebrowser&hl=en) to check if the mDNS local device is visible. Copy the IP address. |
| 151 | +4. Go to android option's to add a printer by IP address. |
| 152 | +5. You will see following log on the http server |
| 153 | + ``` |
| 154 | + Serving HTTP on :: port 631 (http://[::]:631/) ... |
| 155 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] code 501, message Unsupported method ('POST') |
| 156 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] "POST /ipp/print HTTP/1.1" 501 - |
| 157 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] code 501, message Unsupported method ('POST') |
| 158 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] "POST /ipp/print HTTP/1.1" 501 - |
| 159 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] code 501, message Unsupported method ('POST') |
| 160 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] "POST /ipp/printer HTTP/1.1" 501 - |
| 161 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] code 501, message Unsupported method ('POST') |
| 162 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] "POST /ipp/printer HTTP/1.1" 501 - |
| 163 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] code 501, message Unsupported method ('POST') |
| 164 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] "POST /ipp HTTP/1.1" 501 - |
| 165 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] code 501, message Unsupported method ('POST') |
| 166 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] "POST /ipp HTTP/1.1" 501 - |
| 167 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] code 501, message Unsupported method ('POST') |
| 168 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] "POST / HTTP/1.1" 501 - |
| 169 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] code 501, message Unsupported method ('POST') |
| 170 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] "POST / HTTP/1.1" 501 - |
| 171 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] code 400, message Bad request version ('@ßI^') |
| 172 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] "ùõ |
| 173 | + +n}¯@ßI^" 400 - |
| 174 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] code 501, message Unsupported method ('OPTIONS') |
| 175 | + ::ffff:10.233.72.164 - - [09/Jun/2026 14:52:02] "OPTIONS * HTTP/1.1" 501 - |
| 176 | + ``` |
| 177 | +6. Simulating a printer |
| 178 | + 1. Generate a ssl certificate. |
| 179 | + 2. Run the following script |
| 180 | + ``` |
| 181 | + sudo python3 -c ' |
| 182 | + from http.server import BaseHTTPRequestHandler, HTTPServer |
| 183 | + import ssl |
| 184 | +
|
| 185 | + class SecureFakePrinterHandler(BaseHTTPRequestHandler): |
| 186 | + def do_POST(self): |
| 187 | + # 1. Read incoming data length to ensure clean processing |
| 188 | + content_length = int(self.headers.get("Content-Length", 0)) |
| 189 | + if content_length > 0: |
| 190 | + self.rfile.read(content_length) |
| 191 | +
|
| 192 | + # 2. Build a valid, minimal IPP successful response header |
| 193 | + # Bytes represent: IPP version 2.0, status 0x0000 (successful-ok), |
| 194 | + # request-id 1, operation-attributes-tag, and end-of-attributes-tag. |
| 195 | + ipp_successful_header = b"\x02\x00\x00\x00\x00\x00\x00\x01\x01\x47\x00\x12\x61\x74\x74\x72\x69\x62\x75\x74\x65\x73\x2d\x63\x68\x61\x72\x73\x65\x74\x00\x05\x75\x74\x66\x2d\x38\x03" |
| 196 | +
|
| 197 | + # 3. Send headers matching the expected secure printing format |
| 198 | + self.send_response(200) |
| 199 | + self.send_header("Content-Type", "application/ipp") |
| 200 | + self.send_header("Content-Length", str(len(ipp_successful_header))) |
| 201 | + self.end_headers() |
| 202 | + |
| 203 | + # 4. Write the binary payload back to the Narzo |
| 204 | + self.wfile.write(ipp_successful_header) |
| 205 | +
|
| 206 | + def do_OPTIONS(self): |
| 207 | + self.send_response(200) |
| 208 | + self.send_header("Allow", "POST, OPTIONS") |
| 209 | + self.end_headers() |
| 210 | +
|
| 211 | + print("Secure Fake Printer with IPP payload listening on port 631..."); |
| 212 | + server = HTTPServer(("", 631), SecureFakePrinterHandler) |
| 213 | + context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) |
| 214 | + context.load_cert_chain(certfile="server.pem") |
| 215 | + server.socket = context.wrap_socket(server.socket, server_side=True) |
| 216 | + server.serve_forever() |
| 217 | + ' |
| 218 | + ``` |
| 219 | + 3. Start a secure broadcast: `dns-sd -R "Narzo Secure Printer" _ipps._tcp local 631 txtvers=1 pdl=application/pdf` |
| 220 | + 4. The log should NOT show any error logs, however the android will still say "no printer found", I will leave this as a TODO for future me. |
0 commit comments