summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRoman Smrž <roman.smrz@seznam.cz>2023-03-26 21:34:44 +0200
committerRoman Smrž <roman.smrz@seznam.cz>2023-03-28 22:10:06 +0200
commitc9a90244a7b4f9c752541c5ff19616f7ff980ee4 (patch)
treeefe53e9eab497f446538c9171c77dd3a66468f3f /src
parenta76fa89bf612f39a053390dfe1c78ba1f9331bd8 (diff)
Network subnets and routing
Diffstat (limited to 'src')
-rw-r--r--src/Main.hs31
-rw-r--r--src/Network.hs76
-rw-r--r--src/Parser.hs7
-rw-r--r--src/Test.hs1
4 files changed, 96 insertions, 19 deletions
diff --git a/src/Main.hs b/src/Main.hs
index 0d8a3fd..b49fe09 100644
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -64,22 +64,28 @@ atomicallyTest act = do
Left e -> throwError e
Right x -> return x
-withNetwork :: (Network -> TestRun a) -> TestRun a
-withNetwork inner = do
+withInternet :: (Network -> TestRun a) -> TestRun a
+withInternet inner = do
testDir <- asks $ optTestDir . teOptions . fst
inet <- newInternet testDir
- let net = inetRoot inet
+ res <- withNetwork (inetRoot inet) $ \net -> do
+ local (fmap $ \s -> s { tsNetwork = net }) $ inner net
+ delInternet inet
+ return res
+withSubnet :: Network -> Maybe (TypedVarName Network) -> (Network -> TestRun a) -> TestRun a
+withSubnet parent tvname inner = do
+ net <- newSubnet parent (fromTypedVarName <$> tvname)
+ withNetwork net inner
+
+withNetwork :: Network -> (Network -> TestRun a) -> TestRun a
+withNetwork net inner = do
tcpdump <- liftIO (findExecutable "tcpdump") >>= return . \case
Just path -> withProcess (Left net) ProcNameTcpdump (Just softwareTermination)
- (path ++ " -i br0 -w '" ++ testDir ++ "/br0.pcap' -U -Z root") . const
+ (path ++ " -i br0 -w '" ++ netDir net ++ "/br0.pcap' -U -Z root") . const
Nothing -> id
- res <- tcpdump $ do
- local (fmap $ \s -> s { tsNetwork = net }) $ inner net
-
- delInternet inet
- return res
+ tcpdump $ inner net
withNode :: Expr Network -> Either (TypedVarName Node) (TypedVarName Process) -> (Node -> TestRun a) -> TestRun a
withNode netexpr tvname inner = do
@@ -156,6 +162,11 @@ evalSteps = mapM_ $ \case
forM_ value $ \i -> do
withVar name i $ evalSteps inner
+ Subnet name@(TypedVarName vname) parentExpr inner -> do
+ parent <- eval parentExpr
+ withSubnet parent (Just name) $ \net -> do
+ withVar vname net $ evalSteps inner
+
DeclNode name@(TypedVarName vname) net inner -> do
withNode net (Left name) $ \node -> do
withVar vname node $ evalSteps inner
@@ -243,7 +254,7 @@ runTest out opts test = do
oldHandler <- installHandler processStatusChanged (CatchInfo sigHandler) Nothing
res <- runExceptT $ flip runReaderT (tenv, tstate) $ fromTestRun $ do
- withNetwork $ \_ -> evalSteps (testSteps test)
+ withInternet $ \_ -> evalSteps (testSteps test)
void $ installHandler processStatusChanged oldHandler Nothing
diff --git a/src/Network.hs b/src/Network.hs
index 29621fc..ec0b380 100644
--- a/src/Network.hs
+++ b/src/Network.hs
@@ -9,6 +9,7 @@ module Network (
callOn,
newInternet, delInternet,
+ newSubnet,
newNode,
) where
@@ -27,6 +28,29 @@ import System.Process
import Test
+{-
+NETWORK STRUCTURE
+=================
+
+Local network (namespace "s<PREFIX>", e.g. "s1_2"):
+
+ (upstream, if any) (to subnets, if any and prefix length < 24)
+ ↑ veth_sX_1 (IP: prefix.1(.0)*.254)
+ veth0 veth_sX_2 (IP: prefix.2(.0)*.254) → veth0 in subnet namespace
+ | veth_sX_3 (IP: prefix.3(.0)*.254)
+ br0 (IP: prefix(.0)*.1/24) ...
+ / | \
+ veth2 ... veth253
+ ↓ ↓ ↓
+ (to nodes)
+
+Node (namespace "s<PREFIX>:<NODE>", e.g. "s1_2:p0"):
+
+ (upstream)
+ ↑
+ veth0 (IP: prefix.N/24)
+-}
+
data Internet = Internet
{ inetDir :: FilePath
, inetRoot :: Network
@@ -35,6 +59,7 @@ data Internet = Internet
data Network = Network
{ netPrefix :: [Word8]
, netNodes :: TVar [Node]
+ , netSubnets :: TVar [(Word8, Network)]
, netDir :: FilePath
}
@@ -77,7 +102,7 @@ callOn n cmd = callCommand $ T.unpack $ "ip netns exec \"" <> netnsName n <> "\"
instance ExprType Network where
textExprType _ = T.pack "network"
textExprValue n = "s:" <> textNetworkName n
- emptyVarValue = Network [] undefined undefined
+ emptyVarValue = Network [] undefined undefined undefined
instance ExprType Node where
textExprType _ = T.pack "node"
@@ -89,6 +114,9 @@ instance ExprType Node where
]
+nextPrefix :: [Word8] -> [Word8] -> Word8
+nextPrefix _ used = maximum (0 : used) + 1
+
makeIpAddress :: [Word8] -> Word8 -> Text
makeIpAddress prefix num = T.intercalate "." $ map (T.pack . show) $ prefix ++ replicate (3 - length prefix) 0 ++ [num]
@@ -105,46 +133,76 @@ delInternet :: MonadIO m => Internet -> m ()
delInternet _ = liftIO $ do
callCommand $ "ip -all netns delete"
+newSubnet :: MonadIO m => Network -> Maybe VarName -> m Network
+newSubnet net vname = do
+ sub <- liftIO $ atomically $ do
+ pref <- nextPrefix (netPrefix net) . map fst <$> readTVar (netSubnets net)
+ sub <- newNetwork
+ (netPrefix net ++ [pref])
+ (netDir net </> maybe (T.unpack $ netnsName net) (("sub_"++) . unpackVarName) vname)
+ modifyTVar (netSubnets net) ((pref, sub) :)
+ return sub
+ initNetwork sub
+ liftIO $ do
+ callOn net $ "ip link add \"veth_" <> netnsName sub <> "\" type veth peer name veth0 netns \"" <> netnsName sub <> "\""
+ callOn net $ "ip addr add dev \"veth_" <> netnsName sub <> "\" " <> makeIpAddress (netPrefix sub) 254 <> "/24"
+ callOn net $ "ip link set dev \"veth_" <> netnsName sub <> "\" up"
+
+ -- If the new subnet can be split further, routing rule for the whole prefix is needed
+ when (length (netPrefix sub) < 3) $ callOn net $ "ip route add "
+ <> makeIpAddress (netPrefix sub) 0 <> "/" <> (T.pack $ show $ length (netPrefix sub) * 8)
+ <> " via " <> makeIpAddress (netPrefix sub) 1
+ <> " dev \"veth_" <> netnsName sub
+ <> "\" src " <> makeIpAddress (netPrefix sub) 254
+
+ callOn sub $ "ip link set dev veth0 master br0 up"
+ callOn sub $ "ip route add default via " <> makeIpAddress (netPrefix sub) 254 <> " dev br0 src " <> makeIpAddress (netPrefix sub) 1
+ return sub
+
newNetwork :: [Word8] -> FilePath -> STM Network
newNetwork prefix dir = do
Network
<$> pure prefix
<*> newTVar []
+ <*> newTVar []
<*> pure dir
initNetwork :: MonadIO m => Network -> m ()
initNetwork net = liftIO $ do
+ createDirectoryIfMissing True $ netDir net
callCommand $ T.unpack $ "ip netns add \"" <> netnsName net <> "\""
callOn net $ "ip link add name br0 type bridge"
callOn net $ "ip addr add " <> makeIpAddress (netPrefix net) 1 <> " broadcast " <> makeIpAddress (netPrefix net) 255 <> " dev br0"
callOn net $ "ip link set dev br0 up"
callOn net $ "ip link set dev lo up"
+ callOn net $ "ip route add " <> makeIpAddress (netPrefix net) 0 <> "/24 dev br0 src " <> makeIpAddress (netPrefix net) 1
newNode :: MonadIO m => Network -> VarName -> m Node
newNode net vname = liftIO $ do
- node <- atomically $ do
+ (node, idx) <- atomically $ do
nodes <- readTVar (netNodes net)
let nname = nextNodeName vname $ map nodeName nodes
+ idx = fromIntegral $ 2 + length nodes
node = Node { nodeName = nname
- , nodeIp = makeIpAddress (netPrefix net) (fromIntegral $ 2 + length nodes)
+ , nodeIp = makeIpAddress (netPrefix net) idx
, nodeNetwork = net
, nodeDir = netDir net </> ("node_" ++ unpackNodeName nname)
}
writeTVar (netNodes net) (node : nodes)
- return node
-
- let name = textNodeName $ nodeName node
- dir = nodeDir node
+ return (node, idx)
+ let dir = nodeDir node
exists <- doesPathExist dir
when exists $ ioError $ userError $ dir ++ " exists"
createDirectoryIfMissing True dir
+ let veth = T.pack $ "veth" <> show idx
callCommand $ T.unpack $ "ip netns add \"" <> netnsName node <> "\""
- callOn net $ "ip link add \"veth_" <> name <> "\" type veth peer name veth0 netns \"" <> netnsName node <> "\""
- callOn net $ "ip link set dev \"veth_" <> name <> "\" master br0 up"
+ callOn net $ "ip link add " <> veth <> " type veth peer name veth0 netns \"" <> netnsName node <> "\""
+ callOn net $ "ip link set dev " <> veth <> " master br0 up"
callOn node $ "ip addr add " <> nodeIp node <> "/24 broadcast " <> makeIpAddress (netPrefix net) 255 <> " dev veth0"
callOn node $ "ip link set dev veth0 up"
callOn node $ "ip link set dev lo up"
+ callOn node $ "ip route add default via " <> makeIpAddress (netPrefix net) 1 <> " dev veth0 src " <> nodeIp node
return node
diff --git a/src/Parser.hs b/src/Parser.hs
index b79931b..aafba2c 100644
--- a/src/Parser.hs
+++ b/src/Parser.hs
@@ -630,6 +630,12 @@ testWith = do
modify $ \s -> s { testContext = ctx }
testBlock indent
+testSubnet :: TestParser [TestStep]
+testSubnet = command "subnet" $ Subnet
+ <$> param ""
+ <*> paramOrContext "of"
+ <*> innerBlock
+
testNode :: TestParser [TestStep]
testNode = command "node" $ DeclNode
<$> param ""
@@ -690,6 +696,7 @@ testStep = choice
, forStatement
, testLocal
, testWith
+ , testSubnet
, testNode
, testSpawn
, testSend
diff --git a/src/Test.hs b/src/Test.hs
index 6077b92..0d96902 100644
--- a/src/Test.hs
+++ b/src/Test.hs
@@ -36,6 +36,7 @@ data Test = Test
data TestStep = forall a. ExprType a => Let SourceLine (TypedVarName a) (Expr a) [TestStep]
| forall a. ExprType a => For SourceLine (TypedVarName a) (Expr [a]) [TestStep]
+ | Subnet (TypedVarName Network) (Expr Network) [TestStep]
| DeclNode (TypedVarName Node) (Expr Network) [TestStep]
| Spawn (TypedVarName Process) (Either (TypedVarName Node) (Either (Expr Network) (Expr Node))) [TestStep]
| Send (Expr Process) (Expr Text)