diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+Copyright (c) 2022, Internet Initiative Japan Inc.
+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 the copyright holders nor the names of its
+    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/Network/UDP.hs b/Network/UDP.hs
new file mode 100644
--- /dev/null
+++ b/Network/UDP.hs
@@ -0,0 +1,263 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+-- | Best current practice library for UDP clients and servers.
+--
+--   * Efficient receiving function without memory copy
+--   * Proper buffer size
+--   * Type-safe APIs
+--   * TCP-like APIs (creating a UDP connection from a listing socket) in the server side
+--   * Auto migration (network interface selection) in the client side
+--
+--   The 'Network.Socket.ByteString.recv' family in
+--   "Network.Socket.ByteString" uses 'createAndTrim'
+--   internaly.  So, one buffer is allocated before corresponding
+--   system calls are called. Then another buffer is allocated
+--   according to the input size and the input is copied.
+--   Receiving functions provided by this library uses 'createUptoN'
+--   to avoid the memory copy.
+--
+--   Recent application protocols are designed to avoid IP
+--   fragmentation. So, the UDP payload size is never over 1,500.
+--   This library uses 2,048 for the buffer size. This size ensures
+--   no global locking when allocating 'ByteString' (i.e. a buffer).
+--
+--   To know the background of TCP-like API in the server side, see:
+--
+--   * https://kazu-yamamoto.hatenablog.jp/entry/2022/02/25/153122
+--
+--   To know the background of auto migration in the client side, see:
+--
+--   * https://kazu-yamamoto.hatenablog.jp/entry/2021/06/29/134930
+--   * https://www.iij.ad.jp/en/dev/iir/pdf/iir_vol52_focus2_EN.pdf (Sec 3.9)
+module Network.UDP (
+  -- * Sockets used by clients and servers after accept
+    UDPSocket(..)
+  , clientSocket
+  , recv
+  , recvBuf
+  , send
+  , sendBuf
+  -- * Server's wildcard socket
+  , ListenSocket(..)
+  , serverSocket
+  , ClientSockAddr(..)
+  , recvFrom
+  , sendTo
+  -- * Server's connected socket
+  , accept
+  -- * Closing
+  , stop
+  , close
+  -- * Misc
+  , natRebinding
+  ) where
+
+import Control.Concurrent
+import qualified Control.Exception as E
+import Control.Monad
+import Data.ByteString (ByteString)
+import Data.IP hiding (addr)
+import Data.Maybe (fromJust)
+import Data.Word (Word8)
+import Foreign.Ptr (Ptr)
+import qualified GHC.IO.Exception as E
+import qualified Network.Socket as NS
+import Network.Socket hiding (accept, close, sendBuf, recvBuf)
+import qualified Network.Socket.ByteString as NSB
+import qualified System.IO.Error as E
+
+import Network.UDP.Types
+import qualified Network.UDP.Recv as R
+
+----------------------------------------------------------------
+
+anySockAddr :: SockAddr -> SockAddr
+anySockAddr (SockAddrInet p _)      = SockAddrInet  p 0
+anySockAddr (SockAddrInet6 p f _ s) = SockAddrInet6 p f (0,0,0,0) s
+anySockAddr _                       = error "anySockAddr"
+
+isAnySockAddr :: SockAddr -> Bool
+isAnySockAddr (SockAddrInet _ 0)              = True
+isAnySockAddr (SockAddrInet6 _ _ (0,0,0,0) _) = True
+isAnySockAddr _                               = False
+
+----------------------------------------------------------------
+
+-- | A listening socket for UDP which can be used
+--   for 'recvFrom' and 'sendTo'.
+--   Optionally, a connected UDP socket can be created
+--   with 'accept' as an emulation of TCP.
+data ListenSocket = ListenSocket {
+    listenSocket :: Socket
+  , mySockAddr   :: SockAddr
+  , wildcard     :: Bool -- ^ 'True' for wildcard. 'False' for interface-specific.
+  } deriving (Eq, Show)
+
+-- | A UDP socket which are used with 'recv' and 'send'.
+data UDPSocket = UDPSocket {
+    udpSocket    :: Socket
+  , peerSockAddr :: SockAddr -- ^ Used for a unconnected socket naturally. Used for a connected sockdet for checking
+  , connected    :: Bool
+  } deriving (Eq, Show)
+
+-- | A client socket address from the server point of view.
+data ClientSockAddr = ClientSockAddr SockAddr [Cmsg] deriving (Eq, Show)
+
+----------------------------------------------------------------
+
+-- | Creating a listening UDP socket.
+serverSocket :: (IP, PortNumber) -> IO ListenSocket
+serverSocket ip = E.bracketOnError open NS.close $ \s -> do
+    setSocketOption s ReuseAddr 1
+    withFdSocket s setCloseOnExecIfNeeded
+#if !defined(openbsd_HOST_OS)
+    when (family == AF_INET6) $ setSocketOption s IPv6Only 1
+#endif
+    bind s sa
+    let wild = isAnySockAddr sa
+    when wild $ do
+        let opt = decideOption sa
+        setSocketOption s opt 1
+    return $ ListenSocket s sa wild
+  where
+    sa     = toSockAddr ip
+    family = sockAddrFamily sa
+    open   = socket family Datagram defaultProtocol
+
+----------------------------------------------------------------
+
+-- | Receiving data with a listening UDP socket.
+--   For a wildcard socket, recvmsg() is called.
+--   For an interface specific socket, recvfrom() is called.
+recvFrom :: ListenSocket -> IO (ByteString, ClientSockAddr)
+recvFrom ListenSocket{..}
+  | wildcard = do
+        (bs,sa,cmsg,_) <- R.recvMsg listenSocket properUDPSize properCMSGSize 0
+        return (bs, ClientSockAddr sa cmsg)
+  | otherwise = do
+        (bs,sa) <- R.recvFrom listenSocket properUDPSize
+        return (bs, ClientSockAddr sa [])
+
+-- | Sending data with a listening UDP socket.
+--   For a wildcard socket, sendmsg() is called.
+--   For an interface specific socket, sento() is called.
+sendTo :: ListenSocket -> ByteString -> ClientSockAddr -> IO ()
+sendTo ListenSocket{..} bs (ClientSockAddr sa cmsgs)
+  | wildcard  = void $ NSB.sendMsg listenSocket sa [bs] cmsgs 0
+  | otherwise = void $ NSB.sendTo  listenSocket bs sa
+
+----------------------------------------------------------------
+
+-- | Creating a connected UDP socket like TCP's accept().
+accept :: ListenSocket -> ClientSockAddr -> IO UDPSocket
+accept ListenSocket{..} (ClientSockAddr peersa cmsgs) = E.bracketOnError open NS.close $ \s -> do
+    setSocketOption s ReuseAddr 1
+    withFdSocket s setCloseOnExecIfNeeded
+    let mysa' | wildcard  = getMySockAddr mySockAddr cmsgs
+              | otherwise = anySockAddr mySockAddr
+    -- wildcard:  (UDP, *.443, *:*) -> (UDP, 127.0.0.1:443, *:*)
+    -- otherwise: (UDP, 127.0.0.1:443, *:*) -> (UDP, *:443, *:*)
+    bind s mysa'
+    -- bind and connect is not atomic
+    -- So, bind may results in EADDRINUSE
+       `E.catch` postphone (bind s mysa')
+    connect s peersa  -- (UDP, 127.0.0.1:443, pa:pp)
+    return $ UDPSocket s peersa True
+  where
+    postphone action e
+      | E.ioeGetErrorType e == E.ResourceBusy = threadDelay 10000 >> action
+      | otherwise                             = E.throwIO e
+    family = sockAddrFamily mySockAddr
+    open   = socket family Datagram defaultProtocol
+
+----------------------------------------------------------------
+
+-- | Creating a unconnected UDP socket.
+clientSocket :: HostName -> ServiceName -> Bool -> IO UDPSocket
+clientSocket host port conn = do
+    addr <- head <$> NS.getAddrInfo (Just hints) (Just host) (Just port)
+    E.bracketOnError (NS.openSocket addr) NS.close $ \s -> do
+        let sa = addrAddress addr
+        when conn $ NS.connect s sa
+        return $ UDPSocket s sa conn
+ where
+    hints = NS.defaultHints { addrSocketType = Datagram }
+
+-- | Sending data with a UDP socket.
+--   If the socket is connected, send() is called.
+--   Otherwise, sento() is called.
+send :: UDPSocket -> (ByteString -> IO ())
+send UDPSocket{..}
+  | connected = \bs -> void $ NSB.send   udpSocket bs
+  | otherwise = \bs -> void $ NSB.sendTo udpSocket bs peerSockAddr
+
+-- | Receiving data with a UDP socket.
+--   If the socket is connected, recv() is called.
+--   Otherwise, recvfrom() is called.
+recv :: UDPSocket -> IO ByteString
+recv UDPSocket{..}
+  | connected = R.recv udpSocket properUDPSize
+  | otherwise = go
+  where
+    go = do
+        (bs, sa) <- R.recvFrom udpSocket properUDPSize
+        if sa == peerSockAddr then return bs else go
+
+-- | Sending data in a buffer with a UDP socket.
+--   If the socket is connected, send() is called.
+--   Otherwise, sento() is called.
+sendBuf :: UDPSocket -> (Ptr Word8 -> Int -> IO ())
+sendBuf UDPSocket{..} ptr siz
+  | connected = void $ NS.sendBuf   udpSocket ptr siz
+  | otherwise = void $ NS.sendBufTo udpSocket ptr siz peerSockAddr
+
+-- | Receiving data in a buffer with a UDP socket.
+--   If the socket is connected, recv() is called.
+--   Otherwise, recvfrom() is called.
+recvBuf :: UDPSocket -> (Ptr Word8 -> Int -> IO Int)
+recvBuf UDPSocket{..} ptr siz
+  | connected = NS.recvBuf   udpSocket ptr siz
+  | otherwise = go
+  where
+    go = do
+        (len,sa) <- NS.recvBufFrom udpSocket ptr siz
+        if sa == peerSockAddr then return len else go
+
+-- | Closing a socket.
+stop :: ListenSocket -> IO ()
+stop (ListenSocket s _ _) = NS.close s
+
+-- | Closing a socket.
+close :: UDPSocket -> IO ()
+close (UDPSocket s _ _) = NS.close s
+
+-- | Emulation of NAT rebiding in the client side.
+--   This is mainly used for test purposes.
+natRebinding :: UDPSocket -> IO UDPSocket
+natRebinding (UDPSocket _ sa conn) = E.bracketOnError open NS.close $ \s -> do
+    when conn $ NS.connect s sa
+    return $ UDPSocket s sa conn
+  where
+    family = sockAddrFamily sa
+    open = NS.socket family Datagram NS.defaultProtocol
+
+----------------------------------------------------------------
+
+decideOption :: SockAddr -> SocketOption
+decideOption SockAddrInet{}  = RecvIPv4PktInfo
+decideOption SockAddrInet6{} = RecvIPv6PktInfo
+decideOption _               = error "decideOption"
+
+-- | Obtaining my sockaddr for a wildcard socket from cmsgs.
+getMySockAddr :: SockAddr -> [Cmsg] -> SockAddr
+getMySockAddr (SockAddrInet p _) cmsgs = SockAddrInet p addr
+  where
+    pktinfo = fromJust $ lookupCmsg CmsgIdIPv4PktInfo cmsgs
+    IPv4PktInfo _ _ addr = fromJust $ decodeCmsg pktinfo
+getMySockAddr (SockAddrInet6 p f _ sc) cmsgs = SockAddrInet6 p f addr sc
+  where
+    pktinfo = fromJust $ lookupCmsg CmsgIdIPv6PktInfo cmsgs
+    IPv6PktInfo _ addr = fromJust $ decodeCmsg pktinfo
+getMySockAddr _ _ = error "getMySockAddr"
diff --git a/Network/UDP/Recv.hs b/Network/UDP/Recv.hs
new file mode 100644
--- /dev/null
+++ b/Network/UDP/Recv.hs
@@ -0,0 +1,44 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.UDP.Recv (
+    recv
+  , recvFrom
+  , recvMsg
+  ) where
+
+import Data.ByteString (ByteString)
+import Data.ByteString.Internal (create, ByteString(..), createUptoN)
+import Foreign.ForeignPtr (withForeignPtr)
+import Network.Socket (Socket, SockAddr, Cmsg, MsgFlag, recvBuf, recvBufFrom, recvBufMsg)
+import Network.Socket.Internal (zeroMemory)
+import System.IO.Error (ioeSetErrorString, mkIOError)
+import GHC.IO.Exception (IOErrorType(..))
+
+mkInvalidRecvArgError :: String -> IOError
+mkInvalidRecvArgError loc = ioeSetErrorString err "non-positive length"
+  where
+    err = mkIOError InvalidArgument loc Nothing Nothing
+
+recv :: Socket -> Int -> IO ByteString
+recv s siz
+  | siz < 0   = ioError (mkInvalidRecvArgError "Network.UDP.Recv.recv")
+  | otherwise = createUptoN siz $ \ptr -> recvBuf s ptr siz
+
+recvFrom :: Socket -> Int -> IO (ByteString, SockAddr)
+recvFrom s siz = do
+    bs@(PS fptr _ _) <- create siz $ \ptr -> zeroMemory ptr (fromIntegral siz)
+    withForeignPtr fptr $ \ptr -> do
+        (len, sa) <- recvBufFrom s ptr siz
+        let bs' | len < siz = PS fptr 0 len
+                | otherwise = bs
+        return (bs', sa)
+
+recvMsg :: Socket -> Int -> Int -> MsgFlag
+        -> IO (ByteString, SockAddr, [Cmsg], MsgFlag)
+recvMsg s siz clen flags = do
+    bs@(PS fptr _ _) <- create siz $ \ptr -> zeroMemory ptr (fromIntegral siz)
+    withForeignPtr fptr $ \ptr -> do
+        (addr,len,cmsgs,flags') <- recvBufMsg s [(ptr,siz)] clen flags
+        let bs' | len < siz = PS fptr 0 len
+                | otherwise = bs
+        return (bs', addr, cmsgs, flags')
diff --git a/Network/UDP/Types.hs b/Network/UDP/Types.hs
new file mode 100644
--- /dev/null
+++ b/Network/UDP/Types.hs
@@ -0,0 +1,14 @@
+module Network.UDP.Types where
+
+import Network.Socket
+
+properUDPSize :: Int
+properUDPSize = 2048
+
+properCMSGSize :: Int
+properCMSGSize = 64
+
+sockAddrFamily :: SockAddr -> Family
+sockAddrFamily SockAddrInet{}  = AF_INET
+sockAddrFamily SockAddrInet6{} = AF_INET6
+sockAddrFamily _               = error "sockAddrFamily"
diff --git a/network-udp.cabal b/network-udp.cabal
new file mode 100644
--- /dev/null
+++ b/network-udp.cabal
@@ -0,0 +1,34 @@
+name:                network-udp
+version:             0.0.0
+synopsis:            UDP library
+description:         Best current practice library for UDP clients and servers.
+license:             BSD3
+license-file:        LICENSE
+author:              Kazu Yamamoto
+maintainer:          kazu@iij.ad.jp
+-- copyright:
+category:            Network
+build-type:          Simple
+cabal-version:       >= 1.10
+
+----------------------------------------------------------------
+
+Source-Repository head
+  Type:                 git
+  Location:             https://github.com/kazu-yamamoto/network-udp
+
+----------------------------------------------------------------
+
+library
+  exposed-modules:     Network.UDP
+  other-modules:       Network.UDP.Recv
+                       Network.UDP.Types
+  build-depends:       base >= 4.9 && < 5
+                     , bytestring
+                     , containers
+                     , iproute
+                     , network
+  -- hs-source-dirs:
+  default-language:    Haskell2010
+  ghc-options:         -Wall
+  default-extensions:  Strict StrictData
