tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

Resolver.hs (3210B)


      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 IO.Resolver (Family(..), Resolution(..), resolveTCP, resolveWith, hostLimit, addressLimit, validHost) where
      6 import qualified IO.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` ()