summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Presence/DNSCache.hs198
1 files changed, 198 insertions, 0 deletions
diff --git a/Presence/DNSCache.hs b/Presence/DNSCache.hs
new file mode 100644
index 00000000..4447589c
--- /dev/null
+++ b/Presence/DNSCache.hs
@@ -0,0 +1,198 @@
1{-# LANGUAGE TupleSections #-}
2module DNSCache
3 ( DNSCache
4 , reverseResolve
5 , forwardResolve
6 , newDNSCache
7 ) where
8
9import Control.Concurrent
10import Control.Concurrent.STM
11import Data.Text ( Text )
12import Network.Socket ( SockAddr(..), AddrInfoFlag(..), defaultHints, getAddrInfo, AddrInfo(..) )
13import Data.Time.Clock ( UTCTime, getCurrentTime, diffUTCTime )
14import System.IO.Error ( isDoesNotExistError )
15import System.Endian ( fromBE32, toBE32 )
16import Control.Exception ( handle, ErrorCall(..) )
17import Data.Map ( Map )
18import qualified Data.Map as Map
19import qualified Network.BSD as BSD
20import qualified Data.Text as Text
21import Control.Monad
22import Data.List
23import Data.Ord
24
25import SockAddr ()
26import ControlMaybe ( handleIO_ )
27import GetHostByAddr ( getHostByAddr )
28
29type TimeStamp = UTCTime
30
31data DNSCache =
32 DNSCache
33 { fcache :: TVar (Map Text [(TimeStamp, SockAddr)])
34 , rcache :: TVar (Map SockAddr [(TimeStamp, Text)])
35 }
36
37
38newDNSCache :: IO DNSCache
39newDNSCache = do
40 atomically $ do
41 fcache <- newTVar Map.empty
42 rcache <- newTVar Map.empty
43 return DNSCache { fcache=fcache, rcache=rcache }
44
45equivBy f a b = f a == f b
46
47updateCache :: Eq x =>
48 Bool -> TimeStamp -> [x] -> Maybe [(TimeStamp,x)] -> Maybe [(TimeStamp,x)]
49updateCache withScrub utc xs mys = do
50 let ys = maybe [] id mys
51 ys' = filter scrub ys
52 ys'' = map (utc,) xs ++ ys'
53 minute = 60
54 scrub (t,x) | withScrub && diffUTCTime utc t < minute = False
55 scrub (t,x) | x `elem` xs = False
56 scrub _ = True
57 guard $ not (null ys'')
58 return ys''
59
60dnsObserve :: DNSCache -> Bool -> TimeStamp -> [(Text,SockAddr)] -> STM ()
61dnsObserve dns withScrub utc obs = do
62 f <- readTVar $ fcache dns
63 r <- readTVar $ rcache dns
64 let gs = do
65 g <- groupBy (equivBy fst) $ sortBy (comparing fst) obs
66 (n,_) <- take 1 g
67 return (n,map snd g)
68 f' = foldl' updatef f gs
69 hs = do
70 h <- groupBy (equivBy snd) $ sortBy (comparing snd) obs
71 (_,a) <- take 1 h
72 return (a,map fst h)
73 r' = foldl' updater r hs
74 writeTVar (fcache dns) f'
75 writeTVar (rcache dns) r'
76 where
77 updatef f (n,addrs) = Map.alter (updateCache withScrub utc addrs) n f
78 updater r (a,ns) = Map.alter (updateCache withScrub utc ns) a r
79
80make6mapped4 addr@(SockAddrInet6 {}) = addr
81make6mapped4 addr@(SockAddrInet port a) = SockAddrInet6 port 0 (0,0,0xFFFF,fromBE32 a) 0
82
83rawForwardResolve dns fail timeout addrtext = do
84 r <- atomically newEmptyTMVar
85 mvar <- atomically newEmptyTMVar
86 rt <- forkOS $ resolver r mvar
87 tt <- forkIO $ timer (fail addrtext) timeout r rt
88 atomically $ putTMVar mvar tt
89 atomically $ readTMVar r
90 where
91 resolver r mvar = do
92 xs <- handle (\e -> let _ = isDoesNotExistError e in return [])
93 $ do fmap (nub . map (make6mapped4 . addrAddress)) $
94 getAddrInfo (Just $ defaultHints { addrFlags = [ AI_CANONNAME, AI_V4MAPPED ]})
95 (Just $ Text.unpack $ strip_brackets addrtext)
96 (Just "5269")
97 did <- atomically $ tryPutTMVar r xs
98 when did $ do
99 tt <- atomically $ readTMVar mvar
100 throwTo tt (ErrorCall "Interrupted delay")
101 utc <- getCurrentTime
102 atomically $ dnsObserve dns True utc $ map (addrtext,) xs
103 return ()
104 strip_brackets s =
105 case Text.uncons s of
106 Just ('[',t) -> Text.takeWhile (/=']') t
107 _ -> s
108
109
110reportTimeout addrtext = do
111 putStrLn $ "timeout resolving: "++show addrtext
112 -- killThread rt
113
114timer fail timeout r rt = do
115 handle (\(ErrorCall _)-> return ()) $ do
116 threadDelay timeout
117 did <- atomically $ tryPutTMVar r []
118 when did fail
119
120unmap6mapped4 addr@(SockAddrInet6 port _ (0,0,0xFFFF,a) _) =
121 SockAddrInet port (toBE32 a)
122unmap6mapped4 addr = addr
123
124rawReverseResolve dns fail timeout addr = do
125 r <- atomically newEmptyTMVar
126 mvar <- atomically newEmptyTMVar
127 rt <- forkOS $ resolver r mvar
128 tt <- forkIO $ timer (fail addr) timeout r rt
129 atomically $ putTMVar mvar tt
130 atomically $ readTMVar r
131 where
132 resolver r mvar =
133 handleIO_ (return ()) $ do
134 ent <- getHostByAddr (unmap6mapped4 addr) -- AF_UNSPEC addr
135 let names = BSD.hostName ent : BSD.hostAliases ent
136 xs = map Text.pack $ nub names
137 forkIO $ do
138 utc <- getCurrentTime
139 atomically $ dnsObserve dns False utc $ map (,addr) xs
140 atomically $ putTMVar r xs
141
142expiredReverse dns addr = do
143 utc <- getCurrentTime
144 es <- atomically $ do
145 r <- readTVar $ rcache dns
146 let ns = maybe [] id $ Map.lookup addr r
147 minute = 60 -- seconds
148 (es0,ns') = partition ( (>=minute) . flip diffUTCTime utc . fst ) ns
149 es = map snd es0
150 modifyTVar' (rcache dns) $ Map.insert addr ns'
151 f <- readTVar $ fcache dns
152 let f' = foldl' (flip $ Map.alter (expire utc)) f es
153 expire utc Nothing = Nothing
154 expire utc (Just as) = if null as' then Nothing else Just as'
155 where as' = filter ( (<minute) . flip diffUTCTime utc . fst) as
156 writeTVar (fcache dns) f'
157 return es
158 return es
159
160cachedReverse dns addr = do
161 utc <- getCurrentTime
162 atomically $ do
163 r <- readTVar (rcache dns)
164 let ns = maybe [] id $ Map.lookup addr r
165 {-
166 ns' = filter ( (<minute) . flip diffUTCTime utc . fst) ns
167 minute = 60 -- seconds
168 modifyTVar' (rcache dns) $ Map.insert addr ns'
169 return $ map snd ns'
170 -}
171 return $ map snd ns
172
173cachedForward dns n = do
174 utc <- getCurrentTime
175 atomically $ do
176 f <- readTVar (fcache dns)
177 let as = maybe [] id $ Map.lookup n f
178 as' = filter ( (<minute) . flip diffUTCTime utc . fst) as
179 minute = 60 -- seconds
180 modifyTVar' (fcache dns) $ Map.insert n as'
181 return $ map snd as'
182
183reverseResolve dns addr = do
184 expired <- expiredReverse dns addr
185 forM_ expired $ \n -> forkIO $ do
186 rawForwardResolve dns (const $ return ()) 1000000 n
187 return ()
188 xs <- rawReverseResolve dns (const $ return ()) 1000000 addr
189 cs <- cachedReverse dns addr
190 return $ xs ++ filter (not . flip elem xs) cs
191
192forwardResolve dns n = do
193 as <- rawForwardResolve dns (const $ return ()) 1000000 n
194 if null as
195 then cachedForward dns n
196 else return as
197
198