Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / UDSServer.hs @ e181c8cd

History | View | Annotate | Download (16.1 kB)

1
{-# LANGUAGE TemplateHaskell #-}
2
{-# LANGUAGE FlexibleContexts #-}
3

    
4
{-| Implementation of the Ganeti Unix Domain Socket JSON server interface.
5

    
6
-}
7

    
8
{-
9

    
10
Copyright (C) 2013 Google Inc.
11

    
12
This program is free software; you can redistribute it and/or modify
13
it under the terms of the GNU General Public License as published by
14
the Free Software Foundation; either version 2 of the License, or
15
(at your option) any later version.
16

    
17
This program is distributed in the hope that it will be useful, but
18
WITHOUT ANY WARRANTY; without even the implied warranty of
19
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20
General Public License for more details.
21

    
22
You should have received a copy of the GNU General Public License
23
along with this program; if not, write to the Free Software
24
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25
02110-1301, USA.
26

    
27
-}
28

    
29
module Ganeti.UDSServer
30
  ( ConnectConfig(..)
31
  , ServerConfig(..)
32
  , Client
33
  , Server
34
  , RecvResult(..)
35
  , MsgKeys(..)
36
  , strOfKey
37
  -- * Unix sockets
38
  , openClientSocket
39
  , closeClientSocket
40
  , openServerSocket
41
  , closeServerSocket
42
  , acceptSocket
43
  -- * Client and server
44
  , connectClient
45
  , connectServer
46
  , pipeClient
47
  , acceptClient
48
  , closeClient
49
  , clientToFd
50
  , closeServer
51
  , buildResponse
52
  , parseResponse
53
  , buildCall
54
  , parseCall
55
  , recvMsg
56
  , recvMsgExt
57
  , sendMsg
58
  -- * Client handler
59
  , Handler(..)
60
  , HandlerResult
61
  , listener
62
  ) where
63

    
64
import Control.Applicative
65
import Control.Concurrent.Lifted (fork)
66
import Control.Monad.Base
67
import Control.Monad.Trans.Control
68
import Control.Exception (catch)
69
import Control.Monad
70
import qualified Data.ByteString as B
71
import qualified Data.ByteString.Lazy as BL
72
import qualified Data.ByteString.UTF8 as UTF8
73
import qualified Data.ByteString.Lazy.UTF8 as UTF8L
74
import Data.IORef
75
import Data.List
76
import Data.Word (Word8)
77
import qualified Network.Socket as S
78
import System.Directory (removeFile)
79
import System.IO (hClose, hFlush, hWaitForInput, Handle, IOMode(..))
80
import System.IO.Error (isEOFError)
81
import System.Posix.Types (Fd)
82
import System.Posix.IO (createPipe, fdToHandle, handleToFd)
83
import System.Timeout
84
import Text.JSON (encodeStrict, decodeStrict)
85
import qualified Text.JSON as J
86
import Text.JSON.Types
87

    
88
import Ganeti.BasicTypes
89
import Ganeti.Errors (GanetiException(..), ErrorResult)
90
import Ganeti.JSON
91
import Ganeti.Logging
92
import Ganeti.Runtime (GanetiDaemon(..), MiscGroup(..), GanetiGroup(..))
93
import Ganeti.THH
94
import Ganeti.Utils
95
import Ganeti.Constants (privateParametersBlacklist)
96

    
97
-- * Utility functions
98

    
99
-- | Wrapper over System.Timeout.timeout that fails in the IO monad.
100
withTimeout :: Int -> String -> IO a -> IO a
101
withTimeout secs descr action = do
102
  result <- timeout (secs * 1000000) action
103
  case result of
104
    Nothing -> fail $ "Timeout in " ++ descr
105
    Just v -> return v
106

    
107

    
108
-- * Generic protocol functionality
109

    
110
-- | Result of receiving a message from the socket.
111
data RecvResult = RecvConnClosed    -- ^ Connection closed
112
                | RecvError String  -- ^ Any other error
113
                | RecvOk String     -- ^ Successfull receive
114
                  deriving (Show, Eq)
115

    
116

    
117
-- | The end-of-message separator.
118
eOM :: Word8
119
eOM = 3
120

    
121
-- | The end-of-message encoded as a ByteString.
122
bEOM :: B.ByteString
123
bEOM = B.singleton eOM
124

    
125
-- | Valid keys in the requests and responses.
126
data MsgKeys = Method
127
             | Args
128
             | Success
129
             | Result
130

    
131
-- | The serialisation of MsgKeys into strings in messages.
132
$(genStrOfKey ''MsgKeys "strOfKey")
133

    
134

    
135
-- Information required for creating a server connection.
136
data ServerConfig = ServerConfig
137
                    { connDaemon :: GanetiDaemon
138
                    , connConfig :: ConnectConfig
139
                    }
140

    
141
-- Information required for creating a client or server connection.
142
data ConnectConfig = ConnectConfig
143
                     { recvTmo :: Int
144
                     , sendTmo :: Int
145
                     }
146

    
147
-- | A client encapsulation. Note that it has separate read and write handle.
148
-- For sockets it is the same handle. It is required for bi-directional
149
-- inter-process pipes though.
150
data Client = Client { rsocket :: Handle          -- ^ The read part of
151
                                                  -- the client socket
152
                     , wsocket :: Handle          -- ^ The write part of
153
                                                  -- the client socket
154
                     , rbuf :: IORef B.ByteString -- ^ Already received buffer
155
                     , clientConfig :: ConnectConfig
156
                     }
157

    
158
-- | A server encapsulation.
159
data Server = Server { sSocket :: S.Socket        -- ^ The bound server socket
160
                     , sPath :: FilePath          -- ^ The scoket's path
161
                     , serverConfig :: ConnectConfig
162
                     }
163

    
164
-- * Unix sockets
165

    
166
-- | Creates a Unix socket and connects it to the specified @path@,
167
-- where @timeout@ specifies the connection timeout.
168
openClientSocket
169
  :: Int              -- ^ connection timeout
170
  -> FilePath         -- ^ socket path
171
  -> IO Handle
172
openClientSocket tmo path = do
173
  sock <- S.socket S.AF_UNIX S.Stream S.defaultProtocol
174
  withTimeout tmo "creating a connection" $
175
              S.connect sock (S.SockAddrUnix path)
176
  S.socketToHandle sock ReadWriteMode
177

    
178
closeClientSocket :: Handle -> IO ()
179
closeClientSocket = hClose
180

    
181
-- | Creates a Unix socket and binds it to the specified @path@.
182
openServerSocket :: FilePath -> IO S.Socket
183
openServerSocket path = do
184
  sock <- S.socket S.AF_UNIX S.Stream S.defaultProtocol
185
  S.bindSocket sock (S.SockAddrUnix path)
186
  return sock
187

    
188
closeServerSocket :: S.Socket -> FilePath -> IO ()
189
closeServerSocket sock path = do
190
  S.sClose sock
191
  removeFile path
192

    
193
acceptSocket :: S.Socket -> IO Handle
194
acceptSocket sock = do
195
  -- ignore client socket address
196
  (clientSock, _) <- S.accept sock
197
  S.socketToHandle clientSock ReadWriteMode
198

    
199
-- * Client and server
200

    
201
-- | Connects to the master daemon and returns a Client.
202
connectClient
203
  :: ConnectConfig    -- ^ configuration for the client
204
  -> Int              -- ^ connection timeout
205
  -> FilePath         -- ^ socket path
206
  -> IO Client
207
connectClient conf tmo path = do
208
  h <- openClientSocket tmo path
209
  rf <- newIORef B.empty
210
  return Client { rsocket=h, wsocket=h, rbuf=rf, clientConfig=conf }
211

    
212
-- | Creates and returns a server endpoint.
213
connectServer :: ServerConfig -> Bool -> FilePath -> IO Server
214
connectServer sconf setOwner path = do
215
  s <- openServerSocket path
216
  when setOwner . setOwnerAndGroupFromNames path (connDaemon sconf) $
217
    ExtraGroup DaemonsGroup
218
  S.listen s 5 -- 5 is the max backlog
219
  return Server { sSocket = s, sPath = path, serverConfig = connConfig sconf }
220

    
221
-- | Creates a new bi-directional client pipe. The two returned clients
222
-- talk to each other through the pipe.
223
pipeClient :: ConnectConfig -> IO (Client, Client)
224
pipeClient conf =
225
  let newClient r w = do
226
        rf <- newIORef B.empty
227
        rh <- fdToHandle r
228
        wh <- fdToHandle w
229
        return Client { rsocket = rh, wsocket = wh
230
                      , rbuf = rf, clientConfig = conf }
231
  in do
232
    (r1, w1) <- createPipe
233
    (r2, w2) <- createPipe
234
    (,) <$> newClient r1 w2 <*> newClient r2 w1
235

    
236
-- | Closes a server endpoint.
237
closeServer :: (MonadBase IO m) => Server -> m ()
238
closeServer server =
239
  liftBase $ closeServerSocket (sSocket server) (sPath server)
240

    
241
-- | Accepts a client
242
acceptClient :: Server -> IO Client
243
acceptClient s = do
244
  handle <- acceptSocket (sSocket s)
245
  new_buffer <- newIORef B.empty
246
  return Client { rsocket=handle
247
                , wsocket=handle
248
                , rbuf=new_buffer
249
                , clientConfig=serverConfig s
250
                }
251

    
252
-- | Closes the client socket.
253
closeClient :: Client -> IO ()
254
closeClient client = do
255
  closeClientSocket . wsocket $ client
256
  closeClientSocket . rsocket $ client
257

    
258
-- | Extracts the read (the first) and the write (the second) file descriptor
259
-- of a client. This closes the underlying 'Handle's, therefore the original
260
-- client is closed and unusable after the call.
261
--
262
-- The purpose of this function is to keep the communication channel open,
263
-- while replacing a 'Client' with some other means.
264
clientToFd :: Client -> IO (Fd, Fd)
265
clientToFd client | rh == wh  = join (,) <$> handleToFd rh
266
                  | otherwise = (,) <$> handleToFd rh <*> handleToFd wh
267
  where
268
    rh = rsocket client
269
    wh = wsocket client
270

    
271
-- | Sends a message over a transport.
272
sendMsg :: Client -> String -> IO ()
273
sendMsg s buf = withTimeout (sendTmo $ clientConfig s) "sending a message" $ do
274
  let encoded = UTF8L.fromString buf
275
      handle = wsocket s
276
  BL.hPut handle encoded
277
  B.hPut handle bEOM
278
  hFlush handle
279

    
280
-- | Given a current buffer and the handle, it will read from the
281
-- network until we get a full message, and it will return that
282
-- message and the leftover buffer contents.
283
recvUpdate :: ConnectConfig -> Handle -> B.ByteString
284
           -> IO (B.ByteString, B.ByteString)
285
recvUpdate conf handle obuf = do
286
  nbuf <- withTimeout (recvTmo conf) "reading a response" $ do
287
            _ <- hWaitForInput handle (-1)
288
            B.hGetNonBlocking handle 4096
289
  let (msg, remaining) = B.break (eOM ==) nbuf
290
      newbuf = B.append obuf msg
291
  if B.null remaining
292
    then recvUpdate conf handle newbuf
293
    else return (newbuf, B.tail remaining)
294

    
295
-- | Waits for a message over a transport.
296
recvMsg :: Client -> IO String
297
recvMsg s = do
298
  cbuf <- readIORef $ rbuf s
299
  let (imsg, ibuf) = B.break (eOM ==) cbuf
300
  (msg, nbuf) <-
301
    if B.null ibuf      -- if old buffer didn't contain a full message
302
                        -- then we read from network:
303
      then recvUpdate (clientConfig s) (rsocket s) cbuf
304
      else return (imsg, B.tail ibuf)   -- else we return data from our buffer
305
  writeIORef (rbuf s) nbuf
306
  return $ UTF8.toString msg
307

    
308
-- | Extended wrapper over recvMsg.
309
recvMsgExt :: Client -> IO RecvResult
310
recvMsgExt s =
311
  Control.Exception.catch (liftM RecvOk (recvMsg s)) $ \e ->
312
    return $ if isEOFError e
313
               then RecvConnClosed
314
               else RecvError (show e)
315

    
316

    
317
-- | Serialize a request to String.
318
buildCall :: (J.JSON mth, J.JSON args)
319
          => mth    -- ^ The method
320
          -> args   -- ^ The arguments
321
          -> String -- ^ The serialized form
322
buildCall mth args =
323
  let keyToObj :: (J.JSON a) => MsgKeys -> a -> (String, J.JSValue)
324
      keyToObj k v = (strOfKey k, J.showJSON v)
325
  in encodeStrict $ toJSObject [ keyToObj Method mth, keyToObj Args args ]
326

    
327
-- | Parse the required keys out of a call.
328
parseCall :: (J.JSON mth, J.JSON args) => String -> Result (mth, args)
329
parseCall s = do
330
  arr <- fromJResult "parsing top-level JSON message" $
331
           decodeStrict s :: Result (JSObject JSValue)
332
  let keyFromObj :: (J.JSON a) => MsgKeys -> Result a
333
      keyFromObj = fromObj (fromJSObject arr) . strOfKey
334
  (,) <$> keyFromObj Method <*> keyFromObj Args
335

    
336

    
337
-- | Serialize the response to String.
338
buildResponse :: Bool    -- ^ Success
339
              -> JSValue -- ^ The arguments
340
              -> String  -- ^ The serialized form
341
buildResponse success args =
342
  let ja = [ (strOfKey Success, JSBool success)
343
           , (strOfKey Result, args)]
344
      jo = toJSObject ja
345
  in encodeStrict jo
346

    
347
-- | Try to decode an error from the server response. This function
348
-- will always fail, since it's called only on the error path (when
349
-- status is False).
350
decodeError :: JSValue -> ErrorResult JSValue
351
decodeError val =
352
  case fromJVal val of
353
    Ok e -> Bad e
354
    Bad msg -> Bad $ GenericError msg
355

    
356
-- | Check that luxi responses contain the required keys and that the
357
-- call was successful.
358
parseResponse :: String -> ErrorResult JSValue
359
parseResponse s = do
360
  when (UTF8.replacement_char `elem` s) $
361
      failError "Failed to decode UTF-8,\
362
                \ detected replacement char after decoding"
363
  oarr <- fromJResultE "Parsing LUXI response" (decodeStrict s)
364
  let arr = J.fromJSObject oarr
365
  status <- fromObj arr (strOfKey Success)
366
  result <- fromObj arr (strOfKey Result)
367
  if status
368
    then return result
369
    else decodeError result
370

    
371
-- | Logs an outgoing message.
372
logMsg
373
    :: (Show e, J.JSON e, MonadLog m)
374
    => Handler i m o
375
    -> i                          -- ^ the received request (used for logging)
376
    -> GenericResult e J.JSValue  -- ^ A message to be sent
377
    -> m ()
378
logMsg handler req (Bad err) =
379
  logWarning $ "Failed to execute request " ++ hInputLogLong handler req ++ ": "
380
               ++ show err
381
logMsg handler req (Ok result) = do
382
  -- only log the first 2,000 chars of the result
383
  logDebug $ "Result (truncated): " ++ take 2000 (J.encode result)
384
  logInfo $ "Successfully handled " ++ hInputLogShort handler req
385

    
386
-- | Prepares an outgoing message.
387
prepareMsg
388
    :: (J.JSON e)
389
    => GenericResult e J.JSValue  -- ^ A message to be sent
390
    -> (Bool, J.JSValue)
391
prepareMsg (Bad err)   = (False, J.showJSON err)
392
prepareMsg (Ok result) = (True, result)
393

    
394

    
395
-- * Processing client requests
396

    
397
type HandlerResult m o = m (Bool, GenericResult GanetiException o)
398

    
399
data Handler i m o = Handler
400
  { hParse         :: J.JSValue -> J.JSValue -> Result i
401
    -- ^ parses method and its arguments into the input type
402
  , hInputLogShort :: i -> String
403
    -- ^ short description of an input, for the INFO logging level
404
  , hInputLogLong  :: i -> String
405
    -- ^ long description of an input, for the DEBUG logging level
406
  , hExec          :: i -> HandlerResult m o
407
    -- ^ executes the handler on an input
408
  }
409

    
410

    
411
handleJsonMessage
412
    :: (J.JSON o, Monad m)
413
    => Handler i m o            -- ^ handler
414
    -> i                        -- ^ parsed input
415
    -> HandlerResult m J.JSValue
416
handleJsonMessage handler req = do
417
  (close, call_result) <- hExec handler req
418
  return (close, fmap J.showJSON call_result)
419

    
420
-- | Takes a request as a 'String', parses it, passes it to a handler and
421
-- formats its response.
422
handleRawMessage
423
    :: (J.JSON o, MonadLog m)
424
    => Handler i m o            -- ^ handler
425
    -> String                   -- ^ raw unparsed input
426
    -> m (Bool, String)
427
handleRawMessage handler payload =
428
  case parseCall payload >>= uncurry (hParse handler) of
429
    Bad err -> do
430
         let errmsg = "Failed to parse request: " ++ err
431
         logWarning errmsg
432
         return (False, buildResponse False (J.showJSON errmsg))
433
    Ok req -> do
434
        logDebug $ "Request: " ++ hInputLogLong handler req
435
        (close, call_result_json) <- handleJsonMessage handler req
436
        logMsg handler req call_result_json
437
        let (status, response) = prepareMsg call_result_json
438
        return (close, buildResponse status response)
439

    
440
isRisky :: RecvResult -> Bool
441
isRisky msg = case msg of
442
  RecvOk payload -> any (`isInfixOf` payload) privateParametersBlacklist
443
  _ -> False
444

    
445
-- | Reads a request, passes it to a handler and sends a response back to the
446
-- client.
447
handleClient
448
    :: (J.JSON o, MonadBase IO m, MonadLog m)
449
    => Handler i m o
450
    -> Client
451
    -> m Bool
452
handleClient handler client = do
453
  msg <- liftBase $ recvMsgExt client
454

    
455
  debugMode <- liftBase isDebugMode
456
  when (debugMode && isRisky msg) $
457
    logAlert "POSSIBLE LEAKING OF CONFIDENTIAL PARAMETERS. \
458
             \Daemon is running in debug mode. \
459
             \The text of the request has been logged."
460
  logDebug $ "Received message (truncated): " ++ take 500 (show msg)
461

    
462
  case msg of
463
    RecvConnClosed -> logDebug "Connection closed" >>
464
                      return False
465
    RecvError err -> logWarning ("Error during message receiving: " ++ err) >>
466
                     return False
467
    RecvOk payload -> do
468
      (close, outMsg) <- handleRawMessage handler payload
469
      liftBase $ sendMsg client outMsg
470
      return close
471

    
472

    
473
-- | Main client loop: runs one loop of 'handleClient', and if that
474
-- doesn't report a finished (closed) connection, restarts itself.
475
clientLoop
476
    :: (J.JSON o, MonadBase IO m, MonadLog m)
477
    => Handler i m o
478
    -> Client
479
    -> m ()
480
clientLoop handler client = do
481
  result <- handleClient handler client
482
  if result
483
    then clientLoop handler client
484
    else liftBase $ closeClient client
485

    
486
-- | Main listener loop: accepts clients, forks an I/O thread to handle
487
-- that client.
488
listener
489
    :: (J.JSON o, MonadBaseControl IO m, MonadLog m)
490
    => Handler i m o
491
    -> Server
492
    -> m ()
493
listener handler server = do
494
  client <- liftBase $ acceptClient server
495
  _ <- fork $ clientLoop handler client
496
  return ()