-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPingServer.hs
182 lines (155 loc) · 6.16 KB
/
PingServer.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
--module PingServer where
import Control.Concurrent (forkIO, threadDelay)
import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
import Control.Monad (liftM, forever, when)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.State (StateT, execStateT, modify, gets, get, put)
import qualified Data.ByteString as SB
import qualified Data.ByteString.Lazy as LB
import Data.Foldable (toList)
import Data.Maybe (isNothing, fromJust)
import qualified Data.Map as Map
import Data.Time.Clock (UTCTime)
import Data.Word (Word32)
import qualified Data.Dequeue as DQ
import Network.BSD (getProtocolByName, protoNumber)
import Network.Socket (withSocketsDo, socket, inet_addr, Socket,
HostAddress, Family (AF_INET), SocketType (Raw),
SockAddr (SockAddrInet))
import Network.Socket.ByteString (sendTo, recvFrom)
import Icmp (IpPacket(..), IcmpEchoPacket(..),
dumpIcmpEchoRequest, parseIp, parseIcmpEchoReply)
import Timer
import SockOpt
type IP = Word32
data PingServer = PingServer {serverChan :: Chan Command}
data PacketId = PacketId { pktIp :: IP
, pktId :: Int
, pktSeq :: Int
} deriving (Show, Eq)
data ServerState = ServerState { srvIps :: DQ.BankersDequeue IP
, srvTReel :: TimerReel
, srvSentPackets :: Map.Map PacketId UTCTime
, srvPingInterval :: Maybe Double
, srvTimeoutInterval :: Maybe Double
, srvTimeoutAction :: Maybe (IO ())
, srvLinkTimeoutCancel :: Maybe (IO ())
}
data Command = AddHost String
| DelHost String
| SetPingInterval Double
| SetTimeoutInterval Double
| SetTimeoutAction (IO ())
| Exit
| StartPing
| PingReceived IpPacket IcmpEchoPacket
| PingTimedOut PacketId
| LinkTimeout
instance Ord PacketId where
(PacketId a1 b1 c1) <= (PacketId a2 b2 c2) =
(a1 <= a2) || (b1 <= b2) || (c1 <= c2)
main = withSocketsDo $ do
addr <- inet_addr "192.168.3.2"
ps <- newPingServer "br0"
addHost ps "192.168.3.2"
setTimeoutInterval ps 7
setPingInterval ps 3
forever $ threadDelay 1
newPingServer :: String -> IO PingServer
newPingServer interface = do
chan <- newChan
s <- openSocket interface
forkIO $ runServer s chan
return $ PingServer chan
where
openSocket interface = do
proto <- getProtocolByName "icmp"
s <- socket AF_INET Raw (protoNumber proto)
setSocketBindToDevice s interface
return s
addHost :: PingServer -> String -> IO ()
addHost ps = sendC ps . AddHost
delHost :: PingServer -> String -> IO ()
delHost ps = sendC ps . DelHost
setTimeoutInterval :: PingServer -> Double -> IO ()
setTimeoutInterval ps = sendC ps . SetTimeoutInterval
setTimeoutAction :: PingServer -> IO () -> IO ()
setTimeoutAction ps = sendC ps . SetTimeoutAction
setTimeout :: PingServer -> Double -> IO ()
setTimeout ps = sendC ps . SetTimeoutInterval
setPingInterval :: PingServer -> Double -> IO ()
setPingInterval ps = sendC ps . SetPingInterval
sendC :: PingServer -> Command -> IO ()
sendC ps = writeChan (serverChan ps)
runServer :: Socket -> Chan Command -> IO ()
runServer sock chan = do
forkIO $ socketListener sock chan
tReel <- newTimerReel
let state = ServerState { srvIps = DQ.empty
, srvTReel = tReel
, srvSentPackets = Map.empty
, srvPingInterval = Nothing
, srvTimeoutInterval = Nothing
, srvTimeoutAction = Nothing
, srvLinkTimeoutCancel = Nothing
}
execStateT loop state
return ()
where
loop :: StateT ServerState IO ()
loop = do
cmd <- liftIO $ readChan chan
s <- get
case cmd of
AddHost h -> do
addr <- liftIO $ inet_addr h
put $ s {srvIps = DQ.pushFront (srvIps s) addr}
loop
DelHost h -> do
addr <- liftIO $ inet_addr h
put $ s {srvIps = filterAddr addr $ srvIps s}
loop
SetPingInterval dt -> do
when (isNothing $ srvPingInterval s) $
liftIO $ writeChan chan StartPing
put $ s {srvPingInterval = Just dt}
loop
SetTimeoutInterval dt -> do
put $ s {srvTimeoutInterval = Just dt}
when (isNothing $ srvLinkTimeoutCancel s) $ do
cancel <- addTm dt (send LinkTimeout)
modify (\s -> s {srvLinkTimeoutCancel = Just cancel})
loop
StartPing -> do
liftIO $ print "Start ping"
addTm (fromJust $ srvPingInterval s) (send StartPing)
let echo = dumpIcmpEchoRequest $ IcmpEchoPacket 10 10 SB.empty
let ip = (fromJust $ DQ.first $ srvIps s)
liftIO $ sendTo sock (toStrictBS echo) (SockAddrInet 0 ip)
loop
PingReceived ip echo -> do
liftIO $ print $ "Got ping from " ++ show(ip) ++ " " ++ show(echo)
loop
LinkTimeout -> do
liftIO $ print "Link timeout"
loop
Exit -> return ()
--StartPing -> startPing
filterAddr addr addrs = DQ.fromList $ filter (/= addr) (toList addrs)
send = writeChan chan
addTm dt act = do
r <- gets srvTReel
liftIO $ addTimer r dt act
toStrictBS = SB.concat . LB.toChunks
socketListener :: Socket -> Chan Command -> IO ()
socketListener s chan = do
forever $ do
(p, saddr) <- recvFrom s 65536
case parseEcho p of
Left errMsg -> print errMsg
Right (ip, echo) -> writeChan chan $ PingReceived ip echo
where
parseEcho p = do
ip <- parseIp p
echo <- parseIcmpEchoReply $ ipPayload ip
return (ip, echo)