並行コンテキストで OpenSSL.Session APIを適切に使用する方法を理解しようとしています
例えば。 stunnel-style ssl-wrapper
を実装したいとすると、単純なfull-duplex tcp-port-forwarder:
を実装する次の基本的なスケルトン構造を持つことが期待されます。
runProxy :: PortID -> AddrInfo -> IO ()
runProxy localPort@(PortNumber lpn) serverAddrInfo = do
listener <- listenOn localPort
forever $ do
(sClient, clientAddr) <- accept listener
let finalize sServer = do
sClose sServer
sClose sClient
forkIO $ do
tidToServer <- myThreadId
bracket (connectToServer serverAddrInfo) finalize $ \sServer -> do
-- execute one 'copySocket' thread for each data direction
-- and make sure that if one direction dies, the other gets
-- pulled down as well
bracket (forkIO (copySocket sServer sClient
`finally` killThread tidToServer))
(killThread) $ \_ -> do
copySocket sClient sServer -- "controlling" thread
where
-- |Copy data from source to dest until EOF occurs on source
-- Copying may also be aborted due to exceptions
copySocket :: Socket -> Socket -> IO ()
copySocket src dst = go
where
go = do
buf <- B.recv src 4096
unless (B.null buf) $ do
B.sendAll dst buf
go
-- |Create connection to given AddrInfo target and return socket
connectToServer saddr = do
sServer <- socket (addrFamily saddr) Stream defaultProtocol
connect sServer (addrAddress saddr)
return sServer
上記のスケルトンをfull-duplex ssl-wrapping tcp-forwarding proxy
に変換するにはどうすればよいですか? HsOpenSSL APIによって提供される関数呼び出しの同時/並列実行(上記のユースケースのコンテキストで)に対するW.R.Tの危険性はどこにありますか?
PS:コードを堅牢にする方法を完全に理解するのにまだ苦労しています。例外とリソースリークに。したがって、この質問の主な焦点ではありませんが、上記のコードで何か悪いことに気付いた場合は、コメントを残してください。
これを行うには、copySocket
を2つの異なる関数に置き換える必要があります。1つはプレーンソケットからSSLへのデータを処理し、もう1つはSSLからプレーンソケットへのデータを処理します。
copyIn :: SSL.SSL -> Socket -> IO ()
copyIn src dst = go
where
go = do
buf <- SSL.read src 4096
unless (B.null buf) $ do
SB.sendAll dst buf
go
copyOut :: Socket -> SSL.SSL -> IO ()
copyOut src dst = go
where
go = do
buf <- SB.recv src 4096
unless (B.null buf) $ do
SSL.write dst buf
go
次に、SSL接続を確立するようにconnectToServer
を変更する必要があります
-- |Create connection to given AddrInfo target and return socket
connectToServer saddr = do
sServer <- socket (addrFamily saddr) Stream defaultProtocol
putStrLn "connecting"
connect sServer (addrAddress saddr)
putStrLn "establishing ssl context"
ctx <- SSL.context
putStrLn "setting ciphers"
SSL.contextSetCiphers ctx "DEFAULT"
putStrLn "setting verfication mode"
SSL.contextSetVerificationMode ctx SSL.VerifyNone
putStrLn "making ssl connection"
sslServer <- SSL.connection ctx sServer
putStrLn "doing handshake"
SSL.connect sslServer
putStrLn "connected"
return sslServer
finalize
を変更して、SSLセッションをシャットダウンします
let finalize sServer = do
putStrLn "shutting down ssl"
SSL.shutdown sServer SSL.Unidirectional
putStrLn "closing server socket"
maybe (return ()) sClose (SSL.sslSocket sServer)
putStrLn "closing client socket"
sClose sClient
最後に、次のようにwithOpenSSL
内でメインのものを実行することを忘れないでください
main = withOpenSSL $ do
let hints = defaultHints { addrSocketType = Stream, addrFamily = AF_INET }
addrs <- getAddrInfo (Just hints) (Just "localhost") (Just "22222")
let addr = head addrs
print addr
runProxy (PortNumber 11111) addr