docs: cookbook recipe for getaddrinfo IPv4+IPv6 resolution + SSRF resolve-pin

ober

dd183c50f677b6bfb464b2108f81e23b915cdc11

diff --git a/data/cookbooks.sexp b/data/cookbooks.sexp
index 0f7ee2a..00d6cfc 100644
--- a/data/cookbooks.sexp
+++ b/data/cookbooks.sexp
@@ -7054,4 +7054,16 @@
      "index" "lookup")
    ("title"
      .
-     "Line-offset index + binary search for O(log n) offset->line/col")))
+     "Line-offset index + binary search for O(log n) offset->line/col"))
+ (("code"
+    .
+    "(library (example resolve)\n  (export resolve-host-addresses)\n  (import (chezscheme) (only (jerboa core) def))\n\n  (def _libc-loaded\n    (let ((v (getenv \"JERBOA_STATIC\")))\n      (if (and v (not (string=? v \"\")) (not (string=? v \"0\")))\n          #f (load-shared-object #f))))\n  (def c-getaddrinfo (foreign-procedure \"getaddrinfo\" (string string void* void*) int))\n  (def c-freeaddrinfo (foreign-procedure \"freeaddrinfo\" (void*) void))\n  (def c-inet-ntop (foreign-procedure \"inet_ntop\" (int void* u8* int) void*))\n  (def AF_INET 2) (def AF_UNSPEC 0) (def SOCK_STREAM 1)\n  (def INET_ADDRSTRLEN 16) (def INET6_ADDRSTRLEN 46)\n\n  ;; ai_addr moves by OS; ai_family@4 and ai_next@40 are stable.\n  (def ai-addr-offset\n    (let* ([mt (symbol->string (machine-type))]\n           [has? (lambda (sub)\n                   (let ([ml (string-length mt)] [sl (string-length sub)])\n                     (let loop ([i 0])\n                       (cond [(> (+ i sl) ml) #f]\n                             [(string=? sub (substring mt i (+ i sl))) #t]\n                             [else (loop (+ i 1))]))))])\n      (cond [(or (has? \"osx\") (has? \"darwin\") (has? \"fb\") (has? \"ob\") (has? \"nb\")) 32]\n            [else 24])))\n\n  ;; Pass the family straight to inet_ntop so the OS AF_INET6 constant is used.\n  ;; in_addr @ sockaddr+4; in6_addr @ sockaddr+8 on every supported OS.\n  (def (sockaddr->ip family addr)\n    (let-values ([(src len) (if (= family AF_INET)\n                              (values (+ addr 4) INET_ADDRSTRLEN)\n                              (values (+ addr 8) INET6_ADDRSTRLEN))])\n      (let ([buf (make-bytevector len 0)])\n        (and (not (= (c-inet-ntop family src buf len) 0))\n             (let loop ([i 0])\n               (cond [(= i len) #f]\n                     [(= (bytevector-u8-ref buf i) 0)\n                      (utf8->string (let ([b (make-bytevector i)])\n                                      (bytevector-copy! buf 0 b 0 i) b))]\n                     [else (loop (+ i 1))]))))))\n\n  (def (collect node acc)\n    (if (= node 0) (reverse acc)\n      (let* ([fam (foreign-ref 'int node 4)]\n             [ap  (foreign-ref 'void* node ai-addr-offset)]\n             [ip  (and (not (= ap 0)) (sockaddr->ip fam ap))]\n             [nx  (foreign-ref 'void* node 40)])\n        (collect nx (if ip (cons ip acc) acc)))))\n\n  (def (resolve-host-addresses host)\n    (let ([hints (foreign-alloc 48)])\n      (do ([i 0 (+ i 1)]) ((= i 48)) (foreign-set! 'unsigned-8 hints i 0))\n      (foreign-set! 'int hints 4 AF_UNSPEC)\n      (foreign-set! 'int hints 8 SOCK_STREAM)\n      (let ([rp (foreign-alloc 8)])\n        (foreign-set! 'void* rp 0 0)\n        (let ([rc (c-getaddrinfo host #f hints rp)])\n          (foreign-free hints)\n          (unless (= rc 0) (foreign-free rp)\n            (error 'resolve-host-addresses \"DNS failed\" host rc))\n          (let* ([res (foreign-ref 'void* rp 0)] [ips (collect res '())])\n            (foreign-free rp)\n            (unless (= res 0) (c-freeaddrinfo res))\n            ips)))))\n)") ("id" . "getaddrinfo-ipv4-ipv6-resolve-and-ssrf-pin")
+   ("imports" "(chezscheme)" "(only (jerboa core) def)")
+   ("notes"
+     .
+     "Hard-won offsets: ai_family@4, ai_next@40 stable; ai_addr@24 (glibc) vs 32 (BSD/macOS). sockaddr in_addr@4, in6_addr@8. AF_UNSPEC=0 requests both families. Pass the read ai_family directly to inet_ntop (don't hardcode AF_INET6=10/30). For SSRF rebinding safety: resolve once, validate EVERY returned IP against the blocklist (reject if any is non-public), pin one validated IP, and connect to it (tcp-connect-binary IP / rustls-connect-address host IP) — never re-resolve at connect. See lib/std/net/request.ss ssrf-resolve-safe-ip.")
+   ("tags" "getaddrinfo" "dns" "ipv6" "inet_ntop" "ssrf"
+     "std net request")
+   ("title"
+     .
+     "Portable getaddrinfo IPv4+IPv6 resolution and DNS-rebinding-safe SSRF pinning")))