diff options
| -rw-r--r-- | src/Network.hs | 55 | ||||
| -rw-r--r-- | src/Network/Ip.hs | 74 | 
2 files changed, 96 insertions, 33 deletions
| diff --git a/src/Network.hs b/src/Network.hs index e223277..247ef02 100644 --- a/src/Network.hs +++ b/src/Network.hs @@ -58,6 +58,7 @@ data Internet = Internet  data Network = Network      { netPrefix :: IpPrefix      , netNetns :: NetworkNamespace +    , netBridge :: Link Bridge      , netNodes :: TVar [Node]      , netSubnets :: TVar [(Word8, Network)]      , netDir :: FilePath @@ -98,7 +99,7 @@ instance HasNetns Node where getNetns = nodeNetns  instance ExprType Network where      textExprType _ = T.pack "network"      textExprValue n = "s:" <> textNetworkName (netPrefix n) -    emptyVarValue = Network (IpPrefix []) undefined undefined undefined undefined +    emptyVarValue = Network (IpPrefix []) undefined undefined undefined undefined undefined  instance ExprType Node where      textExprType _ = T.pack "node" @@ -115,12 +116,10 @@ nextPrefix _ used = maximum (0 : used) + 1  newInternet :: MonadIO m => FilePath -> m Internet  newInternet dir = do -    inet <- atomicallyWithIO $ do +    atomicallyWithIO $ do          Internet              <$> pure dir              <*> newNetwork (IpPrefix [1]) dir -    initNetwork $ inetRoot inet -    return inet  delInternet :: MonadIO m => Internet -> m ()  delInternet _ = liftIO $ do @@ -135,7 +134,6 @@ newSubnet net vname = do              (netDir net </> maybe (T.unpack $ textNetnsName $ getNetns net) (("sub_"++) . unpackVarName) vname)          lift $ modifyTVar (netSubnets net) ((idx, sub) :)          return (sub, idx) -    initNetwork sub      let lan = lanSubnet $ netPrefix sub          lanIp = IpAddress lan @@ -143,41 +141,40 @@ newSubnet net vname = do          router = lanIp 254      liftIO $ do -        let veth = T.pack $ "veth_s" <> show idx -        callOn net $ "ip link add " <> veth <> " type veth peer name veth0 netns \"" <> textNetnsName (getNetns sub) <> "\"" -        callOn net $ "ip addr add dev " <> veth <> " " <> textIpAddressCidr router -        callOn sub $ "ip link set dev veth0 master br0 up" -- this end needs to go up first, -                            -- otherwise it sometimes gets stuck with NO-CARRIER for a while. -        callOn net $ "ip link set dev " <> veth <> " up" +        (vethNet, vethSub) <- addVEth (net, "veth_s" <> T.pack (show idx)) (sub, "veth0") +        addAddress vethNet router +        setMaster vethSub (netBridge sub) -- this end needs to go up first, otherwise it +        linkUp    vethSub                 -- sometimes gets stuck with NO-CARRIER for a while. +        linkUp    vethNet          -- If the new subnet can be split further, routing rule for the whole prefix is needed          when (allowsSubnets (netPrefix sub)) $ callOn net $ "ip route add "              <> textIpNetwork (netPrefix sub)              <> " via " <> textIpAddress bridge -            <> " dev " <> veth +            <> " dev " <> linkName vethNet              <> " src " <> textIpAddress router          callOn sub $ "ip route add default via " <> textIpAddress router <> " dev br0 src " <> textIpAddress bridge      return sub  newNetwork :: IpPrefix -> FilePath -> WriterT [IO ()] STM Network  newNetwork prefix dir = do +    postpone $ createDirectoryIfMissing True dir + +    netns <- addNetworkNamespace ("s" <> textNetworkName prefix) +    bridge <- addBridge netns "br0" + +    addAddress bridge $ IpAddress (lanSubnet prefix) 1 +    linkUp $ bridge +    linkUp $ loopback netns +      Network          <$> pure prefix -        <*> addNetworkNamespace ("s" <> textNetworkName prefix) +        <*> pure netns +        <*> pure bridge          <*> lift (newTVar [])          <*> lift (newTVar [])          <*> pure dir -initNetwork :: MonadIO m => Network -> m () -initNetwork net = liftIO $ do -    let lan = lanSubnet $ netPrefix net -        lanIp = IpAddress lan -    createDirectoryIfMissing True $ netDir net -    callOn net $ "ip link add name br0 type bridge" -    callOn net $ "ip addr add " <> textIpAddressCidr (lanIp 1) <> " broadcast " <> textIpAddress (lanIp 255) <> " dev br0" -    callOn net $ "ip link set dev br0 up" -    callOn net $ "ip link set dev lo up" -  newNode :: MonadIO m => Network -> VarName -> m Node  newNode net vname = liftIO $ do      let lan = lanSubnet $ netPrefix net @@ -202,12 +199,12 @@ newNode net vname = liftIO $ do      when exists $ ioError $ userError $ dir ++ " exists"      createDirectoryIfMissing True dir -    let veth = T.pack $ "veth" <> show idx -    callOn net  $ "ip link add " <> veth <> " type veth peer name veth0 netns \"" <> textNetnsName (getNetns node) <> "\"" -    callOn net  $ "ip link set dev " <> veth <> " master br0 up" -    callOn node $ "ip addr add " <> textIpAddressCidr (nodeIp node) <> " broadcast " <> textIpAddress (lanIp 255) <> " dev veth0" -    callOn node $ "ip link set dev veth0 up" -    callOn node $ "ip link set dev lo up" +    (vethNet, vethNode) <- addVEth (net, "veth" <> T.pack (show idx)) (node, "veth0") +    setMaster vethNet $ netBridge net +    linkUp vethNet +    addAddress vethNode $ nodeIp node +    linkUp $ vethNode +    linkUp $ loopback node      callOn node $ "ip route add default via " <> textIpAddress (lanIp 1) <> " dev veth0 src " <> textIpAddress (nodeIp node)      return node diff --git a/src/Network/Ip.hs b/src/Network/Ip.hs index 1365f66..13fc284 100644 --- a/src/Network/Ip.hs +++ b/src/Network/Ip.hs @@ -10,14 +10,22 @@ module Network.Ip (      ipSubnet,      lanSubnet, +    MonadPIO(..), +      NetworkNamespace,      HasNetns(..),      addNetworkNamespace,      textNetnsName,      callOn, -) where -import Control.Concurrent.STM +    Link(..), +    Loopback, loopback, +    VEth, addVEth, +    Bridge, addBridge, +    addAddress, +    setMaster, +    linkUp, linkDown, +) where  import Control.Monad.Writer @@ -56,16 +64,27 @@ lanSubnet :: IpPrefix -> IpPrefix  lanSubnet (IpPrefix prefix) = IpPrefix (take 3 $ prefix ++ repeat 0) +class Monad m => MonadPIO m where +    postpone :: IO () -> m () + +instance MonadPIO IO where +    postpone = id + +instance Monad m => MonadPIO (WriterT [IO ()] m) where +    postpone = tell . (:[]) + +  newtype NetworkNamespace = NetworkNamespace      { netnsName :: Text      }      deriving (Eq, Ord)  class HasNetns a where getNetns :: a -> NetworkNamespace +instance HasNetns NetworkNamespace where getNetns = id -addNetworkNamespace :: Text -> WriterT [IO ()] STM NetworkNamespace +addNetworkNamespace :: MonadPIO m => Text -> m NetworkNamespace  addNetworkNamespace name = do -    tell $ (:[]) $ callCommand $ T.unpack $ "ip netns add \"" <> name <> "\"" +    postpone $ callCommand $ T.unpack $ "ip netns add \"" <> name <> "\""      return $ NetworkNamespace          { netnsName = name          } @@ -76,3 +95,50 @@ textNetnsName = netnsName  callOn :: HasNetns a => a -> Text -> IO ()  callOn n cmd = callCommand $ T.unpack $ "ip netns exec \"" <> ns <> "\" " <> cmd      where NetworkNamespace ns = getNetns n + + +data Link a = Link +    { linkName :: Text +    , linkNetns :: NetworkNamespace +    } + +instance HasNetns (Link a) where getNetns = linkNetns + +data Loopback + +loopback :: HasNetns n => n -> Link Loopback +loopback = Link "lo" . getNetns + +data VEth + +addVEth :: (HasNetns n, HasNetns n', MonadPIO m) => (n, Text) -> (n', Text) -> m (Link VEth, Link VEth) +addVEth (netns, name) (netns', name') = do +    postpone $ callOn netns $ "ip link add \"" <> name <> "\" type veth peer name \"" <> name' <> "\" netns \"" <> textNetnsName (getNetns netns') <> "\"" +    return $ (,) +        (Link name  $ getNetns netns ) +        (Link name' $ getNetns netns') + +data Bridge + +addBridge :: (HasNetns n, MonadPIO m) => n -> Text -> m (Link Bridge) +addBridge netns name = do +    postpone $ callOn netns $ "ip link add name \"" <> name <> "\" type bridge" +    return $ Link name $ getNetns netns + +addAddress :: MonadPIO m => Link a -> IpAddress -> m () +addAddress link addr@(IpAddress prefix _) = do +    let bcast = IpAddress prefix 255 +    postpone $ callOn link $ "ip addr add " <> textIpAddressCidr addr <> " broadcast " <> textIpAddress bcast <> " dev \"" <> linkName link <> "\"" + +setMaster :: (MonadPIO m, MonadFail m) => Link a -> Link Bridge -> m () +setMaster link bridge = do +    when (getNetns link /= getNetns bridge) $ fail "link and bridge in different network namespaces" +    postpone $ callOn link $ "ip link set dev \"" <> linkName link <> "\" master \"" <> linkName bridge <> "\"" + +linkUp :: MonadPIO m => Link a -> m () +linkUp link = do +    postpone $ callOn link $ "ip link set dev \"" <> linkName link <> "\" up" + +linkDown :: MonadPIO m => Link a -> m () +linkDown link = do +    postpone $ callOn link $ "ip link set dev \"" <> linkName link <> "\" down" |