Search code examples
haskellnetwork-programmingtcp

Why this TCP echo code does not return anything?


I wrote this code:

{-# LANGUAGE OverloadedStrings #-}

module Main where

import qualified Data.ByteString as BS
import Data.Text (Text)
import Data.Text.Encoding
import Data.Word
import Foreign
import Network.Socket
import Network.Socket.ByteString (recv, sendAll)

textToWord8Ptr :: Text -> IO (Ptr Word8)
textToWord8Ptr text = do
  let utf8Bytes = encodeUtf8 text
  ptr <- mallocArray (BS.length utf8Bytes)
  pokeArray ptr (BS.unpack utf8Bytes)
  return ptr

main = withSocketsDo $ do
  let hints =
        defaultHints
          { addrFlags = [AI_PASSIVE],
            addrSocketType = Stream
          }
  let port = Just "4242"
  let address = Just "tcpbin.com"
  print "running..."
  addr <- getAddrInfo (Just hints) address port
  socket <- openSocket $ head addr
  connection <- connect socket (addrAddress (head addr))
  print "connected"
  msg <- textToWord8Ptr "hellooo"
  bytesSent <- sendBuf socket msg 100
  print "sent bytes"
  bytes <- recv socket 3
  print bytes`

trying to get something to be sent back from the TCP server at https://tcpbin.com. It's a TCPecho service. When I run nc


tcpbin.com 4242
Hello server!
Hello server!

it works, but on this code I get no reply.

I inspected the TCP conversation with wireshark and the server sends a TCP ACK but never responds.


Solution

  • Three issues.

    1. You need a newline (see Steffen's comment)
    2. You should send the right number of bytes and not bytes from unknown memory - that 100 could include secrets, cryptographic material, credit card numbers...
    3. Receive the right number of bytes.

    I also added a shebang for easy testing, good for stackoverflow sized code and questions.

    #!/usr/bin/env cabal
    {- cabal:
         build-depends: base, bytestring, network, text
         default-extensions: OverloadedStrings
    -}
    
    module Main where
    
    import qualified Data.ByteString as BS
    import Data.Text (Text)
    import Data.Text.Encoding
    import Data.Word
    import Foreign
    import Network.Socket
    import Network.Socket.ByteString (recv, sendAll)
    
    textToWord8Ptr :: Text -> IO (Ptr Word8, Int)
    textToWord8Ptr text = do
      let utf8Bytes = encodeUtf8 text
      ptr <- mallocArray (BS.length utf8Bytes)
      pokeArray ptr (BS.unpack utf8Bytes)
      return (ptr, BS.length utf8Bytes)
    
    main :: IO ()
    main = withSocketsDo $ do
      let hints =
            defaultHints
              { addrFlags = [AI_PASSIVE],
                addrSocketType = Stream
              }
      let port = Just "4242"
      let address = Just "tcpbin.com"
      print "running..."
      addr <- getAddrInfo (Just hints) address port
      socket <- openSocket $ head addr
      connection <- connect socket (addrAddress (head addr))
      print "connected"
      (msg, len) <- textToWord8Ptr "hellooo\n" -- Newline needed here
      bytesSent <- sendBuf socket msg len      -- length of thing sent here in bytes which textToWord8Ptr wasn't helping with!
      print "sent bytes"
      bytes <- recv socket len          -- Number of bytes to receive here
      print bytes