2024-06-10 22:01:36 +00:00
|
|
|
{- P2P protocol proxying
|
|
|
|
-
|
|
|
|
- Copyright 2024 Joey Hess <id@joeyh.name>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
2024-06-11 20:56:52 +00:00
|
|
|
{-# LANGUAGE RankNTypes, FlexibleContexts, ScopedTypeVariables #-}
|
2024-06-10 22:01:36 +00:00
|
|
|
|
|
|
|
module P2P.Proxy where
|
|
|
|
|
|
|
|
import Annex.Common
|
|
|
|
import P2P.Protocol
|
|
|
|
import P2P.IO
|
2024-06-11 19:01:14 +00:00
|
|
|
import Utility.Metered (nullMeterUpdate)
|
2024-06-10 22:01:36 +00:00
|
|
|
|
2024-06-17 18:14:08 +00:00
|
|
|
import Control.Concurrent.STM
|
2024-06-17 16:44:08 +00:00
|
|
|
|
2024-06-17 18:14:08 +00:00
|
|
|
type ProtoCloser = Annex ()
|
|
|
|
|
|
|
|
data ClientSide = ClientSide RunState P2PConnection
|
|
|
|
|
|
|
|
data RemoteSide = RemoteSide
|
|
|
|
{ remoteUUID :: UUID
|
|
|
|
, remoteConnect :: Annex (Maybe (RunState, P2PConnection, ProtoCloser))
|
|
|
|
, remoteTMVar :: TMVar (RunState, P2PConnection, ProtoCloser)
|
|
|
|
}
|
|
|
|
|
|
|
|
mkRemoteSide :: UUID -> Annex (Maybe (RunState, P2PConnection, ProtoCloser)) -> Annex RemoteSide
|
|
|
|
mkRemoteSide remoteuuid remoteconnect = RemoteSide
|
|
|
|
<$> pure remoteuuid
|
|
|
|
<*> pure remoteconnect
|
|
|
|
<*> liftIO (atomically newEmptyTMVar)
|
2024-06-12 15:37:14 +00:00
|
|
|
|
2024-06-17 19:51:10 +00:00
|
|
|
runRemoteSide :: RemoteSide -> Proto a -> Annex (Either ProtoFailure a)
|
|
|
|
runRemoteSide remoteside a =
|
|
|
|
liftIO (atomically $ tryReadTMVar $ remoteTMVar remoteside) >>= \case
|
|
|
|
Just (runst, conn, _closer) -> liftIO $ runNetProto runst conn a
|
|
|
|
Nothing -> remoteConnect remoteside >>= \case
|
|
|
|
Just (runst, conn, closer) -> do
|
|
|
|
liftIO $ atomically $ putTMVar
|
|
|
|
(remoteTMVar remoteside)
|
|
|
|
(runst, conn, closer)
|
|
|
|
liftIO $ runNetProto runst conn a
|
|
|
|
Nothing -> giveup "Unable to connect to remote."
|
|
|
|
|
|
|
|
closeRemoteSide :: RemoteSide -> Annex ()
|
|
|
|
closeRemoteSide remoteside =
|
|
|
|
liftIO (atomically $ tryReadTMVar $ remoteTMVar remoteside) >>= \case
|
|
|
|
Just (_, _, closer) -> closer
|
|
|
|
Nothing -> return ()
|
|
|
|
|
2024-06-12 15:37:14 +00:00
|
|
|
{- To keep this module limited to P2P protocol actions,
|
|
|
|
- all other actions that a proxy needs to do are provided
|
|
|
|
- here. -}
|
|
|
|
data ProxyMethods = ProxyMethods
|
|
|
|
{ removedContent :: UUID -> Key -> Annex ()
|
|
|
|
-- ^ called when content is removed from a repository
|
|
|
|
, addedContent :: UUID -> Key -> Annex ()
|
|
|
|
-- ^ called when content is added to a repository
|
|
|
|
}
|
2024-06-10 22:01:36 +00:00
|
|
|
|
2024-06-11 16:05:44 +00:00
|
|
|
{- Type of function that takes a error handler, which is
|
2024-06-11 14:20:11 +00:00
|
|
|
- used to handle a ProtoFailure when receiving a message
|
2024-06-11 16:05:44 +00:00
|
|
|
- from the client or remote.
|
2024-06-11 14:20:11 +00:00
|
|
|
-}
|
2024-06-17 17:04:40 +00:00
|
|
|
type ProtoErrorHandled r =
|
|
|
|
(forall t. ((t -> Annex r) -> Annex (Either ProtoFailure t) -> Annex r)) -> Annex r
|
2024-06-11 14:20:11 +00:00
|
|
|
|
2024-06-11 16:05:44 +00:00
|
|
|
{- This is the first thing run when proxying with a client.
|
|
|
|
- The client has already authenticated. Most clients will send a
|
|
|
|
- VERSION message, although version 0 clients will not and will send
|
|
|
|
- some other message.
|
2024-06-10 22:01:36 +00:00
|
|
|
-
|
|
|
|
- But before the client will send VERSION, it needs to see AUTH_SUCCESS.
|
|
|
|
- So send that, although the connection with the remote is not actually
|
|
|
|
- brought up yet.
|
|
|
|
-}
|
|
|
|
getClientProtocolVersion
|
2024-06-17 19:00:11 +00:00
|
|
|
:: UUID
|
2024-06-10 22:01:36 +00:00
|
|
|
-> ClientSide
|
|
|
|
-> (Maybe (ProtocolVersion, Maybe Message) -> Annex r)
|
2024-06-17 17:04:40 +00:00
|
|
|
-> ProtoErrorHandled r
|
2024-06-17 19:00:11 +00:00
|
|
|
getClientProtocolVersion remoteuuid (ClientSide clientrunst clientconn) cont protoerrhandler =
|
|
|
|
protoerrhandler cont $ client $ getClientProtocolVersion' remoteuuid
|
2024-06-17 18:14:08 +00:00
|
|
|
where
|
|
|
|
client = liftIO . runNetProto clientrunst clientconn
|
2024-06-10 22:01:36 +00:00
|
|
|
|
2024-06-11 14:20:11 +00:00
|
|
|
getClientProtocolVersion'
|
2024-06-17 19:00:11 +00:00
|
|
|
:: UUID
|
2024-06-11 14:20:11 +00:00
|
|
|
-> Proto (Maybe (ProtocolVersion, Maybe Message))
|
2024-06-17 19:00:11 +00:00
|
|
|
getClientProtocolVersion' remoteuuid = do
|
|
|
|
net $ sendMessage (AUTH_SUCCESS remoteuuid)
|
2024-06-10 22:01:36 +00:00
|
|
|
msg <- net receiveMessage
|
|
|
|
case msg of
|
|
|
|
Nothing -> return Nothing
|
|
|
|
Just (VERSION v) ->
|
|
|
|
-- If the client sends a newer version than we
|
|
|
|
-- understand, reduce it; we need to parse the
|
|
|
|
-- protocol too.
|
|
|
|
let v' = if v > maxProtocolVersion
|
|
|
|
then maxProtocolVersion
|
|
|
|
else v
|
|
|
|
in return (Just (v', Nothing))
|
|
|
|
Just othermsg -> return
|
|
|
|
(Just (defaultProtocolVersion, Just othermsg))
|
|
|
|
|
|
|
|
{- Proxy between the client and the remote. This picks up after
|
2024-06-17 18:14:08 +00:00
|
|
|
- getClientProtocolVersion.
|
2024-06-10 22:01:36 +00:00
|
|
|
-}
|
|
|
|
proxy
|
2024-06-11 14:20:11 +00:00
|
|
|
:: Annex r
|
2024-06-12 15:37:14 +00:00
|
|
|
-> ProxyMethods
|
2024-06-10 22:01:36 +00:00
|
|
|
-> ServerMode
|
|
|
|
-> ClientSide
|
2024-06-17 19:51:10 +00:00
|
|
|
-> (Message -> Annex RemoteSide)
|
|
|
|
-> ProtocolVersion
|
2024-06-10 22:01:36 +00:00
|
|
|
-> Maybe Message
|
2024-06-11 14:20:11 +00:00
|
|
|
-- ^ non-VERSION message that was received from the client when
|
|
|
|
-- negotiating protocol version, and has not been responded to yet
|
2024-06-17 17:04:40 +00:00
|
|
|
-> ProtoErrorHandled r
|
2024-06-17 19:51:10 +00:00
|
|
|
proxy proxydone proxymethods servermode (ClientSide clientrunst clientconn) getremoteside protocolversion othermessage protoerrhandler = do
|
2024-06-10 22:01:36 +00:00
|
|
|
case othermessage of
|
2024-06-17 19:51:10 +00:00
|
|
|
Nothing -> protoerrhandler proxynextclientmessage $
|
|
|
|
client $ net $ sendMessage $ VERSION protocolversion
|
2024-06-11 16:05:44 +00:00
|
|
|
Just message -> proxyclientmessage (Just message)
|
2024-06-10 22:01:36 +00:00
|
|
|
where
|
2024-06-17 18:14:08 +00:00
|
|
|
client = liftIO . runNetProto clientrunst clientconn
|
|
|
|
|
2024-06-11 16:05:44 +00:00
|
|
|
proxynextclientmessage () = protoerrhandler proxyclientmessage $
|
2024-06-11 19:01:14 +00:00
|
|
|
client (net receiveMessage)
|
2024-06-10 22:01:36 +00:00
|
|
|
|
2024-06-11 16:05:44 +00:00
|
|
|
servermodechecker c a = c servermode $ \case
|
|
|
|
Nothing -> a
|
|
|
|
Just notallowed ->
|
|
|
|
protoerrhandler proxynextclientmessage $
|
2024-06-11 19:01:14 +00:00
|
|
|
client notallowed
|
2024-06-11 16:05:44 +00:00
|
|
|
|
2024-06-17 19:51:10 +00:00
|
|
|
proxyclientmessage Nothing = proxydone
|
2024-06-11 16:05:44 +00:00
|
|
|
proxyclientmessage (Just message) = case message of
|
2024-06-17 19:51:10 +00:00
|
|
|
CHECKPRESENT _ -> do
|
|
|
|
remoteside <- getremoteside message
|
|
|
|
proxyresponse remoteside message (const proxynextclientmessage)
|
|
|
|
LOCKCONTENT _ -> do
|
|
|
|
remoteside <- getremoteside message
|
|
|
|
proxyresponse remoteside message (const proxynextclientmessage)
|
|
|
|
UNLOCKCONTENT -> do
|
|
|
|
remoteside <- getremoteside message
|
|
|
|
proxynoresponse remoteside message proxynextclientmessage
|
|
|
|
REMOVE k -> do
|
|
|
|
remoteside <- getremoteside message
|
2024-06-11 16:05:44 +00:00
|
|
|
servermodechecker checkREMOVEServerMode $
|
2024-06-17 19:51:10 +00:00
|
|
|
handleREMOVE remoteside k message
|
|
|
|
GET _ _ _ -> do
|
|
|
|
remoteside <- getremoteside message
|
|
|
|
handleGET remoteside message
|
|
|
|
PUT _ k -> do
|
|
|
|
remoteside <- getremoteside message
|
2024-06-11 16:05:44 +00:00
|
|
|
servermodechecker checkPUTServerMode $
|
2024-06-17 19:51:10 +00:00
|
|
|
handlePUT remoteside k message
|
2024-06-11 16:05:44 +00:00
|
|
|
-- These messages involve the git repository, not the
|
|
|
|
-- annex. So they affect the git repository of the proxy,
|
|
|
|
-- not the remote.
|
|
|
|
CONNECT service ->
|
|
|
|
servermodechecker (checkCONNECTServerMode service) $
|
2024-06-12 14:40:51 +00:00
|
|
|
-- P2P protocol does not continue after
|
|
|
|
-- relaying from git.
|
2024-06-17 19:51:10 +00:00
|
|
|
protoerrhandler (\() -> proxydone) $
|
2024-06-12 14:40:51 +00:00
|
|
|
client $ net $ relayService service
|
|
|
|
NOTIFYCHANGE -> protoerr
|
2024-06-11 16:05:44 +00:00
|
|
|
-- Messages that the client should only send after one of
|
|
|
|
-- the messages above.
|
|
|
|
SUCCESS -> protoerr
|
|
|
|
FAILURE -> protoerr
|
2024-06-11 19:01:14 +00:00
|
|
|
DATA _ -> protoerr
|
|
|
|
VALIDITY _ -> protoerr
|
2024-06-11 16:05:44 +00:00
|
|
|
-- If the client errors out, give up.
|
|
|
|
ERROR msg -> giveup $ "client error: " ++ msg
|
|
|
|
-- Messages that only the server should send.
|
|
|
|
CONNECTDONE _ -> protoerr
|
|
|
|
CHANGED _ -> protoerr
|
|
|
|
AUTH_SUCCESS _ -> protoerr
|
|
|
|
AUTH_FAILURE -> protoerr
|
|
|
|
PUT_FROM _ -> protoerr
|
|
|
|
ALREADY_HAVE -> protoerr
|
|
|
|
-- Early messages that the client should not send now.
|
|
|
|
AUTH _ _ -> protoerr
|
|
|
|
VERSION _ -> protoerr
|
|
|
|
|
2024-06-11 20:56:52 +00:00
|
|
|
-- Send a message to the remote, send its response back to the
|
2024-06-12 15:37:14 +00:00
|
|
|
-- client, and pass it to the continuation.
|
2024-06-17 19:51:10 +00:00
|
|
|
proxyresponse remoteside message a =
|
|
|
|
getresponse (runRemoteSide remoteside) message $ \resp ->
|
|
|
|
protoerrhandler (a resp) $
|
|
|
|
client $ net $ sendMessage resp
|
2024-06-11 20:56:52 +00:00
|
|
|
|
|
|
|
-- Send a message to the remote, that it will not respond to.
|
2024-06-17 19:51:10 +00:00
|
|
|
proxynoresponse remoteside message a =
|
2024-06-12 15:37:14 +00:00
|
|
|
protoerrhandler a $
|
2024-06-17 19:51:10 +00:00
|
|
|
runRemoteSide remoteside $ net $ sendMessage message
|
2024-06-11 20:56:52 +00:00
|
|
|
|
|
|
|
-- Send a message to the endpoint and get back its response.
|
|
|
|
getresponse endpoint message handleresp =
|
|
|
|
protoerrhandler (withresp handleresp) $
|
|
|
|
endpoint $ net $ do
|
|
|
|
sendMessage message
|
|
|
|
receiveMessage
|
2024-06-11 19:01:14 +00:00
|
|
|
|
2024-06-11 20:56:52 +00:00
|
|
|
withresp a (Just resp) = a resp
|
|
|
|
-- Whichever of the remote or client the message was read from
|
|
|
|
-- hung up.
|
2024-06-17 19:51:10 +00:00
|
|
|
withresp _ Nothing = proxydone
|
2024-06-11 20:56:52 +00:00
|
|
|
|
|
|
|
-- Read a message from one party, send it to the other,
|
2024-06-12 15:37:14 +00:00
|
|
|
-- and then pass the message to the continuation.
|
2024-06-11 20:56:52 +00:00
|
|
|
relayonemessage from to cont =
|
|
|
|
flip protoerrhandler (from $ net $ receiveMessage) $
|
2024-06-12 15:37:14 +00:00
|
|
|
withresp $ \message ->
|
|
|
|
protoerrhandler (cont message) $
|
|
|
|
to $ net $ sendMessage message
|
2024-06-11 20:56:52 +00:00
|
|
|
|
2024-06-11 16:05:44 +00:00
|
|
|
protoerr = do
|
2024-06-11 19:01:14 +00:00
|
|
|
_ <- client $ net $ sendMessage (ERROR "protocol error")
|
2024-06-11 16:05:44 +00:00
|
|
|
giveup "protocol error"
|
2024-06-12 15:37:14 +00:00
|
|
|
|
2024-06-17 19:51:10 +00:00
|
|
|
handleREMOVE remoteside k message =
|
|
|
|
proxyresponse remoteside message $ \resp () -> do
|
2024-06-12 15:37:14 +00:00
|
|
|
case resp of
|
|
|
|
SUCCESS -> removedContent proxymethods
|
2024-06-17 18:14:08 +00:00
|
|
|
(remoteUUID remoteside) k
|
2024-06-12 15:37:14 +00:00
|
|
|
_ -> return ()
|
|
|
|
proxynextclientmessage ()
|
|
|
|
|
2024-06-17 19:51:10 +00:00
|
|
|
handleGET remoteside message = getresponse (runRemoteSide remoteside) message $
|
|
|
|
withDATA (relayGET remoteside)
|
2024-06-11 21:15:52 +00:00
|
|
|
|
2024-06-17 19:51:10 +00:00
|
|
|
handlePUT remoteside k message =
|
|
|
|
getresponse (runRemoteSide remoteside) message $ \resp -> case resp of
|
|
|
|
ALREADY_HAVE -> protoerrhandler proxynextclientmessage $
|
|
|
|
client $ net $ sendMessage resp
|
|
|
|
PUT_FROM _ ->
|
|
|
|
getresponse client resp $ withDATA (relayPUT remoteside k)
|
|
|
|
_ -> protoerr
|
2024-06-11 21:15:52 +00:00
|
|
|
|
|
|
|
withDATA a message@(DATA len) = a len message
|
|
|
|
withDATA _ _ = protoerr
|
|
|
|
|
2024-06-17 19:51:10 +00:00
|
|
|
relayGET remoteside len = relayDATAStart client $
|
|
|
|
relayDATACore len (runRemoteSide remoteside) client $
|
|
|
|
relayDATAFinish (runRemoteSide remoteside) client $
|
|
|
|
relayonemessage client (runRemoteSide remoteside) $
|
2024-06-12 15:37:14 +00:00
|
|
|
const proxynextclientmessage
|
2024-06-11 21:15:52 +00:00
|
|
|
|
2024-06-17 19:51:10 +00:00
|
|
|
relayPUT remoteside k len = relayDATAStart (runRemoteSide remoteside) $
|
|
|
|
relayDATACore len client (runRemoteSide remoteside) $
|
|
|
|
relayDATAFinish client (runRemoteSide remoteside) $
|
|
|
|
relayonemessage (runRemoteSide remoteside) client finished
|
2024-06-12 15:37:14 +00:00
|
|
|
where
|
|
|
|
finished resp () = do
|
|
|
|
case resp of
|
2024-06-17 18:14:08 +00:00
|
|
|
SUCCESS -> addedContent proxymethods (remoteUUID remoteside) k
|
2024-06-12 15:37:14 +00:00
|
|
|
_ -> return ()
|
|
|
|
proxynextclientmessage ()
|
2024-06-11 21:15:52 +00:00
|
|
|
|
|
|
|
relayDATAStart x receive message =
|
|
|
|
protoerrhandler (\() -> receive) $
|
|
|
|
x $ net $ sendMessage message
|
|
|
|
|
|
|
|
relayDATACore len x y finishget = protoerrhandler send $
|
|
|
|
x $ net $ receiveBytes len nullMeterUpdate
|
|
|
|
where
|
|
|
|
send b = protoerrhandler finishget $
|
|
|
|
y $ net $ sendBytes len b nullMeterUpdate
|
|
|
|
|
2024-06-17 19:51:10 +00:00
|
|
|
relayDATAFinish x y sendsuccessfailure () = case protocolversion of
|
2024-06-11 21:15:52 +00:00
|
|
|
ProtocolVersion 0 -> sendsuccessfailure
|
|
|
|
-- Protocol version 1 has a VALID or
|
|
|
|
-- INVALID message after the data.
|
2024-06-12 15:37:14 +00:00
|
|
|
_ -> relayonemessage x y (\_ () -> sendsuccessfailure)
|
2024-06-11 21:15:52 +00:00
|
|
|
|