Resolver.hs (3214B)
1 {-# LANGUAGE ScopedTypeVariables #-} 2 -- Explicit host name resolution, not an implicit part of connection acquisition. 3 -- getaddrinfo may buffer more results and delay cancellation; only the published 4 -- endpoint prefix is bounded here. No worker is abandoned on interruption. 5 module IOV2.Resolver (Family(..), Resolution(..), resolveTCP, resolveWith, hostLimit, addressLimit, validHost) where 6 import qualified IOV2.Resources as R 7 import qualified Network.Socket as N 8 import Control.Exception 9 10 hostLimit, addressLimit :: Int 11 hostLimit = 253 12 addressLimit = 64 13 data Family = IPv4Only | IPv6Only | AnyFamily deriving (Eq, Show) 14 data Resolution = Resolution [R.Endpoint] Bool deriving (Eq, Show) 15 validHost :: String -> Bool 16 validHost [] = False 17 validHost host = walk hostLimit host 18 where walk _ [] = True 19 walk 0 _ = False 20 walk n (c:rest) = ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || 21 (c >= '0' && c <= '9') || c == '-' || c == '.') && walk (n-1) rest 22 23 resolveTCP :: String -> Family -> Integer -> Int -> IO (Either R.Problem Resolution) 24 resolveTCP host family port limit = resolveWith host port limit $ do 25 let af = case family of IPv4Only -> N.AF_INET; IPv6Only -> N.AF_INET6; AnyFamily -> N.AF_UNSPEC 26 hints = N.defaultHints {N.addrFamily=af, N.addrSocketType=N.Stream, 27 N.addrFlags=[N.AI_NUMERICSERV]} 28 infos <- N.getAddrInfo (Just hints) (Just host) (Just (show port)) 29 pure [endpoint | info <- infos, Just endpoint <- [convert (N.addrAddress info)]] 30 where convert (N.SockAddrInet p h) = Just (R.IPv4 (N.hostAddressToTuple h) (fromIntegral p)) 31 convert (N.SockAddrInet6 p _ h scope) = Just (R.IPv6 (N.hostAddress6ToTuple h) scope (fromIntegral p)) 32 convert _ = Nothing 33 34 -- Injected query supports deterministic failure/bound tests without public DNS. 35 resolveWith :: String -> Integer -> Int -> IO [R.Endpoint] -> IO (Either R.Problem Resolution) 36 resolveWith host port limit query 37 | not (validHost host) = pure (Left (R.Problem "invalid-argument" "resolve" "expected bounded ASCII host name" "none")) 38 | port < 0 || port > 65535 || limit <= 0 || limit > addressLimit = 39 pure (Left (R.Problem "invalid-bound" "resolve" "" "none")) 40 | otherwise = (query >>= bounded limit [] >>= pure . Right) `catch` \(e :: IOException) -> 41 pure (Left (R.Problem "resolution-failed" "resolve" (take 512 (displayException e)) "none")) 42 where 43 bounded 0 acc rest = do 44 truncated <- evaluate (not (null rest)) 45 finish acc truncated 46 bounded _ acc [] = finish acc False 47 bounded n acc (first:rest) = forceEndpoint first `seq` bounded (n-1) (first:acc) rest 48 finish acc truncated = do 49 let prefix = reverse acc 50 -- Neither a lazy split nor endpoint projections may retain the discarded 51 -- host-result tail/AddrInfo records after returning the bounded reply. 52 _ <- evaluate (foldr seq () prefix) 53 pure (Resolution prefix truncated) 54 55 forceEndpoint :: R.Endpoint -> () 56 forceEndpoint (R.IPv4 (a,b,c,d) p) = a `seq` b `seq` c `seq` d `seq` p `seq` () 57 forceEndpoint (R.IPv6 (a,b,c,d,e,f,g,h) scope p) = 58 a `seq` b `seq` c `seq` d `seq` e `seq` f `seq` g `seq` h `seq` scope `seq` p `seq` ()