pull/push over tor working now

Still a couple bugs:

* Closing the connection to the server leaves git upload-pack /
  receive-pack running, which could be used to DOS.

* Sometimes the data is transferred, but it fails at the end, sometimes
  with:

  git-remote-tor-annex: <socket: 10>: commitBuffer: resource vanished (Broken pipe)

  Must be a race condition around shutdown.
This commit is contained in:
Joey Hess 2016-11-21 19:24:55 -04:00
parent 070fb9e624
commit 6b992f672c
No known key found for this signature in database
GPG key ID: C910D9222512E3C7
4 changed files with 116 additions and 115 deletions

View file

@ -21,9 +21,7 @@ run (_remotename:address:[]) = forever $ do
-- gitremote-helpers protocol -- gitremote-helpers protocol
l <- getLine l <- getLine
case l of case l of
"capabilities" -> do "capabilities" -> putStrLn "connect" >> ready
putStrLn "connect"
putStrLn ""
"connect git-upload-pack" -> go UploadPack "connect git-upload-pack" -> go UploadPack
"connect git-receive-pack" -> go ReceivePack "connect git-receive-pack" -> go ReceivePack
_ -> error $ "git-remote-helpers protocol error at " ++ show l _ -> error $ "git-remote-helpers protocol error at " ++ show l
@ -33,9 +31,12 @@ run (_remotename:address:[]) = forever $ do
reverse $ takeWhile (/= '/') $ reverse address reverse $ takeWhile (/= '/') $ reverse address
| otherwise = parseAddressPort address | otherwise = parseAddressPort address
go service = do go service = do
ready
connectService onionaddress onionport service >>= exitWith
ready = do
putStrLn "" putStrLn ""
hFlush stdout hFlush stdout
connectService onionaddress onionport service >>= exitWith
run (_remotename:[]) = giveup "remote address not configured" run (_remotename:[]) = giveup "remote address not configured"
run _ = giveup "expected remote name and address parameters" run _ = giveup "expected remote name and address parameters"

View file

