2016-12-02 17:50:56 +00:00
|
|
|
{- P2P protocol, Annex implementation
|
|
|
|
-
|
|
|
|
- Copyright 2016 Joey Hess <id@joeyh.name>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
|
|
|
{-# LANGUAGE RankNTypes, FlexibleContexts #-}
|
|
|
|
|
|
|
|
module P2P.Annex
|
2016-12-02 19:34:15 +00:00
|
|
|
( RunMode(..)
|
2016-12-06 19:40:31 +00:00
|
|
|
, P2PConnection(..)
|
2016-12-02 17:50:56 +00:00
|
|
|
, runFullProto
|
p2p --link now defaults to setting up a bi-directional link
Both the local and remote git repositories get remotes added
pointing at one-another.
Makes pairing twice as easy!
Security: The new LINK command in the protocol can be sent repeatedly,
but only by a peer who has authenticated with us. So, it's entirely safe to
add a link back to that peer, or to some other peer it knows about.
Anything we receive over such a link, the peer could send us over the
current connection.
There is some risk of being flooded with LINKs, and adding too many
remotes. To guard against that, there's a hard cap on the number of remotes
that can be set up this way. This will only be a problem if setting up
large p2p networks that have exceptional interconnectedness.
A new, dedicated authtoken is created when sending LINK.
This also allows, in theory, using a p2p network like tor, to learn about
links on other networks, like telehash.
This commit was sponsored by Bruno BEAUFILS on Patreon.
2016-12-16 20:32:29 +00:00
|
|
|
, unusedPeerRemoteName
|
|
|
|
, linkAddress
|
2016-12-02 17:50:56 +00:00
|
|
|
) where
|
|
|
|
|
|
|
|
import Annex.Common
|
|
|
|
import Annex.Content
|
2016-12-02 20:39:01 +00:00
|
|
|
import Annex.Transfer
|
2016-12-09 18:52:38 +00:00
|
|
|
import Annex.ChangedRefs
|
2016-12-02 17:50:56 +00:00
|
|
|
import P2P.Protocol
|
|
|
|
import P2P.IO
|
p2p --link now defaults to setting up a bi-directional link
Both the local and remote git repositories get remotes added
pointing at one-another.
Makes pairing twice as easy!
Security: The new LINK command in the protocol can be sent repeatedly,
but only by a peer who has authenticated with us. So, it's entirely safe to
add a link back to that peer, or to some other peer it knows about.
Anything we receive over such a link, the peer could send us over the
current connection.
There is some risk of being flooded with LINKs, and adding too many
remotes. To guard against that, there's a hard cap on the number of remotes
that can be set up this way. This will only be a problem if setting up
large p2p networks that have exceptional interconnectedness.
A new, dedicated authtoken is created when sending LINK.
This also allows, in theory, using a p2p network like tor, to learn about
links on other networks, like telehash.
This commit was sponsored by Bruno BEAUFILS on Patreon.
2016-12-16 20:32:29 +00:00
|
|
|
import P2P.Address
|
|
|
|
import P2P.Auth
|
2016-12-02 18:49:22 +00:00
|
|
|
import Logs.Location
|
|
|
|
import Types.NumCopies
|
2016-12-08 23:56:02 +00:00
|
|
|
import Utility.Metered
|
p2p --link now defaults to setting up a bi-directional link
Both the local and remote git repositories get remotes added
pointing at one-another.
Makes pairing twice as easy!
Security: The new LINK command in the protocol can be sent repeatedly,
but only by a peer who has authenticated with us. So, it's entirely safe to
add a link back to that peer, or to some other peer it knows about.
Anything we receive over such a link, the peer could send us over the
current connection.
There is some risk of being flooded with LINKs, and adding too many
remotes. To guard against that, there's a hard cap on the number of remotes
that can be set up this way. This will only be a problem if setting up
large p2p networks that have exceptional interconnectedness.
A new, dedicated authtoken is created when sending LINK.
This also allows, in theory, using a p2p network like tor, to learn about
links on other networks, like telehash.
This commit was sponsored by Bruno BEAUFILS on Patreon.
2016-12-16 20:32:29 +00:00
|
|
|
import qualified Git.Command
|
|
|
|
import qualified Annex
|
|
|
|
import Annex.UUID
|
|
|
|
import Git.Types (RemoteName, remoteName, remotes)
|
|
|
|
import Config
|
2016-12-02 17:50:56 +00:00
|
|
|
|
|
|
|
import Control.Monad.Free
|
|
|
|
|
2016-12-02 19:34:15 +00:00
|
|
|
data RunMode
|
2016-12-09 20:27:20 +00:00
|
|
|
= Serving UUID (Maybe ChangedRefsHandle)
|
2016-12-02 19:34:15 +00:00
|
|
|
| Client
|
|
|
|
|
2016-12-02 17:50:56 +00:00
|
|
|
-- Full interpreter for Proto, that can receive and send objects.
|
2016-12-08 19:47:49 +00:00
|
|
|
runFullProto :: RunMode -> P2PConnection -> Proto a -> Annex (Either String a)
|
2016-12-06 19:40:31 +00:00
|
|
|
runFullProto runmode conn = go
|
2016-12-02 17:50:56 +00:00
|
|
|
where
|
|
|
|
go :: RunProto Annex
|
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-02 19:34:15 +00:00
|
|
|
go (Free (Local l)) = runLocal runmode go l
|
2016-12-02 17:50:56 +00:00
|
|
|
|
2016-12-08 19:47:49 +00:00
|
|
|
runLocal :: RunMode -> RunProto Annex -> LocalF (Proto a) -> Annex (Either String a)
|
2016-12-02 19:34:15 +00:00
|
|
|
runLocal runmode runner a = case a of
|
2016-12-02 17:50:56 +00:00
|
|
|
TmpContentSize k next -> do
|
|
|
|
tmp <- fromRepo $ gitAnnexTmpObjectLocation k
|
|
|
|
size <- liftIO $ catchDefaultIO 0 $ getFileSize tmp
|
|
|
|
runner (next (Len size))
|
2016-12-06 19:05:44 +00:00
|
|
|
FileSize f next -> do
|
|
|
|
size <- liftIO $ catchDefaultIO 0 $ getFileSize f
|
|
|
|
runner (next (Len size))
|
2016-12-02 18:49:22 +00:00
|
|
|
ContentSize k next -> do
|
|
|
|
let getsize = liftIO . catchMaybeIO . getFileSize
|
|
|
|
size <- inAnnex' isJust Nothing getsize k
|
|
|
|
runner (next (Len <$> size))
|
2016-12-08 23:56:02 +00:00
|
|
|
ReadContent k af o sender next -> do
|
2016-12-02 18:49:22 +00:00
|
|
|
v <- tryNonAsync $ prepSendAnnex k
|
|
|
|
case v of
|
2016-12-08 23:56:02 +00:00
|
|
|
-- The check can detect if the file
|
|
|
|
-- changed while it was transferred, but we don't
|
|
|
|
-- use it. Instead, the receiving peer must
|
|
|
|
-- AlwaysVerify the content it receives.
|
2016-12-02 18:49:22 +00:00
|
|
|
Right (Just (f, _check)) -> do
|
2016-12-08 23:56:02 +00:00
|
|
|
v' <- tryNonAsync $
|
|
|
|
transfer upload k af $
|
|
|
|
sinkfile f o sender
|
2016-12-02 18:49:22 +00:00
|
|
|
case v' of
|
2016-12-08 19:47:49 +00:00
|
|
|
Left e -> return (Left (show e))
|
2016-12-08 23:56:02 +00:00
|
|
|
Right (Left e) -> return (Left (show e))
|
|
|
|
Right (Right ok) -> runner (next ok)
|
|
|
|
-- content not available
|
|
|
|
Right Nothing -> runner (next False)
|
2016-12-08 19:47:49 +00:00
|
|
|
Left e -> return (Left (show e))
|
2016-12-08 22:26:03 +00:00
|
|
|
StoreContent k af o l getb next -> do
|
2016-12-02 18:49:22 +00:00
|
|
|
ok <- flip catchNonAsync (const $ return False) $
|
2016-12-08 23:56:02 +00:00
|
|
|
transfer download k af $ \p ->
|
2016-12-06 19:05:44 +00:00
|
|
|
getViaTmp AlwaysVerify k $ \tmp ->
|
2016-12-08 23:56:02 +00:00
|
|
|
unVerified $ storefile tmp o l getb p
|
2016-12-06 19:05:44 +00:00
|
|
|
runner (next ok)
|
2016-12-08 22:26:03 +00:00
|
|
|
StoreContentTo dest o l getb next -> do
|
2016-12-06 19:05:44 +00:00
|
|
|
ok <- flip catchNonAsync (const $ return False) $
|
2016-12-08 23:56:02 +00:00
|
|
|
storefile dest o l getb nullMeterUpdate
|
2016-12-02 18:49:22 +00:00
|
|
|
runner (next ok)
|
|
|
|
SetPresent k u next -> do
|
|
|
|
v <- tryNonAsync $ logChange k u InfoPresent
|
|
|
|
case v of
|
2016-12-08 19:47:49 +00:00
|
|
|
Left e -> return (Left (show e))
|
2016-12-02 18:49:22 +00:00
|
|
|
Right () -> runner next
|
|
|
|
CheckContentPresent k next -> do
|
|
|
|
v <- tryNonAsync $ inAnnex k
|
|
|
|
case v of
|
2016-12-08 19:47:49 +00:00
|
|
|
Left e -> return (Left (show e))
|
2016-12-02 18:49:22 +00:00
|
|
|
Right result -> runner (next result)
|
|
|
|
RemoveContent k next -> do
|
2016-12-09 16:47:57 +00:00
|
|
|
v <- tryNonAsync $
|
2016-12-09 16:54:12 +00:00
|
|
|
ifM (Annex.Content.inAnnex k)
|
2016-12-09 16:47:57 +00:00
|
|
|
( lockContentForRemoval k $ \contentlock -> do
|
|
|
|
removeAnnex contentlock
|
|
|
|
logStatus k InfoMissing
|
|
|
|
return True
|
|
|
|
, return True
|
|
|
|
)
|
2016-12-02 18:49:22 +00:00
|
|
|
case v of
|
2016-12-08 19:47:49 +00:00
|
|
|
Left e -> return (Left (show e))
|
2016-12-02 18:49:22 +00:00
|
|
|
Right result -> runner (next result)
|
|
|
|
TryLockContent k protoaction next -> do
|
|
|
|
v <- tryNonAsync $ lockContentShared k $ \verifiedcopy ->
|
|
|
|
case verifiedcopy of
|
|
|
|
LockedCopy _ -> runner (protoaction True)
|
|
|
|
_ -> runner (protoaction False)
|
|
|
|
-- If locking fails, lockContentShared throws an exception.
|
|
|
|
-- Let the peer know it failed.
|
|
|
|
case v of
|
|
|
|
Left _ -> runner $ do
|
|
|
|
protoaction False
|
|
|
|
next
|
|
|
|
Right _ -> runner next
|
2016-12-09 19:08:54 +00:00
|
|
|
WaitRefChange next -> case runmode of
|
2016-12-09 20:27:20 +00:00
|
|
|
Serving _ (Just h) -> do
|
2016-12-09 19:08:54 +00:00
|
|
|
v <- tryNonAsync $ liftIO $ waitChangedRefs h
|
|
|
|
case v of
|
|
|
|
Left e -> return (Left (show e))
|
|
|
|
Right changedrefs -> runner (next changedrefs)
|
2016-12-09 20:27:20 +00:00
|
|
|
_ -> return $ Left "change notification not available"
|
p2p --link now defaults to setting up a bi-directional link
Both the local and remote git repositories get remotes added
pointing at one-another.
Makes pairing twice as easy!
Security: The new LINK command in the protocol can be sent repeatedly,
but only by a peer who has authenticated with us. So, it's entirely safe to
add a link back to that peer, or to some other peer it knows about.
Anything we receive over such a link, the peer could send us over the
current connection.
There is some risk of being flooded with LINKs, and adding too many
remotes. To guard against that, there's a hard cap on the number of remotes
that can be set up this way. This will only be a problem if setting up
large p2p networks that have exceptional interconnectedness.
A new, dedicated authtoken is created when sending LINK.
This also allows, in theory, using a p2p network like tor, to learn about
links on other networks, like telehash.
This commit was sponsored by Bruno BEAUFILS on Patreon.
2016-12-16 20:32:29 +00:00
|
|
|
AddLinkToPeer addr next -> do
|
|
|
|
v <- tryNonAsync $ do
|
|
|
|
-- Flood protection; don't let a huge number
|
|
|
|
-- of peer remotes be created.
|
|
|
|
ns <- usedPeerRemoteNames
|
|
|
|
if length ns > 100
|
|
|
|
then return $ Right False
|
|
|
|
else linkAddress addr [] =<< unusedPeerRemoteName
|
|
|
|
case v of
|
|
|
|
Right (Right r) -> runner (next r)
|
|
|
|
_ -> runner (next False)
|
2016-12-02 20:39:01 +00:00
|
|
|
where
|
2016-12-06 19:05:44 +00:00
|
|
|
transfer mk k af ta = case runmode of
|
2016-12-02 20:39:01 +00:00
|
|
|
-- Update transfer logs when serving.
|
2016-12-09 19:08:54 +00:00
|
|
|
Serving theiruuid _ ->
|
2016-12-08 23:56:02 +00:00
|
|
|
mk theiruuid k af noRetry ta noNotification
|
2016-12-02 20:39:01 +00:00
|
|
|
-- Transfer logs are updated higher in the stack when
|
|
|
|
-- a client.
|
2016-12-08 23:56:02 +00:00
|
|
|
Client -> ta nullMeterUpdate
|
|
|
|
|
|
|
|
storefile dest (Offset o) (Len l) getb p = do
|
|
|
|
let p' = offsetMeterUpdate p (toBytesProcessed o)
|
2016-12-08 22:26:03 +00:00
|
|
|
v <- runner getb
|
|
|
|
case v of
|
|
|
|
Right b -> liftIO $ do
|
|
|
|
withBinaryFile dest ReadWriteMode $ \h -> do
|
|
|
|
when (o /= 0) $
|
|
|
|
hSeek h AbsoluteSeek o
|
2016-12-08 23:56:02 +00:00
|
|
|
meteredWrite p' h b
|
|
|
|
sz <- getFileSize dest
|
2016-12-08 22:26:03 +00:00
|
|
|
return (toInteger sz == l + o)
|
|
|
|
Left e -> error e
|
2016-12-08 23:56:02 +00:00
|
|
|
|
|
|
|
sinkfile f (Offset o) sender p = bracket setup cleanup go
|
|
|
|
where
|
|
|
|
setup = liftIO $ openBinaryFile f ReadMode
|
|
|
|
cleanup = liftIO . hClose
|
|
|
|
go h = do
|
|
|
|
let p' = offsetMeterUpdate p (toBytesProcessed o)
|
|
|
|
when (o /= 0) $
|
|
|
|
liftIO $ hSeek h AbsoluteSeek o
|
|
|
|
b <- liftIO $ hGetContentsMetered h p'
|
|
|
|
runner (sender b)
|
p2p --link now defaults to setting up a bi-directional link
Both the local and remote git repositories get remotes added
pointing at one-another.
Makes pairing twice as easy!
Security: The new LINK command in the protocol can be sent repeatedly,
but only by a peer who has authenticated with us. So, it's entirely safe to
add a link back to that peer, or to some other peer it knows about.
Anything we receive over such a link, the peer could send us over the
current connection.
There is some risk of being flooded with LINKs, and adding too many
remotes. To guard against that, there's a hard cap on the number of remotes
that can be set up this way. This will only be a problem if setting up
large p2p networks that have exceptional interconnectedness.
A new, dedicated authtoken is created when sending LINK.
This also allows, in theory, using a p2p network like tor, to learn about
links on other networks, like telehash.
This commit was sponsored by Bruno BEAUFILS on Patreon.
2016-12-16 20:32:29 +00:00
|
|
|
|
|
|
|
unusedPeerRemoteName :: Annex RemoteName
|
|
|
|
unusedPeerRemoteName = go (1 :: Integer) =<< usedPeerRemoteNames
|
|
|
|
where
|
|
|
|
go n names = do
|
|
|
|
let name = "peer" ++ show n
|
|
|
|
if name `elem` names
|
|
|
|
then go (n+1) names
|
|
|
|
else return name
|
|
|
|
|
|
|
|
usedPeerRemoteNames :: Annex [RemoteName]
|
|
|
|
usedPeerRemoteNames = filter ("peer" `isPrefixOf`)
|
|
|
|
. mapMaybe remoteName . remotes <$> Annex.gitRepo
|
|
|
|
|
|
|
|
linkAddress :: P2PAddressAuth -> [P2PAddressAuth] -> RemoteName -> Annex (Either String Bool)
|
|
|
|
linkAddress (P2PAddressAuth addr authtoken) linkbackto remotename = do
|
|
|
|
g <- Annex.gitRepo
|
|
|
|
cv <- liftIO $ tryNonAsync $ connectPeer g addr
|
|
|
|
case cv of
|
|
|
|
Left e -> return $ Left $ "Unable to connect with peer. Please check that the peer is connected to the network, and try again. (" ++ show e ++ ")"
|
|
|
|
Right conn -> do
|
|
|
|
u <- getUUID
|
|
|
|
v <- liftIO $ runNetProto conn $ do
|
|
|
|
authresp <- P2P.Protocol.auth u authtoken
|
|
|
|
lbok <- forM linkbackto $ P2P.Protocol.link
|
|
|
|
return (authresp, lbok)
|
|
|
|
case v of
|
|
|
|
Right (Just theiruuid, lbok) -> do
|
|
|
|
ok <- inRepo $ Git.Command.runBool
|
|
|
|
[ Param "remote", Param "add"
|
|
|
|
, Param remotename
|
|
|
|
, Param (formatP2PAddress addr)
|
|
|
|
]
|
|
|
|
when ok $ do
|
|
|
|
storeUUIDIn (remoteConfig remotename "uuid") theiruuid
|
|
|
|
storeP2PRemoteAuthToken addr authtoken
|
|
|
|
if not ok
|
|
|
|
then return $ Right False
|
|
|
|
else if or lbok || null linkbackto
|
|
|
|
then return $ Right True
|
|
|
|
else return $ Left "Linked with peer. However, the peer was unable to link back to us, so the link is one-way."
|
|
|
|
Right (Nothing, _) -> return $ Left "Unable to authenticate with peer. Please check the address and try again."
|
|
|
|
Left e -> return $ Left $ "Unable to authenticate with peer: " ++ e
|