diff options
Diffstat (limited to 'Presence')
-rw-r--r-- | Presence/DNSCache.hs | 198 |
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 #-} | ||
2 | module DNSCache | ||
3 | ( DNSCache | ||
4 | , reverseResolve | ||
5 | , forwardResolve | ||
6 | , newDNSCache | ||
7 | ) where | ||
8 | |||
9 | import Control.Concurrent | ||
10 | import Control.Concurrent.STM | ||
11 | import Data.Text ( Text ) | ||
12 | import Network.Socket ( SockAddr(..), AddrInfoFlag(..), defaultHints, getAddrInfo, AddrInfo(..) ) | ||
13 | import Data.Time.Clock ( UTCTime, getCurrentTime, diffUTCTime ) | ||
14 | import System.IO.Error ( isDoesNotExistError ) | ||
15 | import System.Endian ( fromBE32, toBE32 ) | ||
16 | import Control.Exception ( handle, ErrorCall(..) ) | ||
17 | import Data.Map ( Map ) | ||
18 | import qualified Data.Map as Map | ||
19 | import qualified Network.BSD as BSD | ||
20 | import qualified Data.Text as Text | ||
21 | import Control.Monad | ||
22 | import Data.List | ||
23 | import Data.Ord | ||
24 | |||
25 | import SockAddr () | ||
26 | import ControlMaybe ( handleIO_ ) | ||
27 | import GetHostByAddr ( getHostByAddr ) | ||
28 | |||
29 | type TimeStamp = UTCTime | ||
30 | |||
31 | data DNSCache = | ||
32 | DNSCache | ||
33 | { fcache :: TVar (Map Text [(TimeStamp, SockAddr)]) | ||
34 | , rcache :: TVar (Map SockAddr [(TimeStamp, Text)]) | ||
35 | } | ||
36 | |||
37 | |||
38 | newDNSCache :: IO DNSCache | ||
39 | newDNSCache = do | ||
40 | atomically $ do | ||
41 | fcache <- newTVar Map.empty | ||
42 | rcache <- newTVar Map.empty | ||
43 | return DNSCache { fcache=fcache, rcache=rcache } | ||
44 | |||
45 | equivBy f a b = f a == f b | ||
46 | |||
47 | updateCache :: Eq x => | ||
48 | Bool -> TimeStamp -> [x] -> Maybe [(TimeStamp,x)] -> Maybe [(TimeStamp,x)] | ||
49 | updateCache 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 | |||
60 | dnsObserve :: DNSCache -> Bool -> TimeStamp -> [(Text,SockAddr)] -> STM () | ||
61 | dnsObserve 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 | |||
80 | make6mapped4 addr@(SockAddrInet6 {}) = addr | ||
81 | make6mapped4 addr@(SockAddrInet port a) = SockAddrInet6 port 0 (0,0,0xFFFF,fromBE32 a) 0 | ||
82 | |||
83 | rawForwardResolve 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 | |||
110 | reportTimeout addrtext = do | ||
111 | putStrLn $ "timeout resolving: "++show addrtext | ||
112 | -- killThread rt | ||
113 | |||
114 | timer fail timeout r rt = do | ||
115 | handle (\(ErrorCall _)-> return ()) $ do | ||
116 | threadDelay timeout | ||
117 | did <- atomically $ tryPutTMVar r [] | ||
118 | when did fail | ||
119 | |||
120 | unmap6mapped4 addr@(SockAddrInet6 port _ (0,0,0xFFFF,a) _) = | ||
121 | SockAddrInet port (toBE32 a) | ||
122 | unmap6mapped4 addr = addr | ||
123 | |||
124 | rawReverseResolve 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 | |||
142 | expiredReverse 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 | |||
160 | cachedReverse 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 | |||
173 | cachedForward 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 | |||
183 | reverseResolve 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 | |||
192 | forwardResolve 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 | |||