@ -141,30 +141,18 @@ data NetF c
| SendBytes Len L.ByteString c | SendBytes Len L.ByteString c
| ReceiveBytes Len (L.ByteString -> c) | ReceiveBytes Len (L.ByteString -> c)
| CheckAuthToken UUID AuthToken (Bool -> c) | CheckAuthToken UUID AuthToken (Bool -> c)
| Relay RelayHandle | RelayService Service c
(RelayData -> Net (Maybe ExitCode)) -- ^ Runs a service, relays its output to the peer, and data
(ExitCode -> c) -- from the peer to it.
-- ^ Waits for data to be written to the RelayHandle, and for messages | Relay RelayHandle RelayHandle (ExitCode -> c)
-- to be received from the peer, and passes the data to the -- ^ Reads from the first RelayHandle, and sends the data to a
-- callback, continuing until it returns an ExitCode. -- peer, while at the same time accepting input from the peer
| RelayService Service -- which is sent the the second RelayHandle. Continues until
(RelayHandle -> RelayData -> Net (Maybe ExitCode)) -- the peer sends an ExitCode.
(ExitCode -> c)
-- ^ Runs a service, and waits for it to output to stdout,
-- and for messages to be received from the peer, and passes
-- the data to the callback (which is also passed the service's
-- stdin RelayHandle), continuing uniil the service exits.
| WriteRelay RelayHandle L.ByteString c
-- ^ Write data to a relay's handle, flushing it immediately.
deriving (Functor) deriving (Functor)
type Net = Free NetF type Net = Free NetF
data RelayData
= RelayData L.ByteString
| RelayMessage Message
deriving (Show)
newtype RelayHandle = RelayHandle Handle newtype RelayHandle = RelayHandle Handle
data LocalF c data LocalF c
@ -212,8 +200,7 @@ runNet (SendBytes _ _ next) ms = ("> bytes", Nothing):runPure next ms
runNet (ReceiveBytes _ next) ms = ("< bytes", Nothing):runPure (next L.empty) ms runNet (ReceiveBytes _ next) ms = ("< bytes", Nothing):runPure (next L.empty) ms
runNet (CheckAuthToken _ _ next) ms = runPure (next True) ms runNet (CheckAuthToken _ _ next) ms = runPure (next True) ms
runNet (Relay _ _ next) ms = runPure (next ExitSuccess) ms runNet (Relay _ _ next) ms = runPure (next ExitSuccess) ms
runNet (RelayService _ _ next) ms = runPure (next ExitSuccess) ms runNet (RelayService _ next) ms = runPure next ms
runNet (WriteRelay _ _ next) ms = runPure next ms
runLocal :: Show r => LocalF (Proto r) -> [Message] -> [(String, Maybe Message)] runLocal :: Show r => LocalF (Proto r) -> [Message] -> [(String, Maybe Message)]
runLocal (KeyFileSize _ next) ms = runPure (next (Len 100)) ms runLocal (KeyFileSize _ next) ms = runPure (next (Len 100)) ms
@ -341,9 +328,7 @@ serve myuuid = go Nothing
-- setPresent not called because the peer may have -- setPresent not called because the peer may have
-- requested the data but not permanatly stored it. -- requested the data but not permanatly stored it.
GET offset key -> void $ sendContent key offset GET offset key -> void $ sendContent key offset
CONNECT service -> do CONNECT service -> net $ relayService service
exitcode <- net $ relayService service relayCallback
net $ sendMessage (CONNECTDONE exitcode)
_ -> net $ sendMessage (ERROR "unexpected command") _ -> net $ sendMessage (ERROR "unexpected command")
sendContent :: Key -> Offset -> Proto Bool sendContent :: Key -> Offset -> Proto Bool
@ -399,19 +384,28 @@ readKeyFileLen key (Offset offset) = do
connect :: Service -> Handle -> Handle -> Proto ExitCode connect :: Service -> Handle -> Handle -> Proto ExitCode
connect service hin hout = do connect service hin hout = do
net $ sendMessage (CONNECT service) net $ sendMessage (CONNECT service)
net $ relay (RelayHandle hin) (relayCallback (RelayHandle hout)) net $ relay (RelayHandle hin) (RelayHandle hout)
relayCallback :: RelayHandle -> RelayData -> Net (Maybe ExitCode) data RelayData
relayCallback hout (RelayMessage (DATA len)) = do = RelayToPeer L.ByteString
writeRelay hout =<< receiveBytes len | RelayFromPeer L.ByteString
return Nothing | RelayDone ExitCode
relayCallback _ (RelayMessage (CONNECTDONE exitcode)) = deriving (Show)
return (Just exitcode)
relayCallback _ (RelayMessage m) = do relayFromPeer :: Net RelayData
sendMessage $ ERROR $ "expected DATA or CONNECTDONE not " ++ unwords (Proto.formatMessage m) relayFromPeer = do
return (Just (ExitFailure 1)) r <- receiveMessage
relayCallback _ (RelayData b) = do case r of
CONNECTDONE exitcode -> return $ RelayDone exitcode
DATA len -> RelayFromPeer <$> receiveBytes len
_ -> do
sendMessage $ ERROR "expected DATA or CONNECTDONE"
return $ RelayDone $ ExitFailure 1
relayToPeer :: RelayData -> Net ()
relayToPeer (RelayDone exitcode) = sendMessage (CONNECTDONE exitcode)
relayToPeer (RelayToPeer b) = do
let len = Len $ fromIntegral $ L.length b let len = Len $ fromIntegral $ L.length b
sendMessage (DATA len) sendMessage (DATA len)
sendBytes len b sendBytes len b
return Nothing relayToPeer (RelayFromPeer _) = return ()

View file

