동시 컨텍스트에서 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가 제공하는 함수 호출의 동시 / 병렬 실행 (위의 사용 사례와 관련하여)에 대한 WRT의 위험은 어디에 있습니까?
추신 : 나는 여전히 코드를 예외 및 리소스 누출로 강력하게 만드는 방법을 완전히 이해하기 위해 고심하고 있습니다. 따라서이 질문의 주요 초점은 아니지만 위의 코드에서 잘못된 점이 발견되면 의견을 남겨주십시오.
답변
이를 위해서는 copySocket
두 가지 기능 으로 교체해야 합니다. 하나는 일반 소켓에서 SSL로, 다른 하나는 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
그런 다음 connectToServer
SSL 연결을 설정 하도록 수정해야 합니다
-- |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