2016-12-01 04:41:01 +00:00
|
|
|
{- P2P protocol, IO implementation
|
2016-11-20 16:08:16 +00:00
|
|
|
-
|
|
|
|
- Copyright 2016 Joey Hess <id@joeyh.name>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
2016-12-07 17:37:35 +00:00
|
|
|
{-# LANGUAGE RankNTypes, FlexibleContexts, CPP #-}
|
2016-11-20 16:08:16 +00:00
|
|
|
|
2016-11-22 18:34:49 +00:00
|
|
|
module P2P.IO
|
2016-12-02 17:45:45 +00:00
|
|
|
( RunProto
|
2016-12-06 19:40:31 +00:00
|
|
|
, P2PConnection(..)
|
|
|
|
, connectPeer
|
2016-12-06 19:49:39 +00:00
|
|
|
, closeConnection
|
2016-12-24 16:12:58 +00:00
|
|
|
, serveUnixSocket
|
2016-12-06 19:40:31 +00:00
|
|
|
, setupHandle
|
2016-12-01 04:41:01 +00:00
|
|
|
, runNetProto
|
|
|
|
, runNet
|
2016-11-20 16:08:16 +00:00
|
|
|
) where
|
|
|
|
|
2016-12-24 16:12:58 +00:00
|
|
|
import Common
|
2016-11-24 20:36:16 +00:00
|
|
|
import P2P.Protocol
|
2016-12-06 19:40:31 +00:00
|
|
|
import P2P.Address
|
2016-11-20 16:08:16 +00:00
|
|
|
import Git
|
|
|
|
import Git.Command
|
2016-11-30 20:38:16 +00:00
|
|
|
import Utility.AuthToken
|
2016-11-20 16:08:16 +00:00
|
|
|
import Utility.SimpleProtocol
|
2016-12-07 17:37:35 +00:00
|
|
|
import Utility.Metered
|
2016-12-06 19:40:31 +00:00
|
|
|
import Utility.Tor
|
2016-12-24 16:12:58 +00:00
|
|
|
import Utility.FileMode
|
2016-11-20 16:08:16 +00:00
|
|
|
|
|
|
|
import Control.Monad.Free
|
|
|
|
import Control.Monad.IO.Class
|
|
|
|
import System.Exit (ExitCode(..))
|
2016-12-06 19:40:31 +00:00
|
|
|
import Network.Socket
|
2016-11-20 16:08:16 +00:00
|
|
|
import Control.Concurrent
|
2016-11-21 23:24:55 +00:00
|
|
|
import Control.Concurrent.Async
|
2016-11-20 16:08:16 +00:00
|
|
|
import qualified Data.ByteString as B
|
|
|
|
import qualified Data.ByteString.Lazy as L
|
2016-12-09 20:45:36 +00:00
|
|
|
import System.Log.Logger (debugM)
|
2016-12-24 16:12:58 +00:00
|
|
|
import qualified Network.Socket as S
|
2016-11-20 16:08:16 +00:00
|
|
|
|
2016-12-01 03:54:00 +00:00
|
|
|
-- Type of interpreters of the Proto free monad.
|
2016-12-08 19:47:49 +00:00
|
|
|
type RunProto m = forall a. (MonadIO m, MonadMask m) => Proto a -> m (Either String a)
|
2016-11-20 16:08:16 +00:00
|
|
|
|
2016-12-06 19:40:31 +00:00
|
|
|
data P2PConnection = P2PConnection
|
|
|
|
{ connRepo :: Repo
|
|
|
|
, connCheckAuth :: (AuthToken -> Bool)
|
|
|
|
, connIhdl :: Handle
|
|
|
|
, connOhdl :: Handle
|
2016-11-20 16:08:16 +00:00
|
|
|
}
|
|
|
|
|
2016-12-06 19:40:31 +00:00
|
|
|
-- Opens a connection to a peer. Does not authenticate with it.
|
|
|
|
connectPeer :: Git.Repo -> P2PAddress -> IO P2PConnection
|
|
|
|
connectPeer g (TorAnnex onionaddress onionport) = do
|
|
|
|
h <- setupHandle =<< connectHiddenService onionaddress onionport
|
|
|
|
return $ P2PConnection
|
|
|
|
{ connRepo = g
|
|
|
|
, connCheckAuth = const False
|
|
|
|
, connIhdl = h
|
|
|
|
, connOhdl = h
|
|
|
|
}
|
|
|
|
|
2016-12-06 19:49:39 +00:00
|
|
|
closeConnection :: P2PConnection -> IO ()
|
|
|
|
closeConnection conn = do
|
|
|
|
hClose (connIhdl conn)
|
|
|
|
hClose (connOhdl conn)
|
|
|
|
|
2016-12-24 16:12:58 +00:00
|
|
|
-- Serves the protocol on a unix socket.
|
|
|
|
--
|
|
|
|
-- The callback is run to serve a connection, and is responsible for
|
|
|
|
-- closing the Handle when done.
|
|
|
|
--
|
|
|
|
-- Note that while the callback is running, other connections won't be
|
2016-12-24 16:49:28 +00:00
|
|
|
-- processed, so longterm work should be run in a separate thread by
|
2016-12-24 16:12:58 +00:00
|
|
|
-- the callback.
|
|
|
|
serveUnixSocket :: FilePath -> (Handle -> IO ()) -> IO ()
|
|
|
|
serveUnixSocket unixsocket serveconn = do
|
|
|
|
nukeFile unixsocket
|
|
|
|
soc <- S.socket S.AF_UNIX S.Stream S.defaultProtocol
|
|
|
|
S.bind soc (S.SockAddrUnix unixsocket)
|
|
|
|
-- Allow everyone to read and write to the socket,
|
|
|
|
-- so a daemon like tor, that is probably running as a different
|
|
|
|
-- de sock $ addModes
|
|
|
|
-- user, can access it.
|
|
|
|
--
|
|
|
|
-- Connections have to authenticate to do anything,
|
|
|
|
-- so it's fine that other local users can connect to the
|
|
|
|
-- socket.
|
|
|
|
modifyFileMode unixsocket $ addModes
|
|
|
|
[groupReadMode, groupWriteMode, otherReadMode, otherWriteMode]
|
|
|
|
S.listen soc 2
|
|
|
|
forever $ do
|
|
|
|
(conn, _) <- S.accept soc
|
|
|
|
setupHandle conn >>= serveconn
|
|
|
|
|
2016-12-06 19:40:31 +00:00
|
|
|
setupHandle :: Socket -> IO Handle
|
|
|
|
setupHandle s = do
|
|
|
|
h <- socketToHandle s ReadWriteMode
|
|
|
|
hSetBuffering h LineBuffering
|
|
|
|
hSetBinaryMode h False
|
|
|
|
return h
|
|
|
|
|
2016-12-01 04:41:01 +00:00
|
|
|
-- Purposefully incomplete interpreter of Proto.
|
2016-12-01 03:54:00 +00:00
|
|
|
--
|
2016-12-01 04:41:01 +00:00
|
|
|
-- This only runs Net actions. No Local actions will be run
|
|
|
|
-- (those need the Annex monad) -- if the interpreter reaches any,
|
2016-12-01 03:54:00 +00:00
|
|
|
-- it returns Nothing.
|
2016-12-08 19:47:49 +00:00
|
|
|
runNetProto :: P2PConnection -> Proto a -> IO (Either String a)
|
2016-12-06 19:40:31 +00:00
|
|
|
runNetProto conn = go
|
2016-11-20 16:08:16 +00:00
|
|
|
where
|
2016-12-01 04:41:01 +00:00
|
|
|
go :: RunProto IO
|
2016-12-10 15:12:18 +00:00
|
|
|
go (Pure v) = return (Right v)
|
2016-12-06 19:40:31 +00:00
|
|
|
go (Free (Net n)) = runNet conn go n
|
2016-12-08 19:47:49 +00:00
|
|
|
go (Free (Local _)) = return (Left "unexpected annex operation attempted")
|
2016-11-20 16:08:16 +00:00
|
|
|
|
2016-12-02 17:45:45 +00:00
|
|
|
-- Interpreter of the Net part of Proto.
|
2016-12-01 04:41:01 +00:00
|
|
|
--
|
|
|
|
-- An interpreter of Proto has to be provided, to handle the rest of Proto
|
|
|
|
-- actions.
|
2016-12-08 19:47:49 +00:00
|
|
|
runNet :: (MonadIO m, MonadMask m) => P2PConnection -> RunProto m -> NetF (Proto a) -> m (Either String a)
|
2016-12-06 19:40:31 +00:00
|
|
|
runNet conn runner f = case f of
|
2016-11-20 16:08:16 +00:00
|
|
|
SendMessage m next -> do
|
2016-12-02 18:16:50 +00:00
|
|
|
v <- liftIO $ tryNonAsync $ do
|
2016-12-09 20:45:36 +00:00
|
|
|
let l = unwords (formatMessage m)
|
2016-12-09 20:55:48 +00:00
|
|
|
debugMessage "P2P >" m
|
2016-12-09 20:45:36 +00:00
|
|
|
hPutStrLn (connOhdl conn) l
|
2016-12-06 19:40:31 +00:00
|
|
|
hFlush (connOhdl conn)
|
2016-11-22 01:22:58 +00:00
|
|
|
case v of
|
2016-12-08 19:47:49 +00:00
|
|
|
Left e -> return (Left (show e))
|
2016-11-22 01:22:58 +00:00
|
|
|
Right () -> runner next
|
2016-11-20 16:08:16 +00:00
|
|
|
ReceiveMessage next -> do
|
2016-12-09 17:34:00 +00:00
|
|
|
v <- liftIO $ tryNonAsync $ getProtocolLine (connIhdl conn)
|
2016-11-22 01:22:58 +00:00
|
|
|
case v of
|
2016-12-08 19:47:49 +00:00
|
|
|
Left e -> return (Left (show e))
|
2016-12-09 17:34:00 +00:00
|
|
|
Right Nothing -> return (Left "protocol error")
|
2016-12-09 20:55:48 +00:00
|
|
|
Right (Just l) -> case parseMessage l of
|
|
|
|
Just m -> do
|
|
|
|
liftIO $ debugMessage "P2P <" m
|
|
|
|
runner (next m)
|
2016-12-09 20:45:36 +00:00
|
|
|
Nothing -> runner $ do
|
|
|
|
let e = ERROR $ "protocol parse error: " ++ show l
|
|
|
|
net $ sendMessage e
|
|
|
|
next e
|
2016-12-07 17:37:35 +00:00
|
|
|
SendBytes len b p next -> do
|
2016-12-02 18:16:50 +00:00
|
|
|
v <- liftIO $ tryNonAsync $ do
|
2016-12-07 17:37:35 +00:00
|
|
|
ok <- sendExactly len b (connOhdl conn) p
|
2016-12-06 19:40:31 +00:00
|
|
|
hFlush (connOhdl conn)
|
2016-12-02 17:45:45 +00:00
|
|
|
return ok
|
2016-11-22 01:22:58 +00:00
|
|
|
case v of
|
2016-12-02 17:45:45 +00:00
|
|
|
Right True -> runner next
|
2016-12-08 19:47:49 +00:00
|
|
|
Right False -> return (Left "short data write")
|
|
|
|
Left e -> return (Left (show e))
|
2016-12-07 18:25:01 +00:00
|
|
|
ReceiveBytes len p next -> do
|
|
|
|
v <- liftIO $ tryNonAsync $ receiveExactly len (connIhdl conn) p
|
2016-11-22 01:22:58 +00:00
|
|
|
case v of
|
2016-12-08 19:47:49 +00:00
|
|
|
Left e -> return (Left (show e))
|
2016-11-22 01:22:58 +00:00
|
|
|
Right b -> runner (next b)
|
2016-11-30 20:38:16 +00:00
|
|
|
CheckAuthToken _u t next -> do
|
2016-12-06 19:40:31 +00:00
|
|
|
let authed = connCheckAuth conn t
|
2016-11-20 20:42:18 +00:00
|
|
|
runner (next authed)
|
2016-11-22 01:22:58 +00:00
|
|
|
Relay hin hout next -> do
|
2016-12-01 04:27:07 +00:00
|
|
|
v <- liftIO $ runRelay runnerio hin hout
|
2016-11-22 01:22:58 +00:00
|
|
|
case v of
|
2016-12-08 19:47:49 +00:00
|
|
|
Left e -> return (Left e)
|
|
|
|
Right exitcode -> runner (next exitcode)
|
2016-11-22 01:22:58 +00:00
|
|
|
RelayService service next -> do
|
2016-12-06 19:40:31 +00:00
|
|
|
v <- liftIO $ runRelayService conn runnerio service
|
2016-11-22 01:22:58 +00:00
|
|
|
case v of
|
2016-12-08 19:47:49 +00:00
|
|
|
Left e -> return (Left e)
|
|
|
|
Right () -> runner next
|
2016-12-01 04:27:07 +00:00
|
|
|
where
|
|
|
|
-- This is only used for running Net actions when relaying,
|
2016-12-01 04:41:01 +00:00
|
|
|
-- so it's ok to use runNetProto, despite it not supporting
|
2016-12-01 04:27:07 +00:00
|
|
|
-- all Proto actions.
|
2016-12-06 19:40:31 +00:00
|
|
|
runnerio = runNetProto conn
|
2016-11-22 01:22:58 +00:00
|
|
|
|
2016-12-09 20:55:48 +00:00
|
|
|
debugMessage :: String -> Message -> IO ()
|
|
|
|
debugMessage prefix m = debugM "p2p" $
|
|
|
|
prefix ++ " " ++ unwords (formatMessage safem)
|
|
|
|
where
|
|
|
|
safem = case m of
|
|
|
|
AUTH u _ -> AUTH u nullAuthToken
|
|
|
|
_ -> m
|
|
|
|
|
2016-12-02 17:45:45 +00:00
|
|
|
-- Send exactly the specified number of bytes or returns False.
|
|
|
|
--
|
|
|
|
-- The ByteString can be larger or smaller than the specified length.
|
|
|
|
-- For example, it can be lazily streaming from a file that gets
|
|
|
|
-- appended to, or truncated.
|
|
|
|
--
|
|
|
|
-- Must avoid sending too many bytes as it would confuse the other end.
|
|
|
|
-- This is easily dealt with by truncating it.
|
|
|
|
--
|
|
|
|
-- If too few bytes are sent, the only option is to give up on this
|
|
|
|
-- connection. False is returned to indicate this problem.
|
2016-12-07 17:37:35 +00:00
|
|
|
sendExactly :: Len -> L.ByteString -> Handle -> MeterUpdate -> IO Bool
|
2016-12-07 18:25:01 +00:00
|
|
|
sendExactly (Len n) b h p = do
|
|
|
|
sent <- meteredWrite' p h (L.take (fromIntegral n) b)
|
|
|
|
return (fromBytesProcessed sent == n)
|
|
|
|
|
|
|
|
receiveExactly :: Len -> Handle -> MeterUpdate -> IO L.ByteString
|
|
|
|
receiveExactly (Len n) h p = hGetMetered h (Just n) p
|
2016-12-02 17:45:45 +00:00
|
|
|
|
2016-12-08 19:47:49 +00:00
|
|
|
runRelay :: RunProto IO -> RelayHandle -> RelayHandle -> IO (Either String ExitCode)
|
|
|
|
runRelay runner (RelayHandle hout) (RelayHandle hin) =
|
|
|
|
bracket setup cleanup go
|
|
|
|
`catchNonAsync` (return . Left . show)
|
2016-11-20 16:08:16 +00:00
|
|
|
where
|
2016-11-21 23:24:55 +00:00
|
|
|
setup = do
|
|
|
|
v <- newEmptyMVar
|
2016-12-08 19:15:29 +00:00
|
|
|
void $ async $ relayFeeder runner v hin
|
2016-11-22 01:22:58 +00:00
|
|
|
void $ async $ relayReader v hout
|
2016-11-21 23:24:55 +00:00
|
|
|
return v
|
2016-11-20 16:08:16 +00:00
|
|
|
|
2016-11-21 23:24:55 +00:00
|
|
|
cleanup _ = do
|
|
|
|
hClose hin
|
|
|
|
hClose hout
|
|
|
|
|
2016-12-08 19:15:29 +00:00
|
|
|
go v = relayHelper runner v
|
2016-11-20 16:08:16 +00:00
|
|
|
|
2016-12-08 19:47:49 +00:00
|
|
|
runRelayService :: P2PConnection -> RunProto IO -> Service -> IO (Either String ())
|
|
|
|
runRelayService conn runner service =
|
|
|
|
bracket setup cleanup go
|
|
|
|
`catchNonAsync` (return . Left . show)
|
2016-11-20 16:08:16 +00:00
|
|
|
where
|
|
|
|
cmd = case service of
|
|
|
|
UploadPack -> "upload-pack"
|
|
|
|
ReceivePack -> "receive-pack"
|
2016-11-21 21:27:38 +00:00
|
|
|
|
|
|
|
serviceproc = gitCreateProcess
|
|
|
|
[ Param cmd
|
2016-12-06 19:40:31 +00:00
|
|
|
, File (repoPath (connRepo conn))
|
|
|
|
] (connRepo conn)
|
2016-11-21 21:27:38 +00:00
|
|
|
|
|
|
|
setup = do
|
2016-11-21 23:24:55 +00:00
|
|
|
(Just hin, Just hout, _, pid) <- createProcess serviceproc
|
|
|
|
{ std_out = CreatePipe
|
|
|
|
, std_in = CreatePipe
|
|
|
|
}
|
|
|
|
v <- newEmptyMVar
|
2016-12-08 19:15:29 +00:00
|
|
|
void $ async $ relayFeeder runner v hin
|
2016-11-22 01:22:58 +00:00
|
|
|
void $ async $ relayReader v hout
|
2016-11-21 23:24:55 +00:00
|
|
|
waiter <- async $ waitexit v pid
|
2016-11-22 01:22:58 +00:00
|
|
|
return (v, waiter, hin, hout, pid)
|
2016-11-21 23:24:55 +00:00
|
|
|
|
2016-11-22 01:22:58 +00:00
|
|
|
cleanup (_, waiter, hin, hout, pid) = do
|
2016-11-21 21:27:38 +00:00
|
|
|
hClose hin
|
|
|
|
hClose hout
|
2016-11-21 23:24:55 +00:00
|
|
|
cancel waiter
|
2016-11-21 21:27:38 +00:00
|
|
|
void $ waitForProcess pid
|
|
|
|
|
2016-12-08 19:15:29 +00:00
|
|
|
go (v, _, _, _, _) = do
|
|
|
|
r <- relayHelper runner v
|
2016-11-22 01:22:58 +00:00
|
|
|
case r of
|
2016-12-08 19:47:49 +00:00
|
|
|
Left e -> return (Left (show e))
|
|
|
|
Right exitcode -> runner $ net $ relayToPeer (RelayDone exitcode)
|
2016-11-21 23:24:55 +00:00
|
|
|
|
|
|
|
waitexit v pid = putMVar v . RelayDone =<< waitForProcess pid
|
2016-11-20 16:08:16 +00:00
|
|
|
|
2016-11-21 23:24:55 +00:00
|
|
|
-- Processes RelayData as it is put into the MVar.
|
2016-12-08 19:47:49 +00:00
|
|
|
relayHelper :: RunProto IO -> MVar RelayData -> IO (Either String ExitCode)
|
2016-12-08 19:15:29 +00:00
|
|
|
relayHelper runner v = loop
|
2016-11-21 23:24:55 +00:00
|
|
|
where
|
|
|
|
loop = do
|
2016-11-20 16:08:16 +00:00
|
|
|
d <- takeMVar v
|
|
|
|
case d of
|
2016-11-21 23:24:55 +00:00
|
|
|
RelayToPeer b -> do
|
2016-11-22 01:22:58 +00:00
|
|
|
r <- runner $ net $ relayToPeer (RelayToPeer b)
|
|
|
|
case r of
|
2016-12-08 19:47:49 +00:00
|
|
|
Left e -> return (Left e)
|
|
|
|
Right () -> loop
|
2016-11-21 23:24:55 +00:00
|
|
|
RelayDone exitcode -> do
|
2016-11-22 01:22:58 +00:00
|
|
|
_ <- runner $ net $ relayToPeer (RelayDone exitcode)
|
2016-12-08 19:47:49 +00:00
|
|
|
return (Right exitcode)
|
2016-12-08 19:15:29 +00:00
|
|
|
RelayFromPeer _ -> loop -- not handled here
|
2016-11-21 23:24:55 +00:00
|
|
|
|
2016-12-08 19:15:29 +00:00
|
|
|
-- Takes input from the peer, and sends it to the relay process's stdin.
|
2016-11-22 01:45:56 +00:00
|
|
|
-- Repeats until the peer tells it it's done or hangs up.
|
2016-12-08 19:15:29 +00:00
|
|
|
relayFeeder :: RunProto IO -> MVar RelayData -> Handle -> IO ()
|
|
|
|
relayFeeder runner v hin = loop
|
2016-11-21 23:24:55 +00:00
|
|
|
where
|
|
|
|
loop = do
|
2016-11-22 01:22:58 +00:00
|
|
|
mrd <- runner $ net relayFromPeer
|
|
|
|
case mrd of
|
2016-12-08 19:47:49 +00:00
|
|
|
Left _e ->
|
2016-12-08 19:15:29 +00:00
|
|
|
putMVar v (RelayDone (ExitFailure 1))
|
2016-12-08 19:47:49 +00:00
|
|
|
Right (RelayDone exitcode) ->
|
2016-12-08 19:15:29 +00:00
|
|
|
putMVar v (RelayDone exitcode)
|
2016-12-08 19:47:49 +00:00
|
|
|
Right (RelayFromPeer b) -> do
|
2016-12-08 19:15:29 +00:00
|
|
|
L.hPut hin b
|
|
|
|
hFlush hin
|
|
|
|
loop
|
2016-12-08 19:47:49 +00:00
|
|
|
Right (RelayToPeer _) -> loop -- not handled here
|
2016-11-21 23:24:55 +00:00
|
|
|
|
|
|
|
-- Reads input from the Handle and puts it into the MVar for relaying to
|
|
|
|
-- the peer. Continues until EOF on the Handle.
|
|
|
|
relayReader :: MVar RelayData -> Handle -> IO ()
|
|
|
|
relayReader v hout = loop
|
|
|
|
where
|
|
|
|
loop = do
|
2016-11-22 00:56:58 +00:00
|
|
|
bs <- getsome []
|
|
|
|
case bs of
|
|
|
|
[] -> return ()
|
|
|
|
_ -> do
|
|
|
|
putMVar v $ RelayToPeer (L.fromChunks bs)
|
2016-11-21 23:24:55 +00:00
|
|
|
loop
|
2016-11-22 00:56:58 +00:00
|
|
|
|
|
|
|
-- Waiit for the first available chunk. Then, without blocking,
|
|
|
|
-- try to get more chunks, in case a stream of chunks is being
|
|
|
|
-- written in close succession.
|
|
|
|
--
|
|
|
|
-- On Windows, hGetNonBlocking is broken, so avoid using it there.
|
|
|
|
getsome [] = do
|
|
|
|
b <- B.hGetSome hout chunk
|
|
|
|
if B.null b
|
|
|
|
then return []
|
|
|
|
#ifndef mingw32_HOST_OS
|
|
|
|
else getsome [b]
|
|
|
|
#else
|
|
|
|
else return [b]
|
|
|
|
#endif
|
|
|
|
getsome bs = do
|
|
|
|
b <- B.hGetNonBlocking hout chunk
|
|
|
|
if B.null b
|
|
|
|
then return (reverse bs)
|
|
|
|
else getsome (b:bs)
|
|
|
|
|
|
|
|
chunk = 65536
|