@ -14,7 +14,6 @@ module Remote.Helper.P2P.IO
import Remote.Helper.P2P import Remote.Helper.P2P
import Utility.Process import Utility.Process
import Types.UUID
import Git import Git
import Git.Command import Git.Command
import Utility.SafeCommand import Utility.SafeCommand
@ -24,10 +23,10 @@ import Utility.Exception
import Control.Monad import Control.Monad
import Control.Monad.Free import Control.Monad.Free
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Data.Maybe
import System.Exit (ExitCode(..)) import System.Exit (ExitCode(..))
import System.IO import System.IO
import Control.Concurrent import Control.Concurrent
import Control.Concurrent.Async
import qualified Data.ByteString as B import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy as L
@ -58,7 +57,6 @@ runNetHandle s runner f = case f of
runner next runner next
ReceiveMessage next -> do ReceiveMessage next -> do
l <- liftIO $ hGetLine (ihdl s) l <- liftIO $ hGetLine (ihdl s)
-- liftIO $ hPutStrLn stderr ("< " ++ show l)
case parseMessage l of case parseMessage l of
Just m -> runner (next m) Just m -> runner (next m)
Nothing -> runner $ do Nothing -> runner $ do
@ -72,64 +70,43 @@ runNetHandle s runner f = case f of
runner next runner next
ReceiveBytes (Len n) next -> do ReceiveBytes (Len n) next -> do
b <- liftIO $ L.hGet (ihdl s) (fromIntegral n) b <- liftIO $ L.hGet (ihdl s) (fromIntegral n)
--liftIO $ hPutStrLn stderr $ "!!!" ++ show (L.length b)
runner (next b) runner (next b)
CheckAuthToken u t next -> do CheckAuthToken u t next -> do
authed <- return True -- TODO XXX FIXME really check authed <- return True -- TODO XXX FIXME really check
runner (next authed) runner (next authed)
Relay hout callback next -> Relay hin hout next ->
runRelay runner hout callback >>= runner . next runRelay runner hin hout >>= runner . next
RelayService service callback next -> RelayService service next ->
runRelayService s runner service callback >>= runner . next runRelayService s runner service >> runner next
WriteRelay (RelayHandle h) b next -> do
liftIO $ do
-- L.hPut h b
hPutStrLn h (show ("relay got:", b, L.length b))
hFlush h
runner next
runRelay runRelay
:: MonadIO m :: MonadIO m
=> RunProto => RunProto
-> RelayHandle -> RelayHandle
-> (RelayData -> Net (Maybe ExitCode)) -> RelayHandle
-> m ExitCode -> m ExitCode
runRelay runner (RelayHandle hout) callback = do runRelay runner (RelayHandle hout) (RelayHandle hin) = liftIO $
v <- liftIO newEmptyMVar bracket setup cleanup go
_ <- liftIO $ forkIO $ readout v
feeder <- liftIO $ forkIO $ feedin v
exitcode <- liftIO $ drain v
liftIO $ killThread feeder
return exitcode
where where
feedin v = forever $ do setup = do
m <- runner $ net receiveMessage v <- newEmptyMVar
putMVar v $ RelayMessage m void $ forkIO $ relayFeeder runner v
void $ forkIO $ relayReader v hout
return v
readout v = do cleanup _ = do
b <- B.hGetSome hout 65536 hClose hin
if B.null b hClose hout
then hClose hout
else do go v = relayHelper runner v hin
putMVar v $ RelayData (L.fromChunks [b])
readout v
drain v = do
d <- takeMVar v
liftIO $ hPutStrLn stderr (show d)
r <- runner $ net $ callback d
case r of
Nothing -> drain v
Just exitcode -> return exitcode
runRelayService runRelayService
:: (MonadIO m, MonadMask m) :: MonadIO m
=> S => S
-> RunProto -> RunProto
-> Service -> Service
-> (RelayHandle -> RelayData -> Net (Maybe ExitCode)) -> m ()
-> m ExitCode runRelayService s runner service = liftIO $ bracket setup cleanup go
runRelayService s runner service callback = bracket setup cleanup go
where where
cmd = case service of cmd = case service of
UploadPack -> "upload-pack" UploadPack -> "upload-pack"
@ -141,45 +118,70 @@ runRelayService s runner service callback = bracket setup cleanup go
] (repo s) ] (repo s)
setup = do setup = do
v <- liftIO newEmptyMVar (Just hin, Just hout, _, pid) <- createProcess serviceproc
(Just hin, Just hout, _, pid) <- liftIO $ { std_out = CreatePipe
createProcess serviceproc , std_in = CreatePipe
{ std_out = CreatePipe }
, std_in = CreatePipe v <- newEmptyMVar
} feeder <- async $ relayFeeder runner v
feeder <- liftIO $ forkIO $ feedin v reader <- async $ relayReader v hout
return (v, feeder, hin, hout, pid) waiter <- async $ waitexit v pid
return (v, feeder, reader, waiter, hin, hout, pid)
cleanup (_, feeder, hin, hout, pid) = liftIO $ do cleanup (_, feeder, reader, waiter, hin, hout, pid) = do
hPutStrLn stderr "!!!!\n\nIN CLEANUP"
hFlush stderr
hClose hin hClose hin
hClose hout hClose hout
liftIO $ killThread feeder cancel reader
cancel waiter
void $ waitForProcess pid void $ waitForProcess pid
go (v, _, hin, hout, pid) = do go (v, _, _, _, hin, _, _) = do
_ <- liftIO $ forkIO $ readout v hout exitcode <- relayHelper runner v hin
_ <- liftIO $ forkIO $ putMVar v . Left =<< waitForProcess pid runner $ net $ relayToPeer (RelayDone exitcode)
liftIO $ drain v hin
waitexit v pid = putMVar v . RelayDone =<< waitForProcess pid
drain v hin = do -- Processes RelayData as it is put into the MVar.
relayHelper :: RunProto -> MVar RelayData -> Handle -> IO ExitCode
relayHelper runner v hin = loop
where
loop = do
d <- takeMVar v d <- takeMVar v
case d of case d of
Left exitcode -> return exitcode RelayFromPeer b -> do
Right relaydata -> do L.hPut hin b
liftIO $ hPutStrLn stderr ("> " ++ show relaydata) hFlush hin
_ <- runner $ net $ loop
callback (RelayHandle hin) relaydata RelayToPeer b -> do
drain v hin runner $ net $ relayToPeer (RelayToPeer b)
loop
readout v hout = do RelayDone exitcode -> do
runner $ net $ relayToPeer (RelayDone exitcode)
return exitcode
-- Takes input from the peer, and puts it into the MVar for processing.
-- Repeats until the peer tells it it's done.
relayFeeder :: RunProto -> MVar RelayData -> IO ()
relayFeeder runner v = loop
where
loop = do
rd <- runner $ net relayFromPeer
putMVar v rd
case rd of
RelayDone _ -> return ()
_ -> loop
-- 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
b <- B.hGetSome hout 65536 b <- B.hGetSome hout 65536
if B.null b if B.null b
then return () then return ()
else do else do
putMVar v $ Right $ putMVar v $ RelayToPeer (L.fromChunks [b])
RelayData (L.fromChunks [b]) loop
readout v hout
feedin v = forever $ do
m <- runner $ net receiveMessage
putMVar v $ Right $ RelayMessage m

View file

@ -17,8 +17,12 @@ import qualified Data.ByteString.UTF8 as BU8
import qualified System.Random as R import qualified System.Random as R
type OnionPort = Int type OnionPort = Int
newtype OnionAddress = OnionAddress String newtype OnionAddress = OnionAddress String
deriving (Show)
type OnionSocket = FilePath type OnionSocket = FilePath
type UniqueIdent = String type UniqueIdent = String
connectHiddenService :: OnionAddress -> OnionPort -> IO Socket connectHiddenService :: OnionAddress -> OnionPort -> IO Socket