summaryrefslogtreecommitdiff
path: root/dht/Presence/DNSCache.hs
diff options
context:
space:
mode:
authorJoe Crayne <joe@jerkface.net>2020-01-03 15:35:23 -0500
committerJoe Crayne <joe@jerkface.net>2020-01-03 17:26:06 -0500
commit31b799222cb76cd0002d9a3cc5b340a7b6fed139 (patch)
tree8b834e455529fb270375e4967d1acad56553544f /dht/Presence/DNSCache.hs
parent1e03ed3670a8386ede93a09fa0c67785e7da6478 (diff)
server library.
Diffstat (limited to 'dht/Presence/DNSCache.hs')
-rw-r--r--dht/Presence/DNSCache.hs285
1 files changed, 0 insertions, 285 deletions
diff --git a/dht/Presence/DNSCache.hs b/dht/Presence/DNSCache.hs
deleted file mode 100644
index 9bb354e1..00000000
--- a/dht/Presence/DNSCache.hs
+++ /dev/null
@@ -1,285 +0,0 @@
1-- | Both 'getAddrInfo' and 'getHostByAddr' have hard-coded timeouts for
2-- waiting upon network queries that can be a little too long for some use
3-- cases. This module wraps both of them so that they block for at most one
4-- second. It caches late-arriving results so that they can be returned by
5-- repeated timed-out queries.
6--
7-- In order to achieve the shorter timeout, it is likely that the you will need
8-- to build with GHC's -threaded option. Otherwise, if the wrapped FFI calls
9-- to resolve the address will block Haskell threads. Note: I didn't verify
10-- this.
11{-# LANGUAGE TupleSections #-}
12{-# LANGUAGE RankNTypes #-}
13{-# LANGUAGE CPP #-}
14module DNSCache
15 ( DNSCache
16 , reverseResolve
17 , forwardResolve
18 , newDNSCache
19 , parseAddress
20 , unsafeParseAddress
21 , strip_brackets
22 , withPort
23 ) where
24
25import Control.Concurrent.ThreadUtil
26import Control.Arrow
27import Control.Concurrent.STM
28import Data.Text ( Text )
29import Network.Socket ( SockAddr(..), AddrInfoFlag(..), defaultHints, getAddrInfo, AddrInfo(..) )
30import Data.Time.Clock ( UTCTime, getCurrentTime, diffUTCTime )
31import System.IO.Error ( isDoesNotExistError )
32import System.Endian ( fromBE32, toBE32 )
33import Control.Exception ( handle )
34import Data.Map ( Map )
35import qualified Data.Map as Map
36import qualified Network.BSD as BSD
37import qualified Data.Text as Text
38import Control.Monad
39import Data.Function
40import Data.List
41import Data.Ord
42import Data.Maybe
43import System.IO.Error
44import System.IO.Unsafe
45
46import SockAddr ()
47import ControlMaybe ( handleIO_ )
48import GetHostByAddr ( getHostByAddr )
49import Control.Concurrent.Delay
50import DPut
51import DebugTag
52
53type TimeStamp = UTCTime
54
55data DNSCache =
56 DNSCache
57 { fcache :: TVar (Map Text [(TimeStamp, SockAddr)])
58 , rcache :: TVar (Map SockAddr [(TimeStamp, Text)])
59 }
60
61
62newDNSCache :: IO DNSCache
63newDNSCache = do
64 fcache <- newTVarIO Map.empty
65 rcache <- newTVarIO Map.empty
66 return DNSCache { fcache=fcache, rcache=rcache }
67
68updateCache :: Eq x =>
69 Bool -> TimeStamp -> [x] -> Maybe [(TimeStamp,x)] -> Maybe [(TimeStamp,x)]
70updateCache withScrub utc xs mys = do
71 let ys = maybe [] id mys
72 ys' = filter scrub ys
73 ys'' = map (utc,) xs ++ ys'
74 minute = 60
75 scrub (t,x) | withScrub && diffUTCTime utc t < minute = False
76 scrub (t,x) | x `elem` xs = False
77 scrub _ = True
78 guard $ not (null ys'')
79 return ys''
80
81dnsObserve :: DNSCache -> Bool -> TimeStamp -> [(Text,SockAddr)] -> STM ()
82dnsObserve dns withScrub utc obs = do
83 f <- readTVar $ fcache dns
84 r <- readTVar $ rcache dns
85 let obs' = map (\(n,a)->(n,a `withPort` 0)) obs
86 gs = do
87 g <- groupBy ((==) `on` fst) $ sortBy (comparing fst) obs'
88 (n,_) <- take 1 g
89 return (n,map snd g)
90 f' = foldl' updatef f gs
91 hs = do
92 h <- groupBy ((==) `on` snd) $ sortBy (comparing snd) obs'
93 (_,a) <- take 1 h
94 return (a,map fst h)
95 r' = foldl' updater r hs
96 writeTVar (fcache dns) f'
97 writeTVar (rcache dns) r'
98 where
99 updatef f (n,addrs) = Map.alter (updateCache withScrub utc addrs) n f
100 updater r (a,ns) = Map.alter (updateCache withScrub utc ns) a r
101
102make6mapped4 :: SockAddr -> SockAddr
103make6mapped4 addr@(SockAddrInet6 {}) = addr
104make6mapped4 addr@(SockAddrInet port a) = SockAddrInet6 port 0 (0,0,0xFFFF,fromBE32 a) 0
105
106tryForkOS :: String -> IO () -> IO ThreadId
107tryForkOS lbl action = catchIOError (forkOSLabeled lbl action) $ \e -> do
108 dput XMisc $ "DNSCache: Link with -threaded to avoid excessively long time-out."
109 forkLabeled lbl action
110
111
112-- Attempt to resolve the given domain name. Returns an empty list if the
113-- resolve operation takes longer than the timeout, but the 'DNSCache' will be
114-- updated when the resolve completes.
115--
116-- When the resolve operation does complete, any entries less than a minute old
117-- will be overwritten with the new results. Older entries are allowed to
118-- persist for reasons I don't understand as of this writing. (See 'updateCache')
119rawForwardResolve ::
120 DNSCache -> (Text -> IO ()) -> Int -> Text -> IO [SockAddr]
121rawForwardResolve dns onFail timeout addrtext = do
122 r <- atomically newEmptyTMVar
123 mvar <- interruptibleDelay
124 rt <- tryForkOS ("resolve."++show addrtext) $ do
125 resolver r mvar
126 startDelay mvar timeout
127 did <- atomically $ tryPutTMVar r []
128 when did (onFail addrtext)
129 atomically $ readTMVar r
130 where
131 resolver r mvar = do
132 xs <- handle (\e -> let _ = isDoesNotExistError e in return [])
133 $ do fmap (nub . map (make6mapped4 . addrAddress)) $
134 getAddrInfo (Just $ defaultHints { addrFlags = [ AI_CANONNAME, AI_V4MAPPED ]})
135 (Just $ Text.unpack $ strip_brackets addrtext)
136 (Just "5269")
137 did <- atomically $ tryPutTMVar r xs
138 when did $ do
139 interruptDelay mvar
140 utc <- getCurrentTime
141 atomically $ dnsObserve dns True utc $ map (addrtext,) xs
142 return ()
143
144strip_brackets :: Text -> Text
145strip_brackets s =
146 case Text.uncons s of
147 Just ('[',t) -> Text.takeWhile (/=']') t
148 _ -> s
149
150
151reportTimeout :: forall a. Show a => a -> IO ()
152reportTimeout addrtext = do
153 dput XMisc $ "timeout resolving: "++show addrtext
154 -- killThread rt
155
156unmap6mapped4 :: SockAddr -> SockAddr
157unmap6mapped4 addr@(SockAddrInet6 port _ (0,0,0xFFFF,a) _) =
158 SockAddrInet port (toBE32 a)
159unmap6mapped4 addr = addr
160
161rawReverseResolve ::
162 DNSCache -> (SockAddr -> IO ()) -> Int -> SockAddr -> IO [Text]
163rawReverseResolve dns onFail timeout addr = do
164 r <- atomically newEmptyTMVar
165 mvar <- interruptibleDelay
166 rt <- forkOS $ resolver r mvar
167 startDelay mvar timeout
168 did <- atomically $ tryPutTMVar r []
169 when did (onFail addr)
170 atomically $ readTMVar r
171 where
172 resolver r mvar =
173 handleIO_ (return ()) $ do
174 ent <- getHostByAddr (unmap6mapped4 addr) -- AF_UNSPEC addr
175 let names = BSD.hostName ent : BSD.hostAliases ent
176 xs = map Text.pack $ nub names
177 forkIO $ do
178 utc <- getCurrentTime
179 atomically $ dnsObserve dns False utc $ map (,addr) xs
180 atomically $ putTMVar r xs
181
182-- Returns expired (older than a minute) cached reverse-dns results
183-- and removes them from the cache.
184expiredReverse :: DNSCache -> SockAddr -> IO [Text]
185expiredReverse dns addr = do
186 utc <- getCurrentTime
187 addr <- return $ addr `withPort` 0
188 es <- atomically $ do
189 r <- readTVar $ rcache dns
190 let ns = maybe [] id $ Map.lookup addr r
191 minute = 60 -- seconds
192 -- XXX: Is this right? flip diffUTCTime utc returns the age of the
193 -- cache entry?
194 (es0,ns') = partition ( (>=minute) . flip diffUTCTime utc . fst ) ns
195 es = map snd es0
196 modifyTVar' (rcache dns) $ Map.insert addr ns'
197 f <- readTVar $ fcache dns
198 let f' = foldl' (flip $ Map.alter (expire utc)) f es
199 expire utc Nothing = Nothing
200 expire utc (Just as) = if null as' then Nothing else Just as'
201 where as' = filter ( (<minute) . flip diffUTCTime utc . fst) as
202 writeTVar (fcache dns) f'
203 return es
204 return es
205
206cachedReverse :: DNSCache -> SockAddr -> IO [Text]
207cachedReverse dns addr = do
208 utc <- getCurrentTime
209 addr <- return $ addr `withPort` 0
210 atomically $ do
211 r <- readTVar (rcache dns)
212 let ns = maybe [] id $ Map.lookup addr r
213 {-
214 ns' = filter ( (<minute) . flip diffUTCTime utc . fst) ns
215 minute = 60 -- seconds
216 modifyTVar' (rcache dns) $ Map.insert addr ns'
217 return $ map snd ns'
218 -}
219 return $ map snd ns
220
221-- Returns any dns query results for the given name that were observed less
222-- than a minute ago and updates the forward-cache to remove any results older
223-- than that.
224cachedForward :: DNSCache -> Text -> IO [SockAddr]
225cachedForward dns n = do
226 utc <- getCurrentTime
227 atomically $ do
228 f <- readTVar (fcache dns)
229 let as = maybe [] id $ Map.lookup n f
230 as' = filter ( (<minute) . flip diffUTCTime utc . fst) as
231 minute = 60 -- seconds
232 modifyTVar' (fcache dns) $ Map.insert n as'
233 return $ map snd as'
234
235-- Reverse-resolves an address to a domain name. Returns both the result of a
236-- new query and any freshly cached results. Cache entries older than a minute
237-- will not be returned, but will be refreshed in spawned threads so that they
238-- may be available for the next call.
239reverseResolve :: DNSCache -> SockAddr -> IO [Text]
240reverseResolve dns addr = do
241 expired <- expiredReverse dns addr
242 forM_ expired $ \n -> forkIO $ do
243 rawForwardResolve dns (const $ return ()) 1000000 n
244 return ()
245 xs <- rawReverseResolve dns (const $ return ()) 1000000 addr
246 cs <- cachedReverse dns addr
247 return $ xs ++ filter (not . flip elem xs) cs
248
249-- Resolves a name, if there's no result within one second, then any cached
250-- results that are less than a minute old are returned.
251forwardResolve :: DNSCache -> Text -> IO [SockAddr]
252forwardResolve dns n = do
253 as <- rawForwardResolve dns (const $ return ()) 1000000 n
254 if null as
255 then cachedForward dns n
256 else return as
257
258parseAddress :: Text -> IO (Maybe SockAddr)
259parseAddress addr_str = do
260 info <- getAddrInfo (Just $ defaultHints { addrFlags = [ AI_NUMERICHOST ] })
261 (Just . Text.unpack $ addr_str)
262 (Just "0")
263 return . listToMaybe $ map addrAddress info
264
265
266splitAtPort :: String -> (String,String)
267splitAtPort s = second sanitizePort $ case s of
268 ('[':t) -> break (==']') t
269 _ -> break (==':') s
270 where
271 sanitizePort (']':':':p) = p
272 sanitizePort (':':p) = p
273 sanitizePort _ = "0"
274
275unsafeParseAddress :: String -> Maybe SockAddr
276unsafeParseAddress addr_str = unsafePerformIO $ do
277 let (ipstr,portstr) = splitAtPort addr_str
278 info <- getAddrInfo (Just $ defaultHints { addrFlags = [ AI_NUMERICHOST ] })
279 (Just ipstr)
280 (Just portstr)
281 return . listToMaybe $ map addrAddress info
282
283withPort :: SockAddr -> Int -> SockAddr
284withPort (SockAddrInet _ a) port = SockAddrInet (toEnum port) a
285withPort (SockAddrInet6 _ a b c) port = SockAddrInet6 (toEnum port) a b c