diff --git a/Control/Pipe/Network.hs b/Control/Pipe/Network.hs
new file mode 100644
--- /dev/null
+++ b/Control/Pipe/Network.hs
@@ -0,0 +1,129 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+module Control.Pipe.Network (
+  Application,
+  socketReader,
+  socketWriter,
+  ServerSettings(..),
+  runTCPServer,
+  ClientSettings(..),
+  runTCPClient,
+  ) where
+
+import qualified Network.Socket as NS
+import Network.Socket (Socket)
+import Network.Socket.ByteString (sendAll, recv)
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import Control.Concurrent (forkIO)
+import qualified Control.Exception as E
+import Control.Monad (forever, unless)
+import Control.Monad.Trans (MonadIO, liftIO, lift)
+import Control.Pipe
+
+-- adapted from conduit
+
+-- | Stream data from the socket.
+socketReader :: MonadIO m => Socket -> Pipe () ByteString m ()
+socketReader socket = go
+  where
+    go = do
+      bs <- lift . liftIO $ recv socket 4096
+      unless (B.null bs) $
+        yield bs >> go
+
+-- | Stream data to the socket.
+socketWriter :: MonadIO m => Socket -> Pipe ByteString Void m r
+socketWriter socket = forever $ await >>= lift . liftIO . sendAll socket
+
+-- | A simple TCP application. It takes two arguments: the 'Producer' to read
+-- input data from, and the 'Consumer' to send output data to.
+type Application m r = Pipe () ByteString m ()
+                    -> Pipe ByteString Void m ()
+                    -> IO r
+
+-- | Settings for a TCP server. It takes a port to listen on, and an optional
+-- hostname to bind to.
+data ServerSettings = ServerSettings
+    { serverPort :: Int
+    , serverHost :: Maybe String -- ^ 'Nothing' indicates no preference
+    }
+
+-- | Run an @Application@ with the given settings. This function will create a
+-- new listening socket, accept connections on it, and spawn a new thread for
+-- each connection.
+runTCPServer :: MonadIO m => ServerSettings -> Application m r -> IO r
+runTCPServer (ServerSettings port host) app = E.bracket
+    (bindPort host port)
+    NS.sClose
+    (forever . serve)
+  where
+    serve lsocket = do
+      (socket, _addr) <- NS.accept lsocket
+      forkIO $ do
+        E.finally
+          (app (socketReader socket) (socketWriter socket))
+          (NS.sClose socket)
+        return ()
+
+-- | Settings for a TCP client, specifying how to connect to the server.
+data ClientSettings = ClientSettings
+    { clientPort :: Int
+    , clientHost :: String
+    }
+
+-- | Run an 'Application' by connecting to the specified server.
+runTCPClient :: MonadIO m => ClientSettings -> Application m r -> IO r
+runTCPClient (ClientSettings port host) app = E.bracket
+    (getSocket host port)
+    NS.sClose
+    (\s -> app (socketReader s) (socketWriter s))
+
+-- | Attempt to connect to the given host/port.
+getSocket :: String -> Int -> IO NS.Socket
+getSocket host' port' = do
+    let hints = NS.defaultHints {
+                          NS.addrFlags = [NS.AI_ADDRCONFIG]
+                        , NS.addrSocketType = NS.Stream
+                        }
+    (addr:_) <- NS.getAddrInfo (Just hints) (Just host') (Just $ show port')
+    E.bracketOnError
+      (NS.socket (NS.addrFamily addr)
+                 (NS.addrSocketType addr)
+                 (NS.addrProtocol addr))
+      NS.sClose
+      (\sock -> NS.connect sock (NS.addrAddress addr) >> return sock)
+
+-- | Attempt to bind a listening @Socket@ on the given host/port. If no host is
+-- given, will use the first address available.
+bindPort :: Maybe String -> Int -> IO Socket
+bindPort host p = do
+    let hints = NS.defaultHints
+            { NS.addrFlags =
+                [ NS.AI_PASSIVE
+                , NS.AI_NUMERICSERV
+                , NS.AI_NUMERICHOST
+                ]
+            , NS.addrSocketType = NS.Stream
+            }
+        port = Just . show $ p
+    addrs <- NS.getAddrInfo (Just hints) host port
+    let
+        tryAddrs (addr1:rest@(_:_)) = E.catch
+                                      (theBody addr1)
+                                      (\(_ :: E.IOException) -> tryAddrs rest)
+        tryAddrs (addr1:[])         = theBody addr1
+        tryAddrs _                  = error "bindPort: addrs is empty"
+        theBody addr =
+          E.bracketOnError
+          (NS.socket
+            (NS.addrFamily addr)
+            (NS.addrSocketType addr)
+            (NS.addrProtocol addr))
+          NS.sClose
+          (\sock -> do
+              NS.setSocketOption sock NS.ReuseAddr 1
+              NS.bindSocket sock (NS.addrAddress addr)
+              NS.listen sock NS.maxListenQueue
+              return sock
+          )
+    tryAddrs addrs
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c)2012, Paolo Capriotti
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Paolo Capriotti nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/pipes-network.cabal b/pipes-network.cabal
new file mode 100644
--- /dev/null
+++ b/pipes-network.cabal
@@ -0,0 +1,27 @@
+Name: pipes-network
+Version: 0.0.1
+License: BSD3
+License-file: LICENSE
+Author: Paolo Capriotti
+Maintainer: p.capriotti@gmail.com
+Stability: Experimental
+Homepage: https://github.com/pcapriotti/pipes-extra
+Category: Control, Enumerator, Network
+Build-type: Simple
+Synopsis: Utilities to deal with sockets.
+Description: Utilities to deal with sockets.
+Cabal-version: >=1.8
+
+Source-Repository head
+    Type: git
+    Location: https://github.com/pcapriotti/pipes-extra
+
+Library
+    Build-Depends:
+        base (== 4.*),
+        mtl,
+        pipes-core (== 0.0.*),
+        bytestring (== 0.9.*),
+        network
+    Exposed-Modules:
+        Control.Pipe.Network
