{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} import Codec.Archive.Tar(Archive, ArchiveMember(..), unarchive, regContents, linkTarget) import Control.Concurrent(threadDelay, forkIO) import Control.Concurrent.Async(Async, async, waitAny) import Control.Exception(SomeException, handle) import Control.Monad(void, forever, forM_, foldM) import qualified Data.ByteString as S import Data.ByteString(ByteString) import qualified Data.ByteString.Char8 as C import Data.ByteString.Unsafe(unsafePackCStringLen) import Data.Char(toLower) import qualified Data.Map.Strict as Map import Data.Serialize.Get(runGet, getWord32host) import Data.Text(pack, unpack) import Data.Text.Encoding(decodeUtf8) import Foreign.Ptr(castPtr, nullPtr) import Hans(newNetworkStack, addDevice, startDevice, processPackets, defaultConfig) import Hans.Device(Device(..), listDevices, defaultDeviceConfig) import Hans.IP4.Packet(IP4, showIP4) import Hans.IP4.Dhcp.Client(DhcpLease(..), DhcpConfig(..), dhcpClient, defaultDhcpConfig) import Hans.Addr(NetworkAddr, toAddr, showAddr) import Hans.Lens(view) import Hans.Socket(TcpListenSocket, sListen, sAccept, tcpRemoteAddr, defaultSocketConfig) import Hypervisor.Console(Console, initXenConsole, writeConsole) import Hypervisor.DomainInfo(domainModuleStart, domainModuleLength, DomainFlags(..), domainFlags) import Hypervisor.XenStore(initXenStore) import Network.HTTP(Request(..), Response(..), ResponseCode) import Network.HTTP.Headers(HeaderName(..), retrieveHeaders, mkHeader, hdrValue) import Network.HTTP.Stream(Stream(..), receiveHTTP, respondHTTP) import Network.HTTP.Streams() import Network.Mime(mimeByExt, defaultMimeMap, defaultMimeType) import Network.URI(URI(..)) main :: IO () main = do con <- initXenConsole handle (handleErr con "at top level") $ do disks <- getRamdisk con archive <- foldM (unarchiveAndMerge con) Map.empty disks xs <- initXenStore nics <- listDevices xs ns <- newNetworkStack defaultConfig devs <- mapM (\ n -> addDevice xs ns n defaultDeviceConfig) nics -- make hans go! void $ forkIO $ handle (handleErr con "packet processor") $ processPackets ns forM_ devs startDevice -- get our addresses and start our web servers writeConsole con ("Found " ++ show (length devs) ++ " devices.\n") leases <- mapM (\ d -> async $ handle (handleErr con "dhcp client") $ do writeConsole con ("Starting DHCP client for " ++ show (devMac d) ++ "\n") l <- dhcpClient ns (manyRetriesConfig con) d writeConsole con ("Picked up DHCP lease for " ++ show (devMac d) ++ "\n") return (l, d)) devs writeConsole con ("Generated " ++ show (length leases) ++ " leases.\n") processAsyncs leases $ \ (mlease, dev) -> case mlease of Nothing -> writeConsole con ("Device " ++ show (devMac dev) ++ " failed to get an address.\n") Just lease -> do let addr = dhcpAddr lease writeConsole con ("Starting listener on " ++ showIP4 addr "" ++ " (dev " ++ show (devMac dev) ++ ")\n") lsock <- sListen ns defaultSocketConfig addr 80 5 void $ forkIO $ handle (handleErr con ("on socket for " ++ showIP4 addr "")) $ do writeConsole con ("Started on "++showIP4 addr ""++":80\n") forever $ do sock <- sAccept (lsock :: TcpListenSocket IP4) let r = view tcpRemoteAddr sock handleClient con (handleReq con archive r) sock writeConsole con ("Known keys:\n") writeConsole con ("-----------------------------------------------\n") forM_ (Map.keys archive) (\ k -> writeConsole con (show k ++ "\n")) writeConsole con ("-----------------------------------------------\n") forever (threadDelay (15 * 1000 * 1000)) unarchiveAndMerge :: Console -> Archive -> (String, ByteString) -> IO Archive unarchiveAndMerge con acc (name, bstr) = case unarchive bstr of Left err -> do writeConsole con (show (S.length bstr) ++ " byte archive failed to expand: " ++ err ++ "\n") return acc Right archive -> do writeConsole con (show (S.length bstr) ++ " byte archive " ++ name ++ " unarchived.\n") return (archive `Map.union` acc) processAsyncs :: [Async a] -> (a -> IO ()) -> IO () processAsyncs [] _ = return () processAsyncs ls process = do (asyncx, x) <- waitAny ls process x processAsyncs (filter (/= asyncx) ls) process handleErr :: Console -> String -> SomeException -> IO a handleErr con place e = do writeConsole con ("Caught exception " ++ place ++ ": ") writeConsole con (show e ++ "\n") threadDelay (10 * 1000 * 1000) fail "handleErr" data Result = Result { _resCode :: ResponseCode , _resKind :: String , _resBody :: ByteString } handleReq :: NetworkAddr addr => Console -> Archive -> addr -> Request String -> IO Result handleReq con archive fromAddr req = go ("site" ++ uriPath (rqURI req)) (2,0,0) $ go ("site/404.html") (4,0,4) $ return (Result (4,0,4) "text/html" builtin404) where go "site/" code otherwiseDo = go "site/index.html" code otherwiseDo go key code otherwiseDo = putStrLn ("go " ++ key ++ " " ++ show code ++ "\n") >> case Map.lookup key archive of Just (RegularFileMember rfile) -> do let mimeType = mimeByExt defaultMimeMap defaultMimeType (pack key) mimeType' = unpack (decodeUtf8 mimeType) writeConsole con ("Request for " ++ key ++ " (" ++ mimeType' ++ ") from " ++ showAddr (toAddr fromAddr) "") return (Result code mimeType' (regContents rfile)) Just (LinkMember link) -> go (linkTarget link) code otherwiseDo Just (SymbolicLinkMember link) -> go (linkTarget link) code otherwiseDo Nothing -> putStrLn ("Nothing found. (" ++ key ++ ")") >> otherwiseDo _ -> otherwiseDo builtin404 :: ByteString builtin404 = "