From 66294ff5ff89c672beaff4134ea9601e02f31956 Mon Sep 17 00:00:00 2001
From: jjm2473 <1129525450@qq.com>
Date: Sat, 17 Aug 2019 22:59:36 +0800
Subject: [PATCH] working code
---
.gitignore | 3 +
.idea/artifacts/vnet_lab_jar.xml | 10 +
.idea/compiler.xml | 13 ++
.idea/encodings.xml | 6 +
.idea/misc.xml | 14 ++
.idea/vcs.xml | 6 +
README.md | 163 ++++++++++++++++
docs/example.json | 106 ++++++++++
pom.xml | 42 ++++
scripts/netenter | 16 ++
.../java/com/github/jjm2473/vnet/Main.java | 24 +++
.../java/com/github/jjm2473/vnet/VNet.java | 181 ++++++++++++++++++
.../com/github/jjm2473/vnet/model/Host.java | 11 ++
.../com/github/jjm2473/vnet/model/Link.java | 10 +
.../github/jjm2473/vnet/model/LinkPeer.java | 23 +++
.../com/github/jjm2473/vnet/model/Net.java | 22 +++
.../com/github/jjm2473/vnet/model/Node.java | 11 ++
.../com/github/jjm2473/vnet/model/Root.java | 15 ++
.../com/github/jjm2473/vnet/model/Switch.java | 7 +
src/main/resources/META-INF/MANIFEST.MF | 3 +
.../com/github/jjm2473/vnet/VNetTest.java | 20 ++
src/test/resources/test.json | 106 ++++++++++
vnet-lab.iml | 2 +
23 files changed, 814 insertions(+)
create mode 100644 .gitignore
create mode 100644 .idea/artifacts/vnet_lab_jar.xml
create mode 100644 .idea/compiler.xml
create mode 100644 .idea/encodings.xml
create mode 100644 .idea/misc.xml
create mode 100644 .idea/vcs.xml
create mode 100644 README.md
create mode 100644 docs/example.json
create mode 100644 pom.xml
create mode 100755 scripts/netenter
create mode 100644 src/main/java/com/github/jjm2473/vnet/Main.java
create mode 100644 src/main/java/com/github/jjm2473/vnet/VNet.java
create mode 100644 src/main/java/com/github/jjm2473/vnet/model/Host.java
create mode 100644 src/main/java/com/github/jjm2473/vnet/model/Link.java
create mode 100644 src/main/java/com/github/jjm2473/vnet/model/LinkPeer.java
create mode 100644 src/main/java/com/github/jjm2473/vnet/model/Net.java
create mode 100644 src/main/java/com/github/jjm2473/vnet/model/Node.java
create mode 100644 src/main/java/com/github/jjm2473/vnet/model/Root.java
create mode 100644 src/main/java/com/github/jjm2473/vnet/model/Switch.java
create mode 100644 src/main/resources/META-INF/MANIFEST.MF
create mode 100644 src/test/java/com/github/jjm2473/vnet/VNetTest.java
create mode 100644 src/test/resources/test.json
create mode 100644 vnet-lab.iml
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9509d5c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+target
+out
+.idea
\ No newline at end of file
diff --git a/.idea/artifacts/vnet_lab_jar.xml b/.idea/artifacts/vnet_lab_jar.xml
new file mode 100644
index 0000000..eeb80ac
--- /dev/null
+++ b/.idea/artifacts/vnet_lab_jar.xml
@@ -0,0 +1,10 @@
+
+
+ $PROJECT_DIR$/out/artifacts/vnet_lab_jar
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..3324bbb
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..c2bae49
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..4b661a5
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..cf0194f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,163 @@
+# Virtual Network Laboratory
+
+基于Linux网络命名空间的虚拟网络实验室,
+可根据[蓝图](#蓝图)创建一个虚拟的网络.
+
+### 常规工作流
+1. 首先根据需求编写[蓝图](#蓝图)文件, 假设保存到`blueprint.json`
+2. 把蓝图编译成shell脚本([下载vnet-lab.jar](https://github.com/jjm2473/virtual-network-laboratory/releases)):
+ ```shell script
+ java -jar vnet-lab.jar blueprint.json > network.sh && chmod 755 network.sh
+ ```
+3. 创建虚拟网络:
+ ```shell script
+ sudo ./network.sh create
+ ```
+4. 创建虚拟网络之后, 即可进入各个Host(即命名空间)进行网络实验,
+ 为了方便进入不同Host, 提供了脚本 [scripts/netenter](scripts/netenter),
+ 假设要进入`pc1`, 执行:
+ ```shell script
+ ./scripts/netenter pc1
+ ```
+ `netenter`还能直接在指定的Host执行指定的命令, 例如在`pc1`执行`ping 8.8.8.8`:
+ ```shell script
+ ./scripts/netenter pc1 ping 8.8.8.8
+ ```
+5. 销毁虚拟网络(或者直接重启机器):
+ ```shell script
+ sudo ./network.sh destory
+ ```
+
+### 蓝图
+[蓝图示例](docs/example.json)
+
+蓝图中有三种角色, 分别是:
+
+1. 主机(hosts)
+ > 主机对应Linux中一个网络命名空间, 主机同时也可能是路由器, 看你怎么配置.
+ 主机有两个属性:
+ 1. name: 主机的名称, 即命名空间的名称, 不可重复
+ 2. gateway: 默认网关, 可不指定
+2. 交换机(switches)
+ > 交换机对应一个虚拟bridge设备, 交换机只要一个属性:
+ 1. name: 交换机的名称, 不可重复
+3. 链路(links)
+ > 链路对应一对veth设备, 相当于网线, 用于连接主机或者交换机.
+ 链路有两个成员, 分别是from 和 to, 代表连接的两个节点, 不分先后,
+ from 和 to各有三个属性:
+ 1. peer: 主机或者交换机节点的名称
+ 2. if: 此端口在节点中的接口名称, 可以不指定, 建议交换机节点别配置, 以免重名
+ 3. ipv4: 配置此端口的ipv4地址, 可以不指定, 连接到交换机的端口忽略此参数
+
+需要注意的是交换机和连接到交换机的端口都在宿主机的根命名空间里, 所以交换机的名称不能重复,
+连接到所有交换机的所有端口的接口名称也不能重复.
+
+### 案例
+要实现的网络拓扑:
+```text
+ 宿主机
+ |
+ 路由器(router1)
+ / \
+ 交换机1 交换机2
+ / \ / \
+ pc1 pc2 pc3 pc4
+```
+~ ___宿主机___ 即当前运行Linux的机器, 蓝图中用`root`表示;
+
+~ ___路由器___ 并非现代意义上的路由器, 而是一台普通的主机, 跟 ___交换机1___ 和 ___交换机2___ 合在一起更像是现代的路由器;
+
+此网络拓扑对应的蓝图文件是 [docs/example.json](docs/example.json).
+
+最终要实现的是pc之间互通, pc可以连接外网.
+
+编译蓝图:
+```shell script
+java -jar vnet-lab.jar docs/example.json > example.sh && chmod 755 example.sh
+```
+之后的命令都在Linux上执行;
+
+创建虚拟网络:
+```shell script
+sudo ./example.sh create
+```
+创建完之后可以执行`ip netns`查看有哪些主机, 这个案例中会输出:
+```text
+pc4 (id: 3)
+pc3 (id: 2)
+pc2 (id: 1)
+pc1 (id: 0)
+router1 (id: 4)
+```
+
+#### pc之间的连通
+在`pc1`中ping `pc2`:
+```shell script
+./scripts/netenter pc1 ping 192.168.65.2
+```
+`pc1`和`pc2`在同一个交换机下, 可以ping通;
+
+再试试ping `pc3`:
+```shell script
+./scripts/netenter pc1 ping 192.168.66.1
+```
+这个时候能不能ping通取决于router1上的路由转发有没有打开:
+```shell script
+./scripts/netenter router1 cat /proc/sys/net/ipv4/ip_forward
+```
+如果输出1, 说明路由转发已经打开, 那前面的ping应该也是成功的;
+如果输出0, 说明路由转发没打开, 可以通过这个命令打开:
+```shell script
+echo 'echo "1">/proc/sys/net/ipv4/ip_forward' | ./scripts/netenter router1
+```
+现在`pc1`到`pc4`的网络可以互通了;
+
+#### pc到路由器的连通
+现在试试ping路由器`router1`的外网端口`eth0`:
+```shell script
+./scripts/netenter pc1 ping 192.168.64.1
+```
+可以ping通;
+
+#### pc到宿主机的连通
+试试ping宿主机的端口`vroute1`:
+```shell script
+./scripts/netenter pc1 ping 192.168.64.254
+```
+发现ping不通;
+同样宿主机也无法ping通`pc1`
+(如果ping通了, 也许是因为宿主机的网关那边有相同的ip, 或者因为宿主机能自己学习路由表, 例如安装了OSPF相关软件):
+```shell script
+ping 192.168.65.1
+```
+ping不通的原因是宿主机不知道192.168.65.1在哪,
+要让pc到宿主机连通有两个方案, 分别是NAT和路由表:
+- NAT方案是在router1上开启NAT, 将pc的地址伪装成192.168.64.1, 再跟宿主机通信,
+这个方案下, 宿主机不知道pc的存在, router1上还能配置防火墙规则禁止宿主机主动连接pc,
+就跟我们平时用的路由器那样;
+- 路由表方案是在宿主机上增加路由规则, 让宿主机知道pc在vroute1端口下,
+这个方案下宿主机和pc可以直接互联, 如果我们把宿主机当成ISP, 那pc就相当于有了公网IP.
+
+为了简单, 选择路由表方案, 直接在宿主机增加一条路由规则:
+```shell script
+sudo ip route add 192.168.64.0/18 via 192.168.64.1
+```
+(`192.168.64.0/18`这个网段包含了`192.168.65.1`)
+
+现在`ping 192.168.65.1`, `./scripts/netenter pc1 ping 192.168.64.254`都能通了.
+
+#### pc到外网
+首先打开宿主机的路由转发:
+```shell script
+sudo sh -c 'echo "1">/proc/sys/net/ipv4/ip_forward'
+```
+这个时候尝试ping外网地址`./scripts/netenter pc1 ping 114.114.114.114`, 还是ping不通,
+原因跟上一节一样, 解决方案也是一样, 但是我们不应该改宿主机上级路由的路由表, 所以只能用NAT方案.
+假设宿主机的外网端口是`eth0`, 那么执行这条命令增加NAT:
+```shell script
+sudo iptables -t nat -A POSTROUTING -s 192.168.64.0/18 -o eth0 -j MASQUERADE
+```
+再来一次`./scripts/netenter pc1 ping 114.114.114.114`,
+好耶, pc可以访问外网了! (如果pc还是上不了网, 是不是因为宿主机本来就不能上网??)
+
+本例完结, 其实宿主机也被当成路由器了.
\ No newline at end of file
diff --git a/docs/example.json b/docs/example.json
new file mode 100644
index 0000000..9d25207
--- /dev/null
+++ b/docs/example.json
@@ -0,0 +1,106 @@
+{
+ "hosts": [
+ {
+ "name": "router1",
+ "gateway": "192.168.64.254"
+ },
+ {
+ "name": "pc1",
+ "gateway": "192.168.65.254"
+ },
+ {
+ "name": "pc2",
+ "gateway": "192.168.65.254"
+ },
+ {
+ "name": "pc3",
+ "gateway": "192.168.66.254"
+ },
+ {
+ "name": "pc4",
+ "gateway": "192.168.66.254"
+ }
+ ],
+ "switches": [
+ {
+ "name": "vsw1"
+ },
+ {
+ "name": "vsw2"
+ }
+ ],
+ "links": [
+ {
+ "from": {
+ "peer": "pc1",
+ "if": "eth0",
+ "ipv4": "192.168.65.1/24"
+ },
+ "to": {
+ "peer": "vsw1"
+ }
+ },
+ {
+ "from": {
+ "peer": "pc2",
+ "if": "eth0",
+ "ipv4": "192.168.65.2/24"
+ },
+ "to": {
+ "peer": "vsw1"
+ }
+ },
+ {
+ "from": {
+ "peer": "pc3",
+ "if": "eth0",
+ "ipv4": "192.168.66.1/24"
+ },
+ "to": {
+ "peer": "vsw2"
+ }
+ },
+ {
+ "from": {
+ "peer": "pc4",
+ "if": "eth0",
+ "ipv4": "192.168.66.2/24"
+ },
+ "to": {
+ "peer": "vsw2"
+ }
+ },
+ {
+ "from": {
+ "peer": "router1",
+ "if": "br-lan0",
+ "ipv4": "192.168.65.254/24"
+ },
+ "to": {
+ "peer": "vsw1"
+ }
+ },
+ {
+ "from": {
+ "peer": "router1",
+ "if": "br-lan1",
+ "ipv4": "192.168.66.254/24"
+ },
+ "to": {
+ "peer": "vsw2"
+ }
+ },
+ {
+ "from": {
+ "peer": "router1",
+ "if": "eth0",
+ "ipv4": "192.168.64.1/24"
+ },
+ "to": {
+ "peer": "root",
+ "if": "vroute1",
+ "ipv4": "192.168.64.254/24"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..9cb4ddc
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,42 @@
+
+
+ 4.0.0
+
+ github.jjm2473
+ vnet-lab
+ 1.0-SNAPSHOT
+
+
+
+ com.alibaba
+ fastjson
+ 1.2.50
+
+
+ commons-io
+ commons-io
+ 2.6
+
+
+ org.testng
+ testng
+ 6.14.3
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 7
+
+
+
+
+
\ No newline at end of file
diff --git a/scripts/netenter b/scripts/netenter
new file mode 100755
index 0000000..1ce9346
--- /dev/null
+++ b/scripts/netenter
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+# this is a script helps you to enter a network namespace
+# dependency: ip unshare
+
+ns=$1
+shift
+prog=${1:-bash}
+shift
+
+if [ -z "$ns" ]; then
+ echo "Usage: $0 NETNS [COMMAND [ARGS...]]"
+ exit -1
+fi
+
+sudo ip netns exec "$ns" unshare -u sh -c 'hostname $0;exec $*' "$ns" "$prog" $*
diff --git a/src/main/java/com/github/jjm2473/vnet/Main.java b/src/main/java/com/github/jjm2473/vnet/Main.java
new file mode 100644
index 0000000..82dc7b3
--- /dev/null
+++ b/src/main/java/com/github/jjm2473/vnet/Main.java
@@ -0,0 +1,24 @@
+package com.github.jjm2473.vnet;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.io.FileUtils;
+
+/**
+ * 主程序
+ */
+public class Main {
+ public static void main(String[] args) throws IOException {
+ if (args.length != 1 || "-h".equals(args[0]) || "--help".equals(args[0]) || "--usage".equals(args[0])) {
+ System.err.println("Convert network blueprint to shell script\n"
+ + "Usage: "+Main.class.getName()+" network.json");
+ System.exit(-1);
+ }
+
+ String json = FileUtils.readFileToString(new File(args[0]), StandardCharsets.UTF_8);
+
+ System.out.println(VNet.fromJson(json).script());
+ }
+}
diff --git a/src/main/java/com/github/jjm2473/vnet/VNet.java b/src/main/java/com/github/jjm2473/vnet/VNet.java
new file mode 100644
index 0000000..851bda1
--- /dev/null
+++ b/src/main/java/com/github/jjm2473/vnet/VNet.java
@@ -0,0 +1,181 @@
+package com.github.jjm2473.vnet;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.alibaba.fastjson.JSON;
+
+import com.github.jjm2473.vnet.model.Link;
+import com.github.jjm2473.vnet.model.LinkPeer;
+import com.github.jjm2473.vnet.model.Net;
+import com.github.jjm2473.vnet.model.Node;
+import com.github.jjm2473.vnet.model.Root;
+import com.github.jjm2473.vnet.model.Host;
+import com.github.jjm2473.vnet.model.Switch;
+
+/**
+ * 解析编译蓝图
+ */
+public class VNet {
+ private Net model;
+ private Map nodeMap;
+ private VNet(Net model, Map nodeMap) {
+ this.model = model;
+ this.nodeMap = nodeMap;
+ }
+
+ /**
+ * 编译成shell脚本, 支持创建和销毁操作
+ * @return
+ */
+ public String script() {
+ StringBuilder sb = new StringBuilder("#!/bin/sh\n\n");
+
+ sb.append("action=$1\n"
+ + "\n"
+ + "if [ \\( -z \"$action\" \\) -o \\( \"$action\" != \"create\" -a \"$action\" != \"destroy\" \\) ]; then\n"
+ + "\techo \"Usage: $0 { create | destroy }\"\n"
+ + "\texit 255\n"
+ + "fi\n");
+
+ sb.append("\nif [ \"$action\" = \"create\" ]; then\n"
+ + "\techo \"creating...\"\n");
+
+ sb.append(create().replace("\n", "\n\t"));
+
+ sb.append("\nelse\n"
+ + "\techo \"destroying...\"\n");
+
+ sb.append(destroy().replace("\n", "\n\t"));
+
+ sb.append("\nfi\n");
+
+ return sb.toString();
+ }
+
+ /**
+ * 创建操作编译成shell脚本
+ * @return
+ */
+ public String create() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("\n# init namespace\n");
+ for (Host h:model.hosts) {
+ sb.append("ip netns add ").append(h.name).append('\n');
+ sb.append("ip netns exec ").append(h.name).append(" ip link set dev lo up\n");
+ }
+
+ sb.append("\n# init bridge\n");
+ for (Switch s:model.switches) {
+ sb.append("ip link add ").append(s.name).append(" type bridge\n");
+ sb.append("ip link set dev ").append(s.name).append(" up\n");
+ }
+
+ sb.append("\n# config link");
+ int vethNo = 0;
+ for (Link link:model.links) {
+ sb.append("\n# config link pair #").append(vethNo).append('\n');
+ String vethPre = "veth" + vethNo + ".";
+ sb.append("ip link add ").append(vethPre).append("0 type veth peer name ").append(vethPre).append("1\n");
+ LinkPeer[] linkPeers = {link.from, link.to};
+ for (int i=0;i nodeMap = new HashMap(net.hosts.size()+net.switches.size());
+ for (Host h:net.hosts) {
+ nodeMap.put(h.name, h);
+ }
+ for (Switch s:net.switches) {
+ nodeMap.put(s.name, s);
+ }
+ if (!nodeMap.containsKey(Root.INSTANCE.name)) {
+ nodeMap.put(Root.INSTANCE.name, Root.INSTANCE);
+ }
+ for (Link l:net.links) {
+ for (LinkPeer peer:new LinkPeer[]{l.from, l.to}) {
+ if (!nodeMap.containsKey(peer.peer)) {
+ throw new IllegalArgumentException("peer "+peer.peer+" not defined");
+ }
+ }
+ }
+ return new VNet(net, nodeMap);
+ }
+}
diff --git a/src/main/java/com/github/jjm2473/vnet/model/Host.java b/src/main/java/com/github/jjm2473/vnet/model/Host.java
new file mode 100644
index 0000000..82a7b58
--- /dev/null
+++ b/src/main/java/com/github/jjm2473/vnet/model/Host.java
@@ -0,0 +1,11 @@
+package com.github.jjm2473.vnet.model;
+
+/**
+ * 表示一台主机或者路由器, 相当于Linux中的一个网络命名空间
+ */
+public class Host extends Node {
+ /**
+ * 自动配置默认网关地址, 如果为null则不配置
+ */
+ public String gateway;
+}
diff --git a/src/main/java/com/github/jjm2473/vnet/model/Link.java b/src/main/java/com/github/jjm2473/vnet/model/Link.java
new file mode 100644
index 0000000..480718e
--- /dev/null
+++ b/src/main/java/com/github/jjm2473/vnet/model/Link.java
@@ -0,0 +1,10 @@
+package com.github.jjm2473.vnet.model;
+
+/**
+ * 链路, 对应Linux中的一对虚拟veth设备, 相当于连接了一根网线的两个网卡,
+ * from 和 to 代表两个端点连接的主机, 没有先后关系
+ */
+public class Link {
+ public LinkPeer from;
+ public LinkPeer to;
+}
diff --git a/src/main/java/com/github/jjm2473/vnet/model/LinkPeer.java b/src/main/java/com/github/jjm2473/vnet/model/LinkPeer.java
new file mode 100644
index 0000000..3420455
--- /dev/null
+++ b/src/main/java/com/github/jjm2473/vnet/model/LinkPeer.java
@@ -0,0 +1,23 @@
+package com.github.jjm2473.vnet.model;
+
+import com.alibaba.fastjson.annotation.JSONField;
+
+/**
+ * 链路的端点
+ */
+public class LinkPeer {
+ /**
+ * 主机或者交换机的节点名称
+ * @see Node#name
+ */
+ public String peer;
+ /**
+ * 接口名称, 可为null
+ */
+ @JSONField(name = "if")
+ public String iface;
+ /**
+ * 自动给这个端点配置ipv4地址, 格式为"192.168.1.1"或者"192.168.1.1/24", 如果为null则不配置, 连接到交换机的端口忽略此参数
+ */
+ public String ipv4;
+}
diff --git a/src/main/java/com/github/jjm2473/vnet/model/Net.java b/src/main/java/com/github/jjm2473/vnet/model/Net.java
new file mode 100644
index 0000000..2fda530
--- /dev/null
+++ b/src/main/java/com/github/jjm2473/vnet/model/Net.java
@@ -0,0 +1,22 @@
+package com.github.jjm2473.vnet.model;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 虚拟网络的配置
+ */
+public class Net {
+ /**
+ * 定义主机和/或路由器, 一个主机或者路由器对应一个网络命名空间
+ */
+ public List hosts;
+ /**
+ * 定义交换机, 一个交换机对应一个虚拟bridge设备
+ */
+ public List switches = Collections.emptyList();
+ /**
+ * 链路, 一条链路相当于一条网线
+ */
+ public List links = Collections.emptyList();
+}
diff --git a/src/main/java/com/github/jjm2473/vnet/model/Node.java b/src/main/java/com/github/jjm2473/vnet/model/Node.java
new file mode 100644
index 0000000..23479c5
--- /dev/null
+++ b/src/main/java/com/github/jjm2473/vnet/model/Node.java
@@ -0,0 +1,11 @@
+package com.github.jjm2473.vnet.model;
+
+/**
+ * 节点, 交换机和主机都用这个表示
+ */
+public class Node {
+ /**
+ * 节点名称
+ */
+ public String name;
+}
diff --git a/src/main/java/com/github/jjm2473/vnet/model/Root.java b/src/main/java/com/github/jjm2473/vnet/model/Root.java
new file mode 100644
index 0000000..2561fd3
--- /dev/null
+++ b/src/main/java/com/github/jjm2473/vnet/model/Root.java
@@ -0,0 +1,15 @@
+package com.github.jjm2473.vnet.model;
+
+/**
+ * 特殊的节点, 表示宿主机, 单例;
+ * 蓝图中如果root没定义则自动添加宿主机为root
+ */
+public class Root extends Node {
+ /**
+ * 宿主机实例
+ */
+ public static final Root INSTANCE = new Root();
+ private Root() {
+ this.name = "root";
+ }
+}
diff --git a/src/main/java/com/github/jjm2473/vnet/model/Switch.java b/src/main/java/com/github/jjm2473/vnet/model/Switch.java
new file mode 100644
index 0000000..985fd6b
--- /dev/null
+++ b/src/main/java/com/github/jjm2473/vnet/model/Switch.java
@@ -0,0 +1,7 @@
+package com.github.jjm2473.vnet.model;
+
+/**
+ * 交换机, 对应生成一个虚拟bridge设备
+ */
+public class Switch extends Node {
+}
diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..e2524fc
--- /dev/null
+++ b/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: com.github.jjm2473.vnet.Main
+
diff --git a/src/test/java/com/github/jjm2473/vnet/VNetTest.java b/src/test/java/com/github/jjm2473/vnet/VNetTest.java
new file mode 100644
index 0000000..867eb79
--- /dev/null
+++ b/src/test/java/com/github/jjm2473/vnet/VNetTest.java
@@ -0,0 +1,20 @@
+package com.github.jjm2473.vnet;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.io.IOUtils;
+import org.testng.annotations.Test;
+
+public class VNetTest {
+ @Test
+ public void testBuild() throws IOException {
+ String json = IOUtils.resourceToString("test.json", StandardCharsets.UTF_8, VNetTest.class.getClassLoader());
+ VNet vNet = VNet.fromJson(json);
+ System.out.println(vNet.script());
+ }
+
+ public static void main(String[] argv) throws IOException {
+ new VNetTest().testBuild();
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/test.json b/src/test/resources/test.json
new file mode 100644
index 0000000..9d25207
--- /dev/null
+++ b/src/test/resources/test.json
@@ -0,0 +1,106 @@
+{
+ "hosts": [
+ {
+ "name": "router1",
+ "gateway": "192.168.64.254"
+ },
+ {
+ "name": "pc1",
+ "gateway": "192.168.65.254"
+ },
+ {
+ "name": "pc2",
+ "gateway": "192.168.65.254"
+ },
+ {
+ "name": "pc3",
+ "gateway": "192.168.66.254"
+ },
+ {
+ "name": "pc4",
+ "gateway": "192.168.66.254"
+ }
+ ],
+ "switches": [
+ {
+ "name": "vsw1"
+ },
+ {
+ "name": "vsw2"
+ }
+ ],
+ "links": [
+ {
+ "from": {
+ "peer": "pc1",
+ "if": "eth0",
+ "ipv4": "192.168.65.1/24"
+ },
+ "to": {
+ "peer": "vsw1"
+ }
+ },
+ {
+ "from": {
+ "peer": "pc2",
+ "if": "eth0",
+ "ipv4": "192.168.65.2/24"
+ },
+ "to": {
+ "peer": "vsw1"
+ }
+ },
+ {
+ "from": {
+ "peer": "pc3",
+ "if": "eth0",
+ "ipv4": "192.168.66.1/24"
+ },
+ "to": {
+ "peer": "vsw2"
+ }
+ },
+ {
+ "from": {
+ "peer": "pc4",
+ "if": "eth0",
+ "ipv4": "192.168.66.2/24"
+ },
+ "to": {
+ "peer": "vsw2"
+ }
+ },
+ {
+ "from": {
+ "peer": "router1",
+ "if": "br-lan0",
+ "ipv4": "192.168.65.254/24"
+ },
+ "to": {
+ "peer": "vsw1"
+ }
+ },
+ {
+ "from": {
+ "peer": "router1",
+ "if": "br-lan1",
+ "ipv4": "192.168.66.254/24"
+ },
+ "to": {
+ "peer": "vsw2"
+ }
+ },
+ {
+ "from": {
+ "peer": "router1",
+ "if": "eth0",
+ "ipv4": "192.168.64.1/24"
+ },
+ "to": {
+ "peer": "root",
+ "if": "vroute1",
+ "ipv4": "192.168.64.254/24"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/vnet-lab.iml b/vnet-lab.iml
new file mode 100644
index 0000000..78b2cc5
--- /dev/null
+++ b/vnet-lab.iml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file