diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2010, Jacob Stanley
+
+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 Jacob Stanley 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/cbits/network-linux.c b/cbits/network-linux.c
new file mode 100644
--- /dev/null
+++ b/cbits/network-linux.c
@@ -0,0 +1,79 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include <ifaddrs.h>
+#include <netdb.h>
+#include <netpacket/packet.h>
+
+#include "network.h"
+#include "common.h"
+
+
+void maccopy(unsigned char *dst, struct sockaddr *addr)
+{
+    /* TODO check that sll_halen is equal to 6 (MAC_SIZE) */
+    memcpy(dst, ((struct sockaddr_ll *)addr)->sll_addr, MAC_SIZE);
+}
+
+struct network_interface *add_interface(struct network_interface *ns, const wchar_t *name, int max_ns)
+{
+    int i;
+    for (i = 0; i < max_ns; i++) {
+        if (wcsempty(ns[i].name)) {
+            wszcopy(ns[i].name, name, NAME_SIZE);
+            return &ns[i];
+        } else if (wcscmp(ns[i].name, name) == 0) {
+            return &ns[i];
+        }
+    }
+    return NULL;
+}
+
+int count_interfaces(struct network_interface *ns, int max_ns)
+{
+    int i;
+    for (i = 0; i < max_ns; i++) {
+        if (wcsempty(ns[i].name)) {
+            break;
+        }
+    }
+    return i;
+}
+
+int c_get_network_interfaces(struct network_interface *ns, int max_ns)
+{
+    struct network_interface *n;
+    struct ifaddrs *ifaddr, *ifa;
+    struct sockaddr *addr;
+    wchar_t name[NAME_SIZE];
+    int family, error;
+   
+    error = getifaddrs(&ifaddr);
+    if (error != 0) {
+        perror("getifaddrs");
+        return 0;
+    }
+
+    memset(ns, 0, sizeof(struct network_interface) * max_ns);
+
+    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+        mbswszcopy(name, ifa->ifa_name, NAME_SIZE);
+        addr = ifa->ifa_addr;
+        family = addr->sa_family;
+
+        n = add_interface(ns, name, max_ns);
+
+        if (family == AF_INET) {
+            ipv4copy(&n->ip_address, addr);
+        } else if (family == AF_INET6) {
+            ipv6copy(&n->ip6_address, addr);
+        } else if (family == AF_PACKET) {
+            maccopy(n->mac_address, addr);
+        }
+    }
+
+    freeifaddrs(ifaddr);
+    return count_interfaces(ns, max_ns);
+}
diff --git a/cbits/network-windows.c b/cbits/network-windows.c
new file mode 100644
--- /dev/null
+++ b/cbits/network-windows.c
@@ -0,0 +1,67 @@
+#define _WIN32_WINNT 0x0501
+
+#include <stdio.h>
+
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <iptypes.h>
+#include <iphlpapi.h>
+
+#include "network.h"
+#include "common.h"
+
+
+int get_adapters_addresses(IP_ADAPTER_ADDRESSES *adapters, ULONG *size)
+{
+    return GetAdaptersAddresses(AF_UNSPEC, 0, 0, adapters, size);
+}
+
+int c_get_network_interfaces(struct network_interface *ns, int max_ns)
+{
+    struct sockaddr *addr;
+    IP_ADAPTER_ADDRESSES *adapters, *adapter;
+    IP_ADAPTER_UNICAST_ADDRESS *unicast;
+    ULONG buffer_size;
+    DWORD error;
+    int family, i;
+
+    /* make an initial call to get the necessary
+     * size into the buffer_size variable */
+    error = get_adapters_addresses(NULL, &buffer_size);
+
+    if (error != ERROR_BUFFER_OVERFLOW) {
+        /* if we didn't get ERROR_BUFFER_OVERFLOW
+         * then buffer_size was not set */
+        return 0;
+    }
+
+    adapters = malloc(buffer_size);
+    error = get_adapters_addresses(adapters, &buffer_size);
+    i = 0;
+
+    if (error == NO_ERROR) {
+        adapter = adapters;
+
+        while (i < max_ns && adapter) {
+            wszcopy(ns[i].name, adapter->FriendlyName, NAME_SIZE);
+            memcpy(ns[i].mac_address, adapter->PhysicalAddress, MAC_SIZE);
+
+            for (unicast = adapter->FirstUnicastAddress; unicast; unicast = unicast->Next) {
+                addr = unicast->Address.lpSockaddr;
+                family = addr->sa_family;
+
+                if (family == AF_INET) {
+                    ipv4copy(&ns[i].ip_address, addr);
+                } else if (family == AF_INET6) {
+                    ipv6copy(&ns[i].ip6_address, addr);
+                }
+            }
+
+            i++;
+            adapter = adapter->Next;
+        }
+    }
+
+    free(adapters);
+    return i;
+}
diff --git a/network-info.cabal b/network-info.cabal
new file mode 100644
--- /dev/null
+++ b/network-info.cabal
@@ -0,0 +1,56 @@
+Name:                network-info
+version:             0.1
+synopsis:            Access the local computer's basic network configuration
+description:         This library provides simple read-only access to the local computer's
+                     networking configuration. It is currently capable of getting a list of
+                     all the network interfaces and their respective IPv4, IPv6 and MAC
+                     addresses.
+                     .
+                     The executable 'test-network-info' will only be built if the flag 'test'
+                     has been set.
+homepage:            http://github.com/jystic/network-info
+license:             BSD3
+license-file:        LICENSE
+author:              Jacob Stanley
+maintainer:          jacob@stanley.io
+category:            Network
+build-type:          Simple
+cabal-version:       >=1.8.0.4
+
+source-repository head
+  type:     git
+  location: git://github.com/jystic/network-info.git
+
+Flag test
+  description: Build test program
+  default:     False
+
+Library
+  hs-source-dirs: src
+  include-dirs: cbits
+  cc-options: -Wall -Werror
+
+  exposed-modules: Network.Info
+  
+  build-depends:
+    base == 4.*
+
+  if os(linux)
+    c-sources: cbits/network-linux.c
+  else
+    if os(windows)
+      c-sources: cbits/network-windows.c
+      extra-libraries: iphlpapi
+    else
+      buildable: False
+
+Executable test-network-info
+  hs-source-dirs: test
+  main-is: Main.hs
+
+  if flag(test)
+    build-depends:
+      base == 4.*,
+      network-info
+  else
+    buildable: False
diff --git a/src/Network/Info.hsc b/src/Network/Info.hsc
new file mode 100644
--- /dev/null
+++ b/src/Network/Info.hsc
@@ -0,0 +1,188 @@
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+module Network.Info (
+    getNetworkInterfaces,
+    NetworkInterface (..),
+    IPv4 (..),
+    IPv6 (..),
+    MAC (..),
+) where
+
+import Data.Bits ((.&.), shiftR, shiftL)
+import Data.List (intersperse)
+import Data.Word
+import Foreign.C.String
+import Foreign.C.Types
+import Foreign.Marshal.Array
+import Foreign.Ptr
+import Foreign.Storable
+import Numeric (showHex)
+import Text.Printf
+
+
+----------------------------------------------------------------------
+-- FFI
+----------------------------------------------------------------------
+
+#include "network.h"
+
+foreign import ccall unsafe "c_get_network_interfaces"
+        c_get_network_interfaces :: Ptr NetworkInterface -> CInt -> IO CInt
+
+#let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__)
+
+
+----------------------------------------------------------------------
+-- Network interfaces
+----------------------------------------------------------------------
+
+-- | Describes the basic configuration of a network interface. /This/
+--   /definition is currently limited to just one address per family./
+data NetworkInterface = NetworkInterface
+    { name :: String -- ^ Interface name (e.g. \"eth0\", \"lo\", \"Local Area Connection\")
+    , ipv4 :: IPv4   -- ^ IPv4 address
+    , ipv6 :: IPv6   -- ^ IPv6 address
+    , mac  :: MAC    -- ^ MAC address
+    } deriving (Show)
+
+instance Storable NetworkInterface where
+    alignment _ = #alignment struct network_interface
+    sizeOf _    = #size struct network_interface
+    peek ptr    = do
+        name <- peekCWString $ (#ptr struct network_interface, name) ptr
+        ipv4 <- (#peek struct network_interface, ip_address) ptr
+        ipv6 <- (#peek struct network_interface, ip6_address) ptr
+        mac  <- (#peek struct network_interface, mac_address) ptr
+        return $ NetworkInterface name ipv4 ipv6 mac
+
+
+-- | Gets the address information for each of the network interfaces on
+--   the local computer.
+getNetworkInterfaces :: IO [NetworkInterface]
+getNetworkInterfaces =
+    allocaArray 64 $ \ptr -> do
+    count <- c_get_network_interfaces ptr 64
+    peekArray (fromIntegral count) ptr
+
+
+----------------------------------------------------------------------
+-- IPv4 addresses
+----------------------------------------------------------------------
+
+-- | Represents an IPv4 address (e.g. @172.23.21.1@, @127.0.0.1@)
+data IPv4 = IPv4
+    {-# UNPACK #-} !Word32
+    deriving (Eq, Ord, Bounded)
+
+instance Show IPv4 where
+    show = showIPv4
+
+instance Storable IPv4 where
+    alignment _ = 1
+    sizeOf _    = 4
+    peek p      = do
+        ip <- peek (castPtr p)
+        return (IPv4 ip)
+    poke p (IPv4 ip) =
+        poke (castPtr p) ip
+
+
+----------------------------------------------------------------------
+-- IPv6 addresses
+----------------------------------------------------------------------
+
+-- | Represents an IPv6 address (e.g. @2001:db8:85a3::8a2e:370:7334@, @::1@)
+data IPv6 = IPv6
+    {-# UNPACK #-} !Word32
+    {-# UNPACK #-} !Word32
+    {-# UNPACK #-} !Word32
+    {-# UNPACK #-} !Word32
+    deriving (Eq, Ord, Bounded)
+
+-- | Not yet capable of collapsing groups of zeros, will still
+--   generate valid addresses however.
+instance Show IPv6 where
+    show = showIPv6
+
+instance Storable IPv6 where
+    alignment _ = 1
+    sizeOf _    = 16
+    peek p      = do
+        let ptr = castPtr p
+        a <- peekElemOff ptr 0
+        b <- peekElemOff ptr 1
+        c <- peekElemOff ptr 2
+        d <- peekElemOff ptr 3
+        return $ IPv6 a b c d
+    poke p (IPv6 a b c d) = do
+        let ptr = castPtr p
+        pokeElemOff ptr 0 a
+        pokeElemOff ptr 1 b
+        pokeElemOff ptr 2 c
+        pokeElemOff ptr 3 d
+
+
+----------------------------------------------------------------------
+-- MAC addresses
+----------------------------------------------------------------------
+
+-- | Represents a MAC address (e.g. @01:23:45:67:89:ab@)
+data MAC = MAC
+    {-# UNPACK #-} !Word8
+    {-# UNPACK #-} !Word8
+    {-# UNPACK #-} !Word8
+    {-# UNPACK #-} !Word8
+    {-# UNPACK #-} !Word8
+    {-# UNPACK #-} !Word8
+    deriving (Eq, Ord, Bounded)
+
+instance Show MAC where
+    show (MAC a b c d e f) = printf "%02x:%02x:%02x:%02x:%02x:%02x" a b c d e f
+
+instance Storable MAC where
+    alignment _ = 1
+    sizeOf _    = 6
+    peek p      = do
+        a <- peek $ castPtr p
+        b <- peekByteOff p 1
+        c <- peekByteOff p 2
+        d <- peekByteOff p 3
+        e <- peekByteOff p 4
+        f <- peekByteOff p 5
+        return $ MAC a b c d e f
+    poke p (MAC a b c d e f) = do
+        poke (castPtr p) a
+        pokeByteOff p 1 b
+        pokeByteOff p 2 c
+        pokeByteOff p 3 d
+        pokeByteOff p 4 e
+        pokeByteOff p 5 f
+
+
+----------------------------------------------------------------------
+-- Helper functions
+----------------------------------------------------------------------
+
+showIPv4 :: IPv4 -> String
+showIPv4 (IPv4 ip) = concat . intersperse "." $ showOctets
+  where
+    showOctets = map show $ word8s ip
+
+-- TODO: drop out consecutive zeros
+showIPv6 :: IPv6 -> String
+showIPv6 (IPv6 a b c d) = (concat . intersperse ":") groups
+  where
+    groups = map (flip showHex "")  $ concatMap (group . word8s) [a,b,c,d]
+
+word8s :: Word32 -> [Word8]
+word8s x = [ fromIntegral $ x
+           , fromIntegral $ x `shiftR` 8
+           , fromIntegral $ x `shiftR` 16
+           , fromIntegral $ x `shiftR` 24 ]
+
+group :: [Word8] -> [Word16]
+group = map2 $ \x y -> (fromIntegral x) `shiftL` 8 + (fromIntegral y)
+
+map2 :: (a -> a -> b) -> [a] -> [b]
+map2 _ [] = []
+map2 f (x:y:zs) = f x y : map2 f zs
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,13 @@
+module Main (main) where
+
+import Network.Info
+
+main = do
+    ns <- getNetworkInterfaces
+    mapM (putStrLn . showInterface) ns
+
+showInterface :: NetworkInterface -> String
+showInterface n = name n ++ "\n"
+               ++ "IPv4 Address: " ++ show (ipv4 n) ++ "\n"
+               ++ "IPv6 Address: " ++ show (ipv6 n) ++ "\n"
+               ++ "MAC Address:  " ++ show (mac n) ++ "\n"
