├── .gitignore
├── examples
├── example_lab.png
├── 2015-12-11
│ └── icn-20151211.pdf
├── example_SDN.config
└── example_lab.config
├── src
├── static
│ ├── images
│ │ ├── other.png
│ │ ├── router.png
│ │ ├── switch.png
│ │ ├── terminal.png
│ │ ├── background.png
│ │ ├── controller.png
│ │ ├── icons
│ │ │ ├── icon.ico
│ │ │ ├── icon.png
│ │ │ └── icon.icns
│ │ ├── nameserver.png
│ │ └── webserver.png
│ └── vendor
│ │ └── js
│ │ └── FileSaver.min.js
├── sdn-manager
│ ├── vue-components
│ │ ├── controllerAndRulesSection.css
│ │ ├── ruleMakerModal.css
│ │ ├── switchDetailsSection.css
│ │ ├── labelsSection.css
│ │ ├── switchDetailsSection.js
│ │ ├── controllerAndRulesSection.js
│ │ ├── ruleMakerModal.js
│ │ └── labelsSection.js
│ ├── helper.js
│ ├── README.md
│ ├── rules-utils
│ │ ├── README.md
│ │ ├── rulesMapper.js
│ │ └── ruleUtils.js
│ ├── dataStore.js
│ ├── ryuActions.js
│ ├── simulation.js
│ └── index.html
├── style
│ ├── simulation.css
│ ├── sdn-home.css
│ ├── nlg-home.css
│ └── main.css
└── lab-generator
│ ├── models
│ ├── model.js
│ └── draw.js
│ ├── helper.js
│ ├── controller.js
│ └── make_draw_model.js
├── images
├── screencapture-201801143.png
└── screencapture-201801261.png
├── forge.config.js
├── LICENSE
├── Makefile
├── package.json
├── README.md
└── electronClient.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | release-builds/
3 | tmp/
4 | .vscode/
5 | out/
6 | .DS_Store
--------------------------------------------------------------------------------
/examples/example_lab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KatharaFramework/Netkit-Lab-Generator/HEAD/examples/example_lab.png
--------------------------------------------------------------------------------
/src/static/images/other.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KatharaFramework/Netkit-Lab-Generator/HEAD/src/static/images/other.png
--------------------------------------------------------------------------------
/src/static/images/router.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KatharaFramework/Netkit-Lab-Generator/HEAD/src/static/images/router.png
--------------------------------------------------------------------------------
/src/static/images/switch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KatharaFramework/Netkit-Lab-Generator/HEAD/src/static/images/switch.png
--------------------------------------------------------------------------------
/src/static/images/terminal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KatharaFramework/Netkit-Lab-Generator/HEAD/src/static/images/terminal.png
--------------------------------------------------------------------------------
/src/static/images/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KatharaFramework/Netkit-Lab-Generator/HEAD/src/static/images/background.png
--------------------------------------------------------------------------------
/src/static/images/controller.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KatharaFramework/Netkit-Lab-Generator/HEAD/src/static/images/controller.png
--------------------------------------------------------------------------------
/src/static/images/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KatharaFramework/Netkit-Lab-Generator/HEAD/src/static/images/icons/icon.ico
--------------------------------------------------------------------------------
/src/static/images/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KatharaFramework/Netkit-Lab-Generator/HEAD/src/static/images/icons/icon.png
--------------------------------------------------------------------------------
/src/static/images/nameserver.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KatharaFramework/Netkit-Lab-Generator/HEAD/src/static/images/nameserver.png
--------------------------------------------------------------------------------
/src/static/images/webserver.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KatharaFramework/Netkit-Lab-Generator/HEAD/src/static/images/webserver.png
--------------------------------------------------------------------------------
/images/screencapture-201801143.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KatharaFramework/Netkit-Lab-Generator/HEAD/images/screencapture-201801143.png
--------------------------------------------------------------------------------
/images/screencapture-201801261.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KatharaFramework/Netkit-Lab-Generator/HEAD/images/screencapture-201801261.png
--------------------------------------------------------------------------------
/src/static/images/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KatharaFramework/Netkit-Lab-Generator/HEAD/src/static/images/icons/icon.icns
--------------------------------------------------------------------------------
/examples/2015-12-11/icn-20151211.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KatharaFramework/Netkit-Lab-Generator/HEAD/examples/2015-12-11/icn-20151211.pdf
--------------------------------------------------------------------------------
/forge.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | packagerConfig: {
3 | icon: './src/static/images/icons/icon'
4 | },
5 | rebuildConfig: {},
6 | makers: [
7 | {
8 | name: '@electron-forge/maker-zip',
9 | platforms: ['darwin', 'linux', 'win32'],
10 | },
11 | ],
12 | };
13 |
--------------------------------------------------------------------------------
/src/sdn-manager/vue-components/controllerAndRulesSection.css:
--------------------------------------------------------------------------------
1 | #controllerAndRulesSection {
2 | width: 400px;
3 | overflow-y: hidden;
4 | }
5 |
6 | #controllerAndRulesSection hr {
7 | margin: 10px;
8 | }
9 |
10 | #controllerAndRulesSection h3 {
11 | margin-top: 10px;
12 | }
13 |
14 | #controllerAndRulesSection input {
15 | background: none;
16 | border: 1px solid darkgray;
17 | padding: 3px 10px;
18 | margin: 3px;
19 | }
20 |
21 | #d3-rules textarea:disabled {
22 | height: 600px;
23 | }
24 |
25 | #controller-post-parameters-textarea {
26 | min-height: 300px;
27 | background-color: white;
28 | margin: 5px;
29 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This file is part of Netkit Lab Generator.
2 |
3 | Netkit Lab Generator is free software: you can redistribute it and/or modify
4 | it under the terms of the GNU General Public License as published by
5 | the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 |
8 | Netkit Lab Generator is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU General Public License for more details.
12 |
13 | You should have received a copy of the GNU General Public License
14 | along with Netkit Lab Generator. If not, see .
15 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: build-mac-arm build-mac-x64 build-win-x64 build-win-arm64 build-linux-x64 build-linux-arm64
2 |
3 | build-mac-arm: install-deps
4 | yarn electron-forge make --arch arm64 --platform darwin
5 |
6 | build-mac-x64: install-deps
7 | yarn electron-forge make --arch x64 --platform darwin
8 |
9 | build-win-x64: install-deps
10 | yarn electron-forge make --arch x64 --platform win32
11 |
12 | build-win-arm64: install-deps
13 | yarn electron-forge make --arch arm64 --platform win32
14 |
15 | build-linux-x64: install-deps
16 | yarn electron-forge make --arch x64 --platform linux
17 |
18 | build-linux-arm64: install-deps
19 | yarn electron-forge make --arch arm64 --platform linux
20 |
21 | install-deps:
22 | npm install
23 |
24 | start: install-deps
25 | npm start
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nlg",
3 | "productName": "Netkit Lab Generator",
4 | "version": "1.5.0",
5 | "description": "Desktop UI for Kathara",
6 | "repository": "https://github.com/KatharaFramework/Netkit-Lab-Generator.git",
7 | "main": "electronClient.js",
8 | "scripts": {
9 | "start": "electron-forge start",
10 | "package": "electron-forge package",
11 | "make": "electron-forge make"
12 | },
13 | "author": "Gaetano Bonofiglio, Lorenzo Ariemma",
14 | "license": "GPL-3.0",
15 | "devDependencies": {
16 | "@electron-forge/cli": "^6.1.1",
17 | "@electron-forge/maker-deb": "^6.1.1",
18 | "@electron-forge/maker-rpm": "^6.1.1",
19 | "@electron-forge/maker-squirrel": "^6.1.1",
20 | "@electron-forge/maker-zip": "^6.1.1",
21 | "electron": "^27.0.0"
22 | },
23 | "dependencies": {
24 | "electron-squirrel-startup": "^1.0.0"
25 | }
26 | }
--------------------------------------------------------------------------------
/src/sdn-manager/vue-components/ruleMakerModal.css:
--------------------------------------------------------------------------------
1 | #rule-modal {
2 | text-align: center;
3 | }
4 |
5 | .half {
6 | width: 50%;
7 | float: left;
8 | padding: 1em;
9 | }
10 |
11 | .modal-body input {
12 | margin: 10px;
13 | border: 1px solid darkgray;
14 | padding: 0 10px;
15 | background-image: none;
16 | }
17 |
18 | #rule-modal input:invalid {
19 | box-shadow: 0 0 2px red;
20 | }
21 |
22 | .modal-body select {
23 | margin: 5px;
24 | }
25 |
26 | #rule-modal .half button {
27 | min-width: 80px;
28 | margin-bottom: 5px;
29 | border-radius: 15px;
30 | padding: 0;
31 | }
32 |
33 | #rule-modal .half button.btn-danger {
34 | border-top-left-radius: 0;
35 | border-bottom-left-radius: 0;
36 | }
37 |
38 | #rule-modal .half button.btn-success {
39 | border-top-right-radius: 0;
40 | border-bottom-right-radius: 0;
41 | }
42 |
43 | .answer-selection {
44 | background-color: white;
45 | min-width: 180px;
46 | padding: 1px 10px;
47 | }
--------------------------------------------------------------------------------
/src/sdn-manager/helper.js:
--------------------------------------------------------------------------------
1 | function toggleSecondaryTopbar() {
2 | let secondaryTopbar = document.getElementById("secondaryTopbar");
3 | if (!secondaryTopbar.style.zIndex) {
4 | secondaryTopbar.style.top = "5rem";
5 | secondaryTopbar.style.zIndex = "1";
6 | } else {
7 | secondaryTopbar.style.top = null;
8 | secondaryTopbar.style.zIndex = null;
9 | }
10 | }
11 |
12 | function hide(...element) {
13 | element.forEach(el => el.style.display = "none")
14 | }
15 |
16 | function unhide(...element) {
17 | element.forEach(el => el.style.display = null)
18 | }
19 |
20 | function enableButtons(...ids) {
21 | ids.forEach(id => document.getElementById(id).disabled = false)
22 | }
23 |
24 | function disableButtons(...ids) {
25 | ids.forEach(id => document.getElementById(id).disabled = true)
26 | }
27 |
28 | function downloadString(string, filename) {
29 | let element = document.body.appendChild(document.createElement("a"));
30 | element.setAttribute("href", "data:text/plaincharset=utf-8," + encodeURIComponent(string));
31 | element.setAttribute("download", filename);
32 | element.style.display = "none";
33 | element.click();
34 | document.body.removeChild(element);
35 | }
36 |
--------------------------------------------------------------------------------
/src/sdn-manager/vue-components/switchDetailsSection.css:
--------------------------------------------------------------------------------
1 | #switchDetailsSection {
2 | overflow-x: auto;
3 | text-align: center;
4 | min-width: 50rem;
5 | }
6 |
7 | #switchDetailsSection > h1 {
8 | margin-top: 10px;
9 | color: var(--main-color);
10 | }
11 |
12 | #switchDetailsSection > button {
13 | float: right;
14 | margin: 10px 20px;
15 | }
16 |
17 |
18 | #switchDetailsSection td > div:first-child hr {
19 | display: none;
20 | }
21 |
22 | #switchDetailsSection td input[type=number] {
23 | max-width: 50px;
24 | margin: 0;
25 | border: none;
26 | }
27 |
28 | /* --- SVG --- */
29 |
30 | #switchDetailsSection svg {
31 | border: 1px solid lightgray;
32 | padding: 10px;
33 | margin: 10px auto;
34 | }
35 |
36 | .x-axis,
37 | .y-axis {
38 | fill: black;
39 | }
40 |
41 | .domain {
42 | fill: white;
43 | }
44 |
45 | /* --- Rules --- */
46 |
47 | #d2-rules {
48 | margin: auto;
49 | width: fit-content;
50 | }
51 |
52 | #d2-rules > div > h2 {
53 | color: var(--main-color);
54 | }
55 |
56 | #d2-rules table {
57 | margin: auto;
58 | }
59 |
60 | #switchDetailsSection .bottoni button {
61 | margin: 2px 3px;
62 | }
63 |
64 | #switchDetailsSection .bottoni textarea {
65 | margin: 5px 0;
66 | height: 500px;
67 | }
68 |
69 | #d2-selection {
70 | margin-top: 30px;
71 | bottom: 10px;
72 | left: 0;
73 | width: 100%;
74 | height: 20px;
75 | }
76 |
77 | #d2-selection p {
78 | margin: 0;
79 | }
80 |
81 | #d2-selection input {
82 | margin: 1px 3px;
83 | }
--------------------------------------------------------------------------------
/src/style/simulation.css:
--------------------------------------------------------------------------------
1 | /* ---------------- */
2 | /* ----- NODI ----- */
3 | /* ---------------- */
4 |
5 | g.nodes circle {
6 | fill: lightgray;
7 | }
8 |
9 | g.nodes circle.network {
10 | fill: black;
11 | }
12 |
13 | g.nodes circle.network.external {
14 | fill: lightgray;
15 | }
16 |
17 | g.nodes circle.switch {
18 | fill: blue;
19 | }
20 |
21 | g.nodes circle.edge {
22 | stroke: black;
23 | stroke-width: 1px;
24 | fill: white;
25 | }
26 |
27 | /* --- mouseover --- */
28 |
29 | g.nodes circle:hover {
30 | r: 30;
31 | }
32 |
33 | g.nodes circle.network:hover {
34 | r: 20;
35 | }
36 |
37 | /* --- selection --- */
38 |
39 | g.nodes circle.selected,
40 | g.nodes circle.edge.selected {
41 | stroke: black;
42 | stroke-width: 2px;
43 | fill: red;
44 | }
45 |
46 | /* ----------------- */
47 | /* ----- LINKS ----- */
48 | /* ----------------- */
49 |
50 | g.links line {
51 | stroke: lightgray;
52 | stroke-width: .7rem;
53 | }
54 |
55 | g.links line.switch {
56 | stroke: lightgray;
57 | }
58 |
59 | line.network.edge:not(.switch) {
60 | stroke-dasharray: 10px 10px;
61 | }
62 |
63 | /* --- mouseover --- */
64 |
65 |
66 | g.links line:hover {
67 | stroke-width: 1rem;
68 | }
69 |
70 | /* --- selection --- */
71 |
72 |
73 | g.links line.selected {
74 | stroke: red;
75 | }
76 |
77 | g.links line.selected.straight {
78 | marker-start: url(#markerArrow1)
79 | }
80 |
81 | g.links line.selected.reversed {
82 | marker-end: url(#markerArrow2)
83 | }
--------------------------------------------------------------------------------
/src/lab-generator/models/model.js:
--------------------------------------------------------------------------------
1 | var labInfo = {
2 | description: "",
3 | version: "",
4 | author: "",
5 | email: "",
6 | web: ""
7 | };
8 |
9 | var backbone = {
10 | name: "",
11 | row: 1,
12 | type: "terminal",
13 | routingSoftware: "frr",
14 | interfaces: {
15 | counter: 1,
16 | if: [{
17 | eth: {
18 | number: 0,
19 | domain: ""
20 | },
21 | ip: ""
22 | }],
23 | free: ""
24 | },
25 | gateways: {
26 | counter: 1,
27 | gw: [{
28 | route: "",
29 | if: 0
30 | }]
31 | },
32 | pc: {
33 | dns: "-"
34 | },
35 | ws: {
36 | userdir: false
37 | },
38 | ns: {
39 | recursion: true,
40 | authority: true
41 | },
42 | other: {
43 | image: "",
44 | files: [],
45 | fileCounter: 0
46 | },
47 | ryu: {
48 | stp: false,
49 | rest: true,
50 | topology: true,
51 | custom: ""
52 | },
53 | routing: {
54 | rip: {
55 | en: false,
56 | connected: false,
57 | ospf: false,
58 | bgp: false,
59 | network: [""],
60 | route: [""],
61 | free: ""
62 | },
63 | ospf: {
64 | en: false,
65 | connected: false,
66 | rip: false,
67 | bgp: false,
68 | if: [],
69 | network: [""],
70 | area: ["0.0.0.0"],
71 | stub: [false],
72 | free: ""
73 | },
74 | bgp: {
75 | en: false,
76 | as: "",
77 | network: [""],
78 | remote: [{
79 | neighbor: "",
80 | as: "",
81 | description: ""
82 | }],
83 | free: ""
84 | },
85 | frr: {
86 | free: ""
87 | }
88 | }
89 | };
--------------------------------------------------------------------------------
/src/sdn-manager/vue-components/labelsSection.css:
--------------------------------------------------------------------------------
1 | #labelsSection {
2 | overflow: auto;
3 | width: 100%;
4 | }
5 |
6 | .colorTag {
7 | display: inline-block;
8 | width: 5rem;
9 | margin-right: 10px;
10 | margin: 2px;
11 | }
12 |
13 | #labelAddRow > input,
14 | #labelAddRow > button {
15 | max-width: 30%;
16 | height: 30px;
17 | padding: 0 10px;
18 | }
19 |
20 | #labelsSection button:hover {
21 | background-color: #bbb;
22 | }
23 |
24 | #labelsSection span.hint {
25 | color: orange;
26 | float: right;
27 | font-size: 1.5em;
28 | position: relative;
29 | }
30 |
31 | #labelsSection span.hint::before {
32 | position: absolute;
33 | bottom: 100%;
34 | font-size: small;
35 | background-color: #31708f;
36 | border-radius: 5px;
37 | padding: 10px;
38 | width: 150px;
39 | color: white;
40 | border: 2px solid black;
41 | transform: scale(0);
42 | }
43 |
44 | #labelsSection span.hint:hover::before {
45 | transform: scale(1);
46 | }
47 |
48 | #labelsSection span.hint-match::before {
49 | left: -30px;
50 | content: 'Has more hidden matches';
51 | }
52 |
53 | #labelsSection span.hint-action::before {
54 | left: -130px;
55 | content: 'Has more hidden actions';
56 | }
57 |
58 | #labelsdiv input {
59 | border: 1px solid;
60 | max-width: 10rem;
61 | padding: 0 1rem;
62 | background-color: cornsilk;
63 | }
64 |
65 | #labelsdiv input:disabled {
66 | border: none;
67 | max-width: 10rem;
68 | padding: 0 1rem;
69 | background-color: transparent;
70 | }
71 |
72 | #labelsdiv button {
73 | margin: 0 .3rem;
74 | padding: 0.3em;
75 | }
76 |
77 | #labelsdiv table td[colspan='3'] {
78 | color: darkgray;
79 | text-align: center;
80 | }
81 |
82 | .remove-slider {
83 | background-color: red;
84 | text-align: center;
85 | color: white;
86 | font-size: xx-large;
87 | cursor: pointer;
88 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Netkit Lab Generator
2 | A tool to configure a Kathará or a Netkit lab and generate all the files you need and the topology graph.
3 |
4 | It works as stand-alone HTML file or using Electron for some advanced capabilities (like the ability to directly run a lab from inside the UI).
5 |
6 | 
7 |
8 | Simply fill the form and download the bash script or the .zip file with the whole lab.
9 | It is also possible to export and import the form configuration for future changes.
10 |
11 | Now supports network topology preview (requires a modern browser or the Electrum release).
12 |
13 | * [Demo](https://www.kathara.org/tools/nlg/) **(may not always be up to date)**
14 | * [Example configuration](https://raw.githubusercontent.com/Kidel/Netkit-Lab-Generator/master/examples/example_lab.config) (can be imported to generate the files or the graph)
15 | * [Screenshot (form)](https://raw.githubusercontent.com/Kidel/Netkit-Lab-Generator/master/images/screencapture-201801261.png)
16 | * [Screenshot (graph)](https://raw.githubusercontent.com/Kidel/Netkit-Lab-Generator/master/images/screencapture-201801143.png)
17 |
18 |
19 | #### Created by
20 |
21 | * https://github.com/KatharaFramework/Netkit-Lab-Generator/graphs/contributors
22 |
23 | ### Links
24 |
25 | * [**Kathará Home**](https://www.kathara.org)
26 | * [**Netkit Home**](http://wiki.netkit.org/index.php/Main_Page)
27 | * [**Netkit on GitHub**](https://github.com/maxonthegit/netkit-core)
28 |
29 | ***
30 |
31 | ### FAQ
32 |
33 | * **Q**: How can I make a machine that is both a web server and a name server?
34 | * **A**: Two machines with the same name are considered as the same machine. For any shared file, the configuration lines of the second machine are added right after the ones of the first machine. For any file that is not shared there is no conflict.
35 |
--------------------------------------------------------------------------------
/src/lab-generator/models/draw.js:
--------------------------------------------------------------------------------
1 | const
2 | LENGTH_MAIN = 350,
3 | LENGTH_SERVER = 150,
4 | LENGTH_SUB = 50,
5 | LENGTH_CLOSE = 0.0001,
6 | WIDTH_SCALE = 3,
7 | GREEN = "green",
8 | RED = "#C5000B",
9 | ORANGE = "#f1db8d",
10 | GRAY = "gray",
11 | LGRAY = "#dddddd",
12 | LLGRAY = "#efefef",
13 | WHITE = "#fafafa",
14 | BLUE = "#2B7CE9",
15 | BLACK = "#2B1B17";
16 |
17 | const DIR = "src/static/images/";
18 |
19 | // Called when the Visualization API is loaded.
20 | function draw(nodes, edges) {
21 | let container = document.getElementById("mynetwork");
22 |
23 | let data = {
24 | nodes,
25 | edges
26 | };
27 |
28 | let options = {
29 | nodes: {
30 | scaling: {
31 | min: 16,
32 | max: 32
33 | }
34 | },
35 | edges: {
36 | smooth: {
37 | type: "dynamic"
38 | },
39 | color: BLACK
40 | },
41 | physics: {
42 | enabled: true,
43 | barnesHut: { gravitationalConstant: -1200 }
44 | },
45 | interaction: {
46 | multiselect: true
47 | },
48 | groups: {
49 | "terminal": {
50 | image: DIR + "terminal.png",
51 | shape: "image",
52 | },
53 | "router": {
54 | image: DIR + "router.png",
55 | shape: "image",
56 | },
57 | "ns": {
58 | image: DIR + "nameserver.png",
59 | shape: "image",
60 | },
61 | "ws": {
62 | image: DIR + "webserver.png",
63 | shape: "image",
64 | value: 8
65 | },
66 | "switch": {
67 | image: DIR + "switch.png",
68 | shape: "image",
69 | },
70 | "controller": {
71 | image: DIR + "controller.png",
72 | shape: "image",
73 | },
74 | "other": {
75 | image: DIR + "other.png",
76 | shape: "image",
77 | },
78 | "domain": {
79 | color: BLACK,
80 | font: { color: "#dddddd" }
81 | },
82 | "eth": {
83 | color: WHITE,
84 | shape: "box"
85 | },
86 | "domain-ip": {
87 | color: LGRAY,
88 | shape: "box"
89 | },
90 | "ospf": {
91 | color: ORANGE,
92 | shape: "box"
93 | },
94 | "rip": {
95 | color: ORANGE,
96 | shape: "box"
97 | },
98 | "bgp": {
99 | color: ORANGE,
100 | shape: "box"
101 | }
102 | }
103 | };
104 |
105 | network = new vis.Network(container, data, options);
106 |
107 | document.getElementById("smoothEnabled").checked = true;
108 | document.getElementById("smoothType").value = "dynamic";
109 | document.getElementById("physicsEnabled").checked = true;
110 | document.getElementById("physicsGravitationalConstant").value = -1200;
111 | document.getElementById("physicsGravitationalConstantValue").value = -1200;
112 | }
113 |
--------------------------------------------------------------------------------
/src/sdn-manager/README.md:
--------------------------------------------------------------------------------
1 | # About
2 | TODO
3 |
4 | # Struttura del progetto
5 | Questo progetto si divide in due parti distinte che collaborano per realizzare uno strumento visuale per il controllo di reti virtuali ed in particolare dei loro traffici di dati.
6 | Queste due parti sono:
7 | - un'interfaccia web che ha lo scopo di permettere all'utilizzatore di controllare una rete virtuale di switch Open VSwitch e (un solo) controller Ryu e di analizzare le sue regole OpenFlow;
8 | - un web server che si occupa di ospitare la web app e di tradurre le richieste da essa provenienti in richieste per il controller Ryu. Questo sarà a sua volta ospitato in un container docker creato ad hoc.
9 |
10 | Il codice per la prima parte (l'interfaccia) è incluso nella cartella src, mentre il codice per il web server e per l'immagine docker sono collocati nella root direcotry di questo progetto.
11 |
12 | ## Docker image
13 | TODO
14 |
15 | ## Web interface
16 | Per la realizzazione dell'interfaccia web è stato utilizzato il framework Vue.js che permette di dividere l'intera applicazione in delle più piccole e più semplici da mantenere "componenti".
17 | TODO
18 |
19 | # Installazione
20 |
21 | L'installazione di questo progetto coincide con la creazione dell'immagine docker che andrà ad ospitare il server e la web application.
22 | Costruire o aggiornare l'immagine docker è semplice: basta eseguire lo script build.sh con il comando sh build.sh.
23 |
24 | ### Kathara-SDN-Interface e Netkit-Lab-Generator
25 |
26 | Quest'applicazione è stata sviluppata insieme a Netkit-Lab-Generator, un'altra interfaccia che semplifica la creazione di reti virtuali ed eventualmente (nel caso di reti OpenFlow) del lancio di questo secondo strumento.
27 |
28 | Infatti Netkit-Lab-Generator avvia un container a partire dall'immagine docker che deve essere stata costruita precedentemente e lo collega al nodo docker che rappresenta la macchina Ryu della rete virtuale. Dopodiché permette la connessione all'applicazione web tramite web browser.
29 |
30 |
31 |
32 |
33 | # TODOs
34 | * Nuovo:
35 | * Creare un server fake per semplificare lo sviluppo
36 |
37 | * Migliorare il grafico con forme complesse anziché pallini
38 | * Colorare i path che già sono stati creati con il colore della label a cui appartengono
39 |
40 | * Limitazioni da rimuovere:
41 | * Funziona con 1 label al massimo
42 |
43 | * Alcune cose non funzionano come dovrebbero:
44 | * Il pulsante "Update statistics" per ora genera delle statistiche casuali (per scelta). Correggere.
45 |
46 | * Altre cose devono ancora essere implementate:
47 | * Questo README va scritto completamente.
48 | * Molti "TODO" sono sparsi nel codice. Ricercali e correggili
--------------------------------------------------------------------------------
/electronClient.js:
--------------------------------------------------------------------------------
1 | const { app, BrowserWindow, ipcMain } = require("electron");
2 | const path = require("path");
3 | const { exec } = require("child_process");
4 | const url = require("url");
5 | const fs = require("fs");
6 |
7 | /* ----------------------------------------------------------- */
8 | /* -------------------------- SETUP -------------------------- */
9 | /* ----------------------------------------------------------- */
10 |
11 | let mainWindow, sdnManagerWindow
12 |
13 | function startMainWindow(){
14 | mainWindow = new BrowserWindow({
15 | width: 1280,
16 | height: 760,
17 | minWidth: 800,
18 | minHeight: 600,
19 | icon: '/src/static/images/icons/icon.png'
20 | });
21 |
22 | mainWindow.loadURL(url.format({
23 | pathname: path.join(__dirname, "index.html"),
24 | protocol: "file:",
25 | slashes: true
26 | }));
27 |
28 | mainWindow.setTitle('Netkit-Lab-Generator');
29 |
30 | mainWindow.on("closed", function () {
31 | mainWindow = null;
32 | if(!sdnManagerWindow) app.quit();
33 | });
34 | }
35 |
36 | function startSDNManagerWindow(){
37 | sdnManagerWindow = new BrowserWindow({ minWidth: 770, minHeight: 500 });
38 |
39 | sdnManagerWindow.loadURL(url.format({
40 | pathname: path.join(__dirname, "src/sdn-manager", "index.html"),
41 | protocol: "file:",
42 | slashes: true
43 | }));
44 |
45 | sdnManagerWindow.setTitle('SDN-Manager');
46 |
47 | sdnManagerWindow.on("closed", function () {
48 | sdnManagerWindow = null;
49 | if(!mainWindow) app.quit();
50 | });
51 | }
52 |
53 | app.on("ready", startMainWindow);
54 | // app.on("ready", startSDNManagerWindow); // DEV
55 |
56 | /* ---------------------------------------------------------- */
57 | /* ------------------------- EVENTS ------------------------- */
58 | /* ---------------------------------------------------------- */
59 |
60 | let _baseFolder = app.getPath("userData");
61 |
62 | function _runKatharaCommand(command){
63 | exec(command, (stderr) => { if(stderr) console.error(stderr) });
64 | }
65 |
66 | ipcMain.on("sdn:start", startSDNManagerWindow);
67 |
68 | /* ------------------------- SCRIPT ------------------------- */
69 |
70 | ipcMain.on("script:copy", function (_, script, filename) {
71 | let pathTemp = path.join(_baseFolder, filename);
72 | console.log("Saving script to " + pathTemp);
73 |
74 | fs.writeFileSync(pathTemp, script)
75 |
76 | console.log("Running " + pathTemp);
77 | exec("bash \"" + pathTemp + "\"");
78 | });
79 |
80 | ipcMain.on("script:execute", function () {
81 | let pathTemp = path.join(_baseFolder, "lab");
82 |
83 | console.log("Running LStart on " + pathTemp);
84 | _runKatharaCommand("kathara lstart -d \"" + pathTemp + "\"");
85 | });
86 |
87 | ipcMain.on("script:clean", function () {
88 | let pathTemp = path.join(_baseFolder, "lab");
89 |
90 | console.log("Running LClean on " + pathTemp);
91 | _runKatharaCommand("kathara lclean -d \"" + pathTemp + "\"");
92 |
93 | if(sdnManagerWindow) sdnManagerWindow.close();
94 | });
95 |
--------------------------------------------------------------------------------
/src/static/vendor/js/FileSaver.min.js:
--------------------------------------------------------------------------------
1 | /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
2 | var saveAs=saveAs||function(e){"use strict";if(typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),i="download"in r,o=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},a=/Version\/[\d\.]+.*Safari/.test(navigator.userAgent),f=e.webkitRequestFileSystem,u=e.requestFileSystem||f||e.mozRequestFileSystem,s=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},c="application/octet-stream",d=0,l=500,w=function(t){var r=function(){if(typeof t==="string"){n().revokeObjectURL(t)}else{t.remove()}};if(e.chrome){r()}else{setTimeout(r,l)}},p=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var i=e["on"+t[r]];if(typeof i==="function"){try{i.call(e,n||e)}catch(o){s(o)}}}},v=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob(["\ufeff",e],{type:e.type})}return e},y=function(t,s,l){if(!l){t=v(t)}var y=this,m=t.type,S=false,h,R,O=function(){p(y,"writestart progress write writeend".split(" "))},g=function(){if(R&&a&&typeof FileReader!=="undefined"){var r=new FileReader;r.onloadend=function(){var e=r.result;R.location.href="data:attachment/file"+e.slice(e.search(/[,;]/));y.readyState=y.DONE;O()};r.readAsDataURL(t);y.readyState=y.INIT;return}if(S||!h){h=n().createObjectURL(t)}if(R){R.location.href=h}else{var i=e.open(h,"_blank");if(i==undefined&&a){e.location.href=h}}y.readyState=y.DONE;O();w(h)},b=function(e){return function(){if(y.readyState!==y.DONE){return e.apply(this,arguments)}}},E={create:true,exclusive:false},N;y.readyState=y.INIT;if(!s){s="download"}if(i){h=n().createObjectURL(t);r.href=h;r.download=s;setTimeout(function(){o(r);O();w(h);y.readyState=y.DONE});return}if(e.chrome&&m&&m!==c){N=t.slice||t.webkitSlice;t=N.call(t,0,t.size,c);S=true}if(f&&s!=="download"){s+=".download"}if(m===c||f){R=e}if(!u){g();return}d+=t.size;u(e.TEMPORARY,d,b(function(e){e.root.getDirectory("saved",E,b(function(e){var n=function(){e.getFile(s,E,b(function(e){e.createWriter(b(function(n){n.onwriteend=function(t){R.location.href=e.toURL();y.readyState=y.DONE;p(y,"writeend",t);w(e)};n.onerror=function(){var e=n.error;if(e.code!==e.ABORT_ERR){g()}};"writestart progress write abort".split(" ").forEach(function(e){n["on"+e]=y["on"+e]});n.write(t);y.abort=function(){n.abort();y.readyState=y.DONE};y.readyState=y.WRITING}),g)}),g)};e.getFile(s,{create:false},b(function(e){e.remove();n()}),b(function(e){if(e.code===e.NOT_FOUND_ERR){n()}else{g()}}))}),g)}),g)},m=y.prototype,S=function(e,t,n){return new y(e,t,n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){if(!n){e=v(e)}return navigator.msSaveOrOpenBlob(e,t||"download")}}m.abort=function(){var e=this;e.readyState=e.DONE;p(e,"abort")};m.readyState=m.INIT=0;m.WRITING=1;m.DONE=2;m.error=m.onwritestart=m.onprogress=m.onwrite=m.onabort=m.onerror=m.onwriteend=null;return S}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!=null){define([],function(){return saveAs})}
--------------------------------------------------------------------------------
/src/sdn-manager/rules-utils/README.md:
--------------------------------------------------------------------------------
1 | https://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-actions-on-request-messages
2 |
3 | # MATCHES
4 | WHAT | EXAMPLE
5 | --- | ---
6 | {} | vuol dire match con ogni cosa
7 | {in_port: 1} | Match con la porta
8 | in_phy_port | {"in_phy_port": 5, "in_port": 3}
9 | metadata | {"metadata": 12345} or {"metadata": "0x1212/0xffff"}
10 | eth_type | {"eth_type": 2048}
11 | vlan_vid |
12 | vlan_pcp | {"vlan_pcp": 3, "vlan_vid": 3}
13 | ip_dscp | {"ip_dscp": 3, "eth_type": 2048}
14 | ip_ecn | {"ip_ecn": 0, "eth_type": 34525}
15 | ip_proto | {"ip_proto": 5, "eth_type": 34525}
16 | udp_src | {"udp_src": 2, "ip_proto": 17, "eth_type": 2048}
17 | udp_dst | {"udp_dst": 6, "ip_proto": 17, "eth_type": 2048}
18 | sctp_src | {"sctp_src": 99, "ip_proto": 132, "eth_type": 2048}
19 | sctp_dst | {"sctp_dst": 99, "ip_proto": 132, "eth_type": 2048}
20 | icmpv4_type | {"icmpv4_type": 5, "ip_proto": 1, "eth_type": 2048}
21 | icmpv4_code | {"icmpv4_code": 6, "ip_proto": 1, "eth_type": 2048}
22 | arp_op | {"arp_op": 3, "eth_type": 2054}
23 | arp_spa | {"arp_spa": "192.168.0.11", "eth_type": 2054}
24 | arp_tpa | {"arp_tpa": "192.168.0.44/24", "eth_type": 2054}
25 | arp_sha | {"arp_sha": "aa:bb:cc:11:22:33", "eth_type": 2054}
26 | arp_tha | {"arp_tha": "aa:bb:cc:11:22:33/00:00:00:00:ff:ff", "eth_type": 2054}
27 | ipv6_src | {"ipv6_src": "2001::aaaa:bbbb:cccc:1111", "eth_type": 34525}
28 | ipv6_dst | {"ipv6_dst": "2001::ffff:cccc:bbbb:1111/64", "eth_type": 34525}
29 | ipv6_flabel | {"ipv6_flabel": 2, "eth_type": 34525}
30 | icmpv6_type | {"icmpv6_type": 3, "ip_proto": 58, "eth_type": 34525}
31 | icmpv6_code | {"icmpv6_code": 4, "ip_proto": 58, "eth_type": 34525}
32 | ipv6_nd_target | {"ipv6_nd_target": "2001::ffff:cccc:bbbb:1111", "icmpv6_type": 135, "ip_proto": 58, "eth_type": 34525}
33 | ipv6_nd_sll | {"ipv6_nd_sll": "aa:bb:cc:11:22:33", "icmpv6_type": 135, "ip_proto": 58, "eth_type": 34525}
34 | ipv6_nd_tll | {"ipv6_nd_tll": "aa:bb:cc:11:22:33", "icmpv6_type": 136, "ip_proto": 58, "eth_type": 34525}
35 | mpls_tc | {"mpls_tc": 2, "eth_type": 34888}
36 | mpls_bos | {"mpls_bos": 1, "eth_type": 34888}
37 | pbb_isid | {"pbb_isid": 5, "eth_type": 35047} or{"pbb_isid": "0x05/0xff", "eth_type": 35047}
38 | tunnel_id | {"tunnel_id": 7} or {"tunnel_id": "0x07/0xff"}
39 | ipv6_exthdr | {"ipv6_exthdr": 3, "eth_type": 34525} or {"ipv6_exthdr": "0x40/0x1F0", "eth_type": 34525}
40 | pbb_uca | {"pbb_uca": 1, "eth_type": 35047}
41 |
42 | # ACTIONS
43 | WHAT | EXAMPLE
44 | --- | ---
45 | [] | vuol dire drop
46 | OUTPUT | [{type: "OUTPUT", port: 2}]
47 | COPY_TTL_OUT | {"type": "COPY_TTL_OUT"}
48 | COPY_TTL_IN | {"type": "COPY_TTL_IN"}
49 | SET_MPLS_TTL | {"type": "SET_MPLS_TTL", "mpls_ttl": 64}
50 | DEC_MPLS_TTL | {"type": "DEC_MPLS_TTL"}
51 | PUSH_VLAN | {"type": "PUSH_VLAN", "ethertype": 33024}
52 | POP_VLAN | {"type": "POP_VLAN"}
53 | SET_QUEUE | {"type": "SET_QUEUE", "queue_id": 7}
54 | GROUP | {"type": "GROUP", "group_id": 5}
55 | SET_NW_TTL | {"type": "SET_NW_TTL", "nw_ttl": 64}
56 | DEC_NW_TTL | {"type": "DEC_NW_TTL"}
57 | PUSH_PBB | {"type": "PUSH_PBB", "ethertype": 35047}
58 | POP_PBB | {"type": "POP_PBB"}
59 | COPY_FIELD | {"type": "COPY_FIELD", "n_bits": 32, "src_offset": 1, "dst_offset": 2, "src_oxm_id": "eth_src", "dst_oxm_id": "eth_dst"}
60 | METER | {"type": "METER", "meter_id": 3}
61 | EXPERIMENTER | {"type": "EXPERIMENTER", "experimenter": 101, "data": "AAECAwQFBgc=", "data_type": "base64"}
62 | WRITE_METADATA | {"type": "WRITE_METADATA", "metadata": 0x3, "metadata_mask": 0x3}
63 | METER | {"type": "METER", "meter_id": 3}
64 | WRITE_ACTIONS | {"type": "WRITE_ACTIONS", actions":[{"type":"POP_VLAN",},{ "type":"OUTPUT", "port": 2}]}
65 | CLEAR_ACTIONS | {"type": "CLEAR_ACTIONS"}
66 |
67 | # ETHERTYPES
68 | HEX | PROTOCOL | DECIMAL
69 | --- | --- | ---
70 | 0x0800 | IPv4 | 2048
71 | 0x0806 | ARP | 2054
72 | 0x8847 | MPLS unicast | 34887
73 | 0x8848 | MPLS multicast | 34888
74 | 0x88cc | LLDP | 35020
--------------------------------------------------------------------------------
/src/style/sdn-home.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --main-color: #337ab7;
3 | --secondary-color: #5b8fbd;
4 | --disclaimer-color: rgb(255, 222, 222);
5 | }
6 |
7 | .browserupgrade {
8 | margin: 0.2em 0;
9 | background: #ffff00;
10 | color: black;
11 | padding: 0.2em 0;
12 | }
13 |
14 | .hidden {
15 | display: none;
16 | }
17 |
18 | .disclaimer {
19 | position: fixed;
20 | padding: 13px 20px;
21 | background-color: var(--disclaimer-color);
22 | z-index: 1;
23 | top: 7rem;
24 | left: 2rem;
25 | border-radius: 15px;
26 | }
27 |
28 | @media (min-width: 1300px), (min-height: 900px) { /*TODO: min-height non funziona*/
29 | .disclaimer:first-child{
30 | display: none;
31 | }
32 | }
33 |
34 | /* ------------------------------------------------------- */
35 | /* ----------------------- CONNECT ----------------------- */
36 | /* ------------------------------------------------------- */
37 |
38 | #connect {
39 | position: fixed;
40 | height: 100%;
41 | width: 100%;
42 | background-color: white;
43 | z-index: 10;
44 | padding: 20% 0;
45 | }
46 |
47 | /* ------------------------------------------------------- */
48 | /* ----------------------- TOP BAR ----------------------- */
49 | /* ------------------------------------------------------- */
50 |
51 | #topbar, #secondaryTopbar {
52 | display: flex;
53 | width: 100%;
54 | height: 5rem;
55 | padding: .6rem;
56 | background: white;
57 | box-shadow: 0px 1px 1px 0 darkgrey;
58 | }
59 |
60 | #topbar button,
61 | #secondaryTopbar button {
62 | margin: 0 .3rem;
63 | }
64 |
65 | #secondaryTopbar {
66 | position: absolute;
67 | top: 0;
68 | z-index: -1;
69 | }
70 |
71 | /* ------------------------------------------------------ */
72 | /* ------------------------ MAIN ------------------------ */
73 | /* ------------------------------------------------------ */
74 |
75 | #main {
76 | overflow: auto;
77 | padding: 5px 2px;
78 | }
79 |
80 | #main-grid {
81 | display: grid;
82 | grid-template-columns: 350px 900px auto;
83 | column-gap: 5px;
84 | }
85 |
86 | /* ------------------------------------------------------- */
87 | /* ------------------------- SDN ------------------------- */
88 | /* ------------------------------------------------------- */
89 |
90 | #sdnGraph {
91 | border: 1px solid lightgray;
92 | background-color: white;
93 | background-image: url(../static/images/background.png);
94 | }
95 |
96 | #confirm-buttons {
97 | position: relative;
98 | border: 1px solid darkgray;
99 | padding: 1rem;
100 | border-radius: 10px;
101 | display: inline-block;
102 | left: 40%;
103 | background: white;
104 | top: -7rem;
105 | transition: top .3s, opacity .4s;
106 | }
107 |
108 | /* ------------------------------------------------------ */
109 | /* ----------------------- INPUTS ----------------------- */
110 | /* ------------------------------------------------------ */
111 |
112 | textarea {
113 | width: 100%;
114 | font-size: 14px;
115 | color: #555;
116 | padding: 1em;
117 | border: 1px solid lightgray;
118 | display: block;
119 | margin: 5px 0;
120 | }
121 |
122 | textarea,
123 | textarea:disabled {
124 | background-color: #fafafa;
125 | }
126 |
127 | a {
128 | cursor: pointer;
129 | }
130 |
131 | td, th {
132 | border: 1px solid lightgray;
133 | padding: 10px;
134 | }
135 |
136 | th {
137 | color: white;
138 | background-color: var(--main-color);
139 | }
140 |
141 | label {
142 | margin-bottom: .3em;
143 | }
144 |
145 | p.btn,
146 | span.btn {
147 | width: 100%;
148 | padding: 2% 4%;
149 | }
150 |
151 | /* ------------------------------------------------------ */
152 | /* ---------------------- SECTIONS ---------------------- */
153 | /* ------------------------------------------------------ */
154 |
155 | #labelsSection,
156 | #switchDetailsSection,
157 | #controllerAndRulesSection {
158 | min-height: 75rem;
159 | white-space: normal;
160 | cursor: auto;
161 | padding: 1rem;
162 | border: 1px solid lightgray;
163 | max-height: 800px;
164 | background-color: white;
165 | width: fit-content;
166 | }
167 |
168 | .tab {
169 | cursor: pointer;
170 | padding: 5px;
171 | }
172 |
173 | .tab.active {
174 | border-bottom: 2px solid lightblue;
175 | }
176 |
177 | #switchDetailsSection > a,
178 | #controllerAndRulesSection > a {
179 | font-size: large;
180 | float: right;
181 | color: red;
182 | margin-bottom: 5px;
183 | }
--------------------------------------------------------------------------------
/src/style/nlg-home.css:
--------------------------------------------------------------------------------
1 | /* -------------------------------------------------------- */
2 | /* ---------------------- FLOAT LEFT ---------------------- */
3 | /* -------------------------------------------------------- */
4 |
5 | #left-controls {
6 | position: fixed;
7 | left: 5px;
8 | top: 55px;
9 | }
10 |
11 | #labInfo {
12 | width: 206px;
13 | margin: 1em 0;
14 | background-color: white;
15 | }
16 |
17 | @media (max-width: 1255px) {
18 | #left-controls {
19 | position: unset;
20 | }
21 |
22 | .xs-floating-right {
23 | z-index: 20;
24 | position: fixed;
25 | top: 50px;
26 | right: 5px;
27 | background-color: white;
28 | width: 230px;
29 | }
30 |
31 | .xs-floating-right td {
32 | padding: 5px 10px;
33 | }
34 | }
35 |
36 | /* ------------------------------------------------------- */
37 | /* ----------------------- MINIMAP ----------------------- */
38 | /* ------------------------------------------------------- */
39 |
40 | #minimap {
41 | position: fixed;
42 | top: 90px;
43 | right: 0;
44 | margin-right: 10px;
45 |
46 | width: 150px;
47 | height: calc(100% - 150px);
48 |
49 | background-color: white;
50 | }
51 |
52 | @media (max-width: 1055px) {
53 | #minimap {
54 | display: none;
55 | }
56 | }
57 |
58 | @media (max-width: 1255px) {
59 | #minimap {
60 | top: 150px;
61 | height: calc(100% - 180px);
62 | }
63 |
64 | .mock-labinfo {
65 | display: block;
66 | }
67 | }
68 |
69 | #minimap-body {
70 | max-height: calc(100% - 35px);
71 | overflow-x: hidden;
72 | overflow-y: auto;
73 | border: 1px solid black;
74 | transition: transform 0.2s ease-in-out;
75 | }
76 |
77 | .mock {
78 | display: block;
79 | overflow: hidden;
80 | text-overflow: ellipsis;
81 | text-align: left;
82 | margin: 3px;
83 | font-size: 80%;
84 | border-radius: 0;
85 | }
86 |
87 | .mock-main-menu {
88 | z-index: 10c;
89 | position: absolute;
90 | width: 100%;
91 | height: 5px;
92 | background-color: var(--main-color);
93 | margin: 0;
94 | }
95 |
96 | .mock-submenu {
97 | box-shadow: 0 5px 0px 0px #5b8fbd;
98 | }
99 |
100 | .mock-preview-text {
101 | margin-top: 7px;
102 | font-size: 40%;
103 | }
104 |
105 | .mock-disclaimer {
106 | width: 80%;
107 | height: 4px;
108 | background-color: var(--disclaimer-color);
109 | margin: 3px;
110 | }
111 |
112 | .mock-labinfo {
113 | display: none;
114 | width: 20px;
115 | height: 30px;
116 | border-top: 3px solid #337ab7;
117 | }
118 |
119 | @media (max-width: 1255px) {
120 | .mock-labinfo {
121 | display: block;
122 | }
123 | }
124 |
125 | .mock-machine:first-child {
126 | border-top: 3px solid var(--main-color);
127 | margin-top: 3px;
128 | }
129 |
130 | .mock-machine {
131 | height: 30px;
132 | background-color: rgba(0, 0, 0, 0.01);
133 | color: rgba(0, 0, 0, 0.45);
134 | margin-bottom: 1px;
135 | margin-top: 0px;
136 | transform: scale(1, 1);
137 | transition: transform 0.2s ease-in-out;
138 | }
139 |
140 | .mock-id { /* */
141 | border-radius: 10px;
142 | background-color: rgba(0, 0, 0, 0.65);
143 | color: white;
144 | padding: 1px 3px;
145 | margin-right: 4px;
146 | }
147 |
148 | .mock-preview-bash {
149 | height: 40px;
150 | border: 1px solid rgba(0, 0, 0, 0.2);
151 | }
152 |
153 | /* ------------------------------------------------------ */
154 | /* ---------------------- CONTENTS ---------------------- */
155 | /* ------------------------------------------------------ */
156 |
157 | #home {
158 | margin: 0 180px 0 250px;
159 | max-width: 1000px;
160 | }
161 |
162 | @media (max-width: 1255px) {
163 | #home {
164 | margin: 0 180px 0 2em;
165 | }
166 | }
167 |
168 | @media (max-width: 1055px) {
169 | #home {
170 | margin: 0 2em;
171 | width: inherit;
172 | max-width: 1055px;
173 | }
174 | }
175 |
176 | @media (min-width: 1520px) {
177 | #home {
178 | margin: 0 auto;
179 | }
180 | }
181 |
182 | /* ------ TABLES ------ */
183 |
184 | #netkit {
185 | margin-top: 2em;
186 | }
187 |
188 | #netkit td {
189 | max-width: 300px;
190 | padding: 3px 10px;
191 | }
192 |
193 | #netkit tr {
194 | text-align: left;
195 | vertical-align: 0;
196 | }
197 |
198 | td, th {
199 | border: 1px solid lightgray;
200 | padding: 5px 10px;
201 | }
202 |
203 | th {
204 | color: white;
205 | background-color: var(--main-color);
206 | padding: 10px;
207 | }
208 |
209 | label {
210 | margin-bottom: .3em;
211 | }
212 |
213 | p.btn,
214 | span.btn {
215 | width: 100%;
216 | }
217 |
218 | .gateways > div:last-child > hr {
219 | display: none;
220 | }
221 |
222 | /* ------ BASH ------ */
223 |
224 | .bash {
225 | margin: 3em 0;
226 | }
227 |
228 | /* @media (min-width: 1600px) {
229 | .bash {
230 | position: fixed;
231 | right: 15px;
232 | top: 10px;
233 |
234 | width: 300px;
235 | }
236 | }
237 |
238 | @media (min-width: 1700px) {
239 | .bash {
240 | right: 65px;
241 | width: 350px;
242 | }
243 | }
244 |
245 | @media (min-width: 1830px) {
246 | .bash {
247 | right: 25px;
248 | width: 540px;
249 | }
250 | } */
--------------------------------------------------------------------------------
/src/lab-generator/helper.js:
--------------------------------------------------------------------------------
1 | function lastElem(arr) {
2 | return arr[arr.length - 1];
3 | }
4 |
5 | function toggle_tab(href) {
6 | toggle_submenu(-1);
7 |
8 | for (let elem of document.querySelectorAll(".tab-pane")) {
9 | elem.classList.remove("active");
10 | }
11 | for (let elem of document.querySelectorAll(".tab-label")) {
12 | elem.classList.remove("active");
13 | }
14 |
15 | let currentTab = document.getElementById(href);
16 | currentTab.classList.add("active");
17 |
18 | let rightControls = document.getElementById("left-controls");
19 | if (href == "home") {
20 | rightControls.classList.remove("ng-hide");
21 | } else {
22 | rightControls.classList.add("ng-hide");
23 | }
24 |
25 | if (href == "home") _toggleActive(0);
26 | if (href == "graph") _toggleActive(1);
27 | if (href == "sdn") _toggleActive(5);
28 | }
29 |
30 | function _toggleActive (offset){
31 | let li = document.querySelectorAll("#main-nav > ul.navbar-nav > li");
32 | for(let i = 0; i < li.length; i++) {
33 | if(i === offset) li[i].classList.add("active");
34 | else li[i].classList.remove("active");
35 | }
36 | }
37 |
38 | function toggle_submenu(number) {
39 | let hidden = false;
40 | for (let i = 0; i < 3; i++) {
41 | if (i == number) {
42 | let current_submenu = document.getElementById("submenu-" + i);
43 | current_submenu.classList.toggle("ng-hide");
44 | document.querySelectorAll("#main-nav > ul.navbar-nav > li")[2+i].classList.add("active");
45 | if(current_submenu.classList.contains("ng-hide")) {
46 | document.querySelectorAll("#main-nav > ul.navbar-nav > li")[2+i].classList.remove("active");
47 | hidden = true;
48 | }
49 | } else {
50 | document.querySelectorAll("#main-nav > ul.navbar-nav > li")[2+i].classList.remove("active");
51 | document.getElementById("submenu-" + i).classList.add("ng-hide");
52 | }
53 | }
54 |
55 | let mockMainMenu = document.getElementById("mock-main-menu");
56 | if (hidden || number == -1) {
57 | mockMainMenu.classList.remove("mock-submenu");
58 | } else {
59 | mockMainMenu.classList.add("mock-submenu");
60 | }
61 | }
62 |
63 | function close_modal(id) {
64 | document.getElementById(id).classList.add("ng-hide");
65 | }
66 |
67 | function isElectron() {
68 | return window && window.process && window.process.type;
69 | }
70 |
71 | function copyLab(){
72 | let script = document.getElementById("sh_script").value;
73 | electron.ipcRenderer.send("script:copy", script, "script.sh");
74 | }
75 |
76 | function executeStart(e) {
77 | e.preventDefault();
78 |
79 | if(!document.getElementById("lstart").classList.contains("disabledLink")){
80 | let sdnTabButton = document.getElementById("connect");
81 |
82 | copyLab();
83 | toggle_submenu(-1);
84 | _executeGeneric(e, "execute");
85 |
86 | if (!sdnTabButton.classList.contains("hidden")
87 | && document.querySelector("#netkit input[data-ng-model=\"machine.ryu.rest\"]").checked
88 | && document.querySelector("#netkit input[data-ng-model=\"machine.ryu.topology\"]").checked)
89 | sdnTabButton.classList.remove("disabledLink");
90 | }
91 | }
92 |
93 | function executeClean(e) {
94 | if(!document.getElementById("lclean").classList.contains("disabledLink")){
95 | toggle_submenu(-1);
96 | toggle_tab("home");
97 |
98 | _executeGeneric(e, "clean");
99 | if (!document.getElementById("connect").classList.contains("disabledLink")) {
100 | document.getElementById("connect").classList.add("disabledLink");
101 | }
102 | }
103 | }
104 |
105 | function startSDNManager(e){
106 | let sdnTabButton = document.getElementById("connect");
107 | if (!sdnTabButton.classList.contains("disabledLink")){
108 | electron.ipcRenderer.send("sdn:start");
109 | }
110 | }
111 |
112 | function _executeGeneric(e, command) {
113 | e.preventDefault();
114 | let modal = document.getElementById("command-modal");
115 | modal.classList.remove("ng-hide");
116 | setTimeout(
117 | () => document.querySelector("#command-modal .modal-footer button").disabled = false,
118 | 2000
119 | );
120 | electron.ipcRenderer.send("script:" + command);
121 | }
122 |
123 | function setNetworkOptions() {
124 | let smoothEnabled = document.getElementById("smoothEnabled");
125 | let smoothType = document.getElementById("smoothType");
126 | let physicsEnabled = document.getElementById("physicsEnabled");
127 | let physicsGConstant = document.getElementById("physicsGravitationalConstant");
128 | document.getElementById("physicsGravitationalConstantValue").value = parseInt(physicsGConstant.value);
129 |
130 | let edges = {}
131 | let physics = {}
132 |
133 | if (smoothEnabled.checked) {
134 | edges = {
135 | smooth: {
136 | type: smoothType.value
137 | }
138 | }
139 | } else {
140 | edges = {
141 | smooth: false
142 | }
143 | }
144 |
145 | if (physicsEnabled.checked) {
146 | physics = {
147 | enabled: true,
148 | barnesHut: {
149 | gravitationalConstant: parseInt(physicsGConstant.value)
150 | }
151 | }
152 | } else {
153 | physics = {
154 | enabled: false
155 | }
156 | }
157 |
158 | network.setOptions({edges, physics})
159 | }
--------------------------------------------------------------------------------
/src/sdn-manager/vue-components/switchDetailsSection.js:
--------------------------------------------------------------------------------
1 | const switchDetailsSection = new Vue({
2 | el: "#switchDetailsSection",
3 | data: {
4 | visible: false,
5 |
6 | device: null,
7 | packetRules: [],
8 | labelRules: [],
9 |
10 | activeSection: 1, // 1: Pacchetti 2: Statistiche 3: Inspect
11 | responseTextareaContent: "",
12 |
13 | columnsVisibility: {
14 | idleTimeout: false,
15 | hardTimeout: false,
16 | tableId: false,
17 | statistics: false
18 | }
19 | },
20 | methods: {
21 | open(device){
22 | controllerAndRulesSection.close();
23 |
24 | this.close();
25 | this.visible = true;
26 | this.device = device;
27 |
28 | let rules = dataStore.getSwitchRules(device);
29 | if(rules){
30 | this.packetRules = rules.filter(rule =>
31 | !( rule.matches.some(match => match.label)
32 | || rule.actions.some(action => action.label)
33 | ));
34 | this.labelRules = rules.filter(
35 | rule => !this.packetRules.includes(rule));
36 | }
37 | },
38 |
39 | close(){
40 | this.visible = false;
41 | this.device = null;
42 | this.packetRules = [];
43 | this.labelRules = [];
44 | this.activeSection = 1;
45 | this.responseTextareaContent = "";
46 | },
47 |
48 | switchTab(tabNumber){
49 | this.activeSection = tabNumber;
50 | if(tabNumber == 2) this.updateSVG();
51 | },
52 |
53 | /* ------------------------------------------------------ */
54 | /* ------------------------ RULES ----------------------- */
55 | /* -------------------- (& controller) ------------------ */
56 |
57 | submitRule(rule, callback){
58 | ryuActions.addFlowEntry(rule, (err) => {
59 | if(!err){
60 | rule.submitted = true;
61 | rule.edited = false;
62 | rule.deleted = false;
63 |
64 | if(callback) callback();
65 | } else {
66 | console.error(err.message);
67 | }
68 | });
69 | },
70 |
71 | updateRule(rule){
72 | this.removeSubmittedRule(rule,
73 | () => this.submitRule(rule));
74 | },
75 |
76 | removeSubmittedRule(rule, callback){
77 | ryuActions.removeFlowEntry(rule,
78 | () => {
79 | rule.submitted = false;
80 | rule.edited = false;
81 | rule.deleted = true;
82 |
83 | if(callback) callback();
84 | });
85 | },
86 |
87 | highlightRuleOnGraph(index){
88 | highlightSegmentOnGraph(
89 | this.device,
90 | this.labelRules[index].matches.find(match => match.name == "source port").value,
91 | this.labelRules[index].actions.find(action => action.name == "forward to port").value
92 | );
93 | },
94 |
95 | /* ----------------------------------------------------- */
96 | /* ------------------------ SVG ------------------------ */
97 | /* ----------------------------------------------------- */
98 |
99 | updateSVG(){
100 | let svg = d3.select("#switchDetailsSection svg");
101 | svg.node().innerHTML = "";
102 | let padding = 45;
103 | let width = svg.attr("width") - padding;
104 | let height = svg.attr("height") - padding;
105 |
106 | let counter = 1;
107 | let data = this.packetRules.concat(this.labelRules).map(function(el){ return { num: counter++, stats: el.stats };}).slice(1);
108 |
109 | let x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
110 | y = d3.scaleLinear().rangeRound([height, 0]);
111 |
112 | let g = svg.append("g")
113 | .attr("transform", "translate(20, 5)");
114 |
115 | x.domain(data.map(function (d) { return d.num; }));
116 | y.domain([0, d3.max(data, function (d) { return d.stats; })]);
117 |
118 | g.append("g")
119 | .attr("class", "x-axis")
120 | .attr("transform", "translate(0," + (height + 5) + ")")
121 | .call(d3.axisBottom(x));
122 |
123 | g.append("g")
124 | .attr("class", "y-axis")
125 | .call(d3.axisLeft(y).ticks(10))
126 | .append("text")
127 | .attr("transform", "rotate(-90)")
128 | .attr("y", 6)
129 | .attr("dy", "0.71em")
130 | .attr("text-anchor", "end")
131 | .text("stats");
132 |
133 | g.selectAll(".bar")
134 | .data(data)
135 | .enter().append("rect")
136 | .attr("class", "bar")
137 | .attr("x", function (d) { return x(d.num); })
138 | .attr("y", function (d) { return y(d.stats); })
139 | .attr("width", x.bandwidth())
140 | .attr("height", function (d) { return height - y(d.stats); })
141 | .attr("fill", "var(--main-color)");
142 | },
143 |
144 | /* ----------------------------------------------------- */
145 | /* ----------------------- OTHER ----------------------- */
146 | /* ----------------------------------------------------- */
147 |
148 | popupModal(index, group){
149 | if(index != undefined && group != undefined){
150 | let rules = (group == "packetRules" ? this.packetRules : this.labelRules);
151 | ruleModal.editRule(rules[index]);
152 | } else ruleModal.open(this.device);
153 | },
154 |
155 | askRyu(action){
156 | let responsePromise = (action == "stats/table") ?
157 | ryuActions.getTablesFilteredNotEmpty(this.device)
158 | : ryuActions.getFromSwitchCustom(action, this.device);
159 |
160 | responsePromise.then(response => {this.responseTextareaContent = JSON.stringify(response, null, 4);});
161 | }
162 | },
163 |
164 | components: {
165 | "rule-element": {
166 | props: ["name", "value", "label"],
167 | template:
168 | "" +
169 | "
" +
170 | "" +
172 | " " +
173 | "{{ name }} " +
174 | "{{ value }}" +
175 | ""
176 | },
177 | }
178 | });
--------------------------------------------------------------------------------
/src/sdn-manager/vue-components/controllerAndRulesSection.js:
--------------------------------------------------------------------------------
1 | const controllerAndRulesSection = new Vue({
2 | el: "#controllerAndRulesSection",
3 | data: {
4 | visible: false,
5 | // HOW TO
6 | howtoVisibility: false,
7 | // CONTROLLER
8 | controllerSection: {
9 | visible: false,
10 | request: {
11 | method: "GET",
12 | path: "",
13 | params: "",
14 | paramsValidity: true
15 | },
16 | output: "",
17 | history: []
18 | },
19 | // SHOW RULES
20 | rulesSection: {
21 | visible: 0,
22 | rules: "",
23 | submittedRules: "",
24 | filter: ""
25 | }
26 | },
27 | methods: {
28 | open(sectionNumber) {
29 | switchDetailsSection.close();
30 |
31 | this.close();
32 | this.visible = true;
33 |
34 | if (sectionNumber == 1) {
35 | this.howtoVisibility = true;
36 | } else if (sectionNumber == 2) {
37 | this.controllerSection.visible = true;
38 | } else if (sectionNumber == 3) {
39 | this.rulesSection.visible = 1; // 0: nascosto; 1: regole simulate; 2: regole installate
40 | this.showRulesFilteredByDevice();
41 | }
42 | },
43 |
44 | close() {
45 | this.visible = false;
46 |
47 | this.howtoVisibility = false;
48 | this.controllerSection.visible = false;
49 |
50 | this.rulesSection.visible = 0;
51 | this.rulesSection.filter = "";
52 | },
53 |
54 | showRulesFilteredByDevice() {
55 | let rules = this.rulesSection.visible == 1 ? dataStore.getRules() : dataStore.getSubmittedRules();
56 | rules = this._makeCleanRulesFromExistingOnes(rules);
57 |
58 | let representation = JSON.stringify(
59 | rules.filter(rule =>
60 | this.rulesSection.filter ? (rule.device == this.rulesSection.filter) : true),
61 | null, 4
62 | );
63 |
64 | if(this.rulesSection.visible == 1) this.rulesSection.rules = representation;
65 | else if(this.rulesSection.visible == 2) this.rulesSection.submittedRules = representation;
66 | },
67 |
68 | showSimulatedRules(){
69 | this.rulesSection.filter = "";
70 | this.rulesSection.visible = 1;
71 | this.showRulesFilteredByDevice();
72 | },
73 |
74 | showSubmittedRules(){
75 | this.rulesSection.filter = "";
76 | this.rulesSection.visible = 2;
77 | this.showRulesFilteredByDevice();
78 | },
79 |
80 | exportJSON(){
81 | let rules = this._makeCleanRulesFromExistingOnes(dataStore.getRules());
82 | downloadString(JSON.stringify(rules), "rules.JSON");
83 | },
84 |
85 | importRules(){
86 | let fileInput = document.getElementById("rules-file");
87 | try {
88 | let filePath = fileInput.files[0];
89 | if (filePath) {
90 | let fileReader = new FileReader();
91 | fileReader.onloadend = function (event) {
92 | try {
93 | let res = event.target.result;
94 | let rules = JSON.parse(res
95 | .substring(res.indexOf("[")));
96 | dataStore.importRules(rules);
97 | }
98 | catch (err) {
99 | alert("Error: " + err);
100 | }
101 | };
102 | fileInput.value = "";
103 | fileReader.readAsBinaryString(filePath);
104 | } else {
105 | fileInput.click();
106 | }
107 | }
108 | catch (err) {
109 | alert("Error in File Reader: " + err);
110 | }
111 | },
112 |
113 | _makeCleanRulesFromExistingOnes(rules){
114 | rules = JSON.parse(JSON.stringify(rules));
115 | for(let rule of rules){
116 | if(rule.stats != undefined) delete rule.stats;
117 | if(rule.deleted != undefined) delete rule.deleted;
118 | if(rule.submitted != undefined) delete rule.submitted;
119 | if(rule.edited != undefined) delete rule.edited;
120 | if(rule.openflowRules != undefined) delete rule.openflowRules;
121 | }
122 | return rules;
123 | },
124 |
125 | makeRequest(){
126 | arguments[0].preventDefault();
127 | if(this.controllerSection.request.path
128 | && !(this.controllerSection.request.method == "POST" && this.controllerSection.request.params == "")){
129 | try {
130 | let params;
131 | if(this.controllerSection.request.method == "POST"){
132 | params = JSON.parse(this.controllerSection.request.params);
133 | } else params = {};
134 |
135 | this.controllerSection.output = "Request sent...";
136 | ryuActions.makeCustom(
137 | this.controllerSection.request.method,
138 | this.controllerSection.request.path,
139 | params,
140 | (response) => { this._setHTTPResponse(response); }
141 | );
142 |
143 | this._addToHistory();
144 |
145 | this.controllerSection.request.path = "";
146 | this.controllerSection.request.params = "";
147 |
148 | } catch (err) {
149 | this.controllerSection.output = "Error while parsing parameteres";
150 | }
151 | }
152 | },
153 |
154 | repeat(index){
155 | let historyElement = this.controllerSection.history[index];
156 | this.controllerSection.request.method = historyElement.method;
157 | this.controllerSection.request.path = historyElement.path;
158 | },
159 |
160 | checkParams(){
161 | try {
162 | JSON.parse(this.controllerSection.request.params);
163 | this.controllerSection.request.paramsValidity = true;
164 | } catch (e){
165 | this.controllerSection.request.paramsValidity = false;
166 | }
167 | },
168 |
169 | _setHTTPResponse(response){
170 | try {
171 | this.controllerSection.output =
172 | JSON.stringify(
173 | JSON.parse(response), null, 4
174 | );
175 | } catch (e){
176 | this.controllerSection.output = response;
177 | }
178 | },
179 |
180 | _addToHistory(){
181 | let historyElement = {
182 | method: this.controllerSection.request.method,
183 | path: this.controllerSection.request.path
184 | };
185 |
186 | this.controllerSection.history.unshift(historyElement);
187 |
188 | if(this.controllerSection.history.length > 5)
189 | this.controllerSection.history.pop();
190 | }
191 | }
192 | });
--------------------------------------------------------------------------------
/src/sdn-manager/dataStore.js:
--------------------------------------------------------------------------------
1 | const dataStore = {
2 | _katharaConfig: null,
3 | simulation: null,
4 | _rules: [],
5 |
6 | _path: {
7 | steps: [],
8 | pendingStep: null,
9 | startsFromEdge: false,
10 | endsOnEdge: false
11 | },
12 |
13 | set(katharaConfig){
14 | this._katharaConfig = katharaConfig;
15 | katharaConfig.forEach(machine => this.retrieveRulesOnTheSwitch(machine.name));
16 | },
17 |
18 | isReady(){ return this._katharaConfig !== null},
19 |
20 | /* --------------------------------------------- */
21 | /* ------------------- PATHS ------------------- */
22 | /* --------------------------------------------- */
23 |
24 | appendPathStep(options) {
25 | // options è: {device, ingressPort || egressPort }. Ogni campo è string || number
26 | if (!this._path.pendingStep) {
27 | this._path.pendingStep = options;
28 | } else {
29 | let step = {
30 | device: options.device,
31 | ingressPort: this._path.pendingStep.ingressPort,
32 | egressPort: options.egressPort,
33 | isStart: false,
34 | isEnd: false
35 | };
36 | this._path.pendingStep = null;
37 | this._path.steps.push(step);
38 | }
39 | },
40 |
41 | pathHasAtLeastOneStep() {
42 | return this._path.steps.length > 0;
43 | },
44 |
45 | discardPath() {
46 | if (this._path.steps.length)
47 | this._path.steps = [];
48 | this._path.pendingStep = null;
49 | this._path.startsFromEdge = false;
50 | this._path.endsOnEdge = false;
51 | },
52 |
53 | setEdgeProperties(startsFromEdge, endsOnEdge){
54 | if(startsFromEdge) this._path.startsFromEdge = true;
55 | if(endsOnEdge) this._path.endsOnEdge = true;
56 | },
57 |
58 | getPath() {
59 | if (this.pathHasAtLeastOneStep()){
60 | if(this._path.startsFromEdge) this._path.steps[0].isStart = true;
61 | if(this._path.endsOnEdge) this._path.steps[this._path.steps.length - 1].isEnd = true;
62 |
63 | return this._path.steps;
64 | }
65 | },
66 |
67 | /* ----------------------------------------------- */
68 | /* -------------------- RULES -------------------- */
69 | /* ----------------------------------------------- */
70 |
71 | /**
72 | * Crea una nuova regola e la memorizza.
73 | * Il numero di parametri di questo metodo non è stabilito, ma in base al loro numero
74 | * è determinato il loro significato:
75 | * 3 parametri => device, matches, actions
76 | * 7 parametri => device, matches, actions, priority, table, idleTimeout, hardTimeout
77 | * 8 parametri => device, matches, actions, priority, table, idleTimeout, hardTimeout, stats
78 | *
79 | * Nel caso di 8 parametri si suppone che la regola sia stata creata a partire da una regola OpenFlow,
80 | * per cui l'ultimo parametro è la statistica di attivazioni della regola nell'ambiente Kathara.
81 | */
82 | createAndStoreRule(...values) {
83 | let rule = ruleUtils.simulatedRules.createNewRule(...values);
84 | this.storeRule(rule);
85 | return rule;
86 | },
87 |
88 | storeRule(rule){
89 | ruleUtils.simulatedRules.addAPointerToLabelInTheRuleMPLSFields_andAddLablesToLabelsSection(rule);
90 | this._rules.push(rule);
91 | },
92 |
93 | /* ------------------- IMPORT ------------------- */
94 |
95 | importRules(rules){
96 | for (let rule of rules){
97 | let newRule = this.createAndSaveRule(rule.device, rule.matches, rule.actions, rule.priority, rule.table, rule.idleTimeout, rule.hardTimeout);
98 | ruleUtils.simulatedRules.addAPointerToLabelInTheRuleMPLSFields_andAddLablesToLabelsSection(newRule, true); // TODO: Controlla che funzioni
99 | }
100 | },
101 |
102 | /* ------------------------------------------------ */
103 | /* ------------------ CONTROLLER ------------------ */
104 | /* ------------------- (& rules) ------------------- */
105 |
106 | submitAllRules() {
107 | if(confirm("Every new/edited rule is going to be installed in the controller. Would you like to procede?")){
108 | this.getRules().forEach(rule => {
109 | if(!rule.submitted || rule.edited){
110 | ryuActions.addFlowEntry(rule, function(err){
111 | if(!err){
112 | rule.submitted = true;
113 | rule.edited = false;
114 | rule.deleted = false;
115 | }
116 | });
117 | } else if(rule.deleted && rule.submitted){
118 | ryuActions.removeFlowEntry(rule, () => {
119 | rule.submitted = false;
120 | });
121 | }
122 | });
123 | }
124 | },
125 |
126 | retrieveRulesOnTheSwitch(switchName){
127 | ryuActions.getSwitchRules(switchName).then(rules => {
128 | let simulatedRules = rules.map(rule =>
129 | ruleUtils.simulatedRules.makeSimulatedRuleFromOpenFlowOne(switchName, rule));
130 |
131 | let mergedRules = ruleUtils.simulatedRules.mergeMPLSRulesWithDifferentProtocolMatch(simulatedRules);
132 | mergedRules.forEach(newRule => this.storeRule(newRule));
133 | });
134 | },
135 |
136 | /* ---------------------------------------------- */
137 | /* --------------- NETWORK CONFIG --------------- */
138 | /* ---------------------------------------------- */
139 |
140 | getDeviceInfo(deviceName) {
141 | let deviceInfos = this._katharaConfig.find(
142 | deviceInfos => deviceInfos.name == deviceName
143 | );
144 | return {
145 | interfaces: deviceInfos.interfaces.if.map(function (interfaccia) {
146 | return {
147 | number: interfaccia.eth.number,
148 | domain: interfaccia.eth.domain,
149 | ip: interfaccia.ip
150 | };
151 | }),
152 | name: deviceInfos.name,
153 | type: deviceInfos.type
154 | };
155 | },
156 |
157 | /* --------------------------------------------- */
158 | /* ------------- GETTERS & SETTERS ------------- */
159 | /* --------------------------------------------- */
160 |
161 | getSimulation() {
162 | return this.simulation;
163 | },
164 |
165 | setSimulation(simulation) {
166 | this.simulation = simulation;
167 | },
168 |
169 | getRules(){
170 | this._rules = this._rules.filter(rule => !rule.deleted || rule.submitted);
171 | return this._rules;
172 | },
173 |
174 | getSwitchRules(deviceName) {
175 | return this.getRules().filter(rule => rule.device == deviceName);
176 | },
177 |
178 | getSubmittedRules(){
179 | return this.getRules().filter(rule => rule.submitted);
180 | }
181 | }
--------------------------------------------------------------------------------
/src/lab-generator/controller.js:
--------------------------------------------------------------------------------
1 | var changed = true;
2 |
3 | var app = angular.module("napp", []);
4 |
5 | app.config(["$compileProvider",
6 | function ($compileProvider) {
7 | $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|local|data):/);
8 | }
9 | ]);
10 |
11 | app.controller("nc", function ($location, $anchorScroll, $scope) {
12 |
13 | $scope.app = "include/app.html";
14 | $scope.labInfo = JSON.clone(labInfo);
15 | $scope.netkit = [];
16 | $scope.counter = 0;
17 | $scope.labInfo.toggle = "enable";
18 |
19 | $scope.scrollTo = function (e, hash) {
20 | e.preventDefault();
21 | $location.hash(hash);
22 | $anchorScroll();
23 | };
24 |
25 | $scope.addMachine = function () {
26 | $scope.counter++;
27 | var p = JSON.clone(backbone);
28 | p.row = $scope.counter;
29 | p._uid = Math.floor((Math.random() * (1000 ** 5)) + 1);
30 | $scope.netkit.push(p);
31 |
32 | changed = true;
33 | };
34 |
35 | $scope.addMachine();
36 |
37 | $scope.removeMachine = function () {
38 | if ($scope.netkit.length > 1 && confirm("Are you sure you want to remove the machine?")) {
39 | $scope.netkit.pop();
40 | $scope.counter--;
41 |
42 | changed = true;
43 | }
44 | };
45 |
46 | $scope.addInterface = function (machine) {
47 | machine.interfaces.if.push({ "eth": { "number": machine.interfaces.counter } });
48 | machine.interfaces.counter++;
49 |
50 | changed = true;
51 | };
52 |
53 | $scope.removeInterface = function (machine) {
54 | if (machine.interfaces.counter > 1 && confirm("Are you sure you want to remove the interface?")) {
55 | machine.interfaces.if.pop();
56 | machine.interfaces.counter--;
57 |
58 | changed = true;
59 | }
60 | };
61 |
62 | $scope.addGateway = function (machine) {
63 | machine.gateways.counter++;
64 | machine.gateways.gw.push({ "route": "", "if": 0 });
65 | };
66 |
67 | $scope.removeGateway = function (machine) {
68 | if (machine.gateways.counter > 1 && confirm("Are you sure you want to remove the gateway?")) {
69 | machine.gateways.gw.pop();
70 | machine.gateways.counter--;
71 | }
72 | };
73 |
74 | $scope.addFile = function (machine) {
75 | machine.other.fileCounter++;
76 | machine.other.files.push({ "name": "", "contents": "" });
77 | };
78 |
79 | $scope.removeFile = function (machine) {
80 | if (machine.other.fileCounter > 0 && confirm("Are you sure you want to remove the file?")) {
81 | machine.other.files.pop();
82 | machine.other.fileCounter--;
83 | }
84 | };
85 |
86 | $scope.addRipNetwork = function (machine) {
87 | machine.routing.rip.network.push("");
88 | changed = true;
89 | };
90 |
91 | $scope.removeRipNetwork = function (machine) {
92 | if (machine.routing.rip.network.length > 1 && confirm("Are you sure you want to remove the network?")) {
93 | machine.routing.rip.network.pop();
94 | changed = true;
95 | }
96 | };
97 |
98 | $scope.addRipRoute = function (machine) {
99 | machine.routing.rip.route.push("");
100 | };
101 |
102 | $scope.removeRipRoute = function (machine) {
103 | if (machine.routing.rip.route.length > 1 && confirm("Are you sure you want to remove the route?")) {
104 | machine.routing.rip.route.pop();
105 | }
106 | };
107 |
108 | $scope.addOspfNetwork = function (machine) {
109 | machine.routing.ospf.network.push("");
110 | machine.routing.ospf.area.push("0.0.0.0");
111 | machine.routing.ospf.stub.push(false);
112 |
113 | changed = true;
114 | };
115 |
116 | $scope.removeOspfNetwork = function (machine) {
117 | if (machine.routing.ospf.network.length > 1 && confirm("Are you sure you want to remove the network?")) {
118 | machine.routing.ospf.network.pop();
119 | machine.routing.ospf.area.pop();
120 | machine.routing.ospf.stub.pop();
121 |
122 | changed = true;
123 | }
124 | };
125 |
126 | $scope.addBgpNetwork = function (machine) {
127 | machine.routing.bgp.network.push("");
128 |
129 | changed = true;
130 | };
131 |
132 | $scope.removeBgpNetwork = function (machine) {
133 | if (machine.routing.bgp.network.length > 1 && confirm("Are you sure you want to remove the network?")) {
134 | machine.routing.bgp.network.pop();
135 |
136 | changed = true;
137 | }
138 | };
139 |
140 | $scope.addBgpNeighbor = function (machine) {
141 | machine.routing.bgp.remote.push({ "neighbor": "", "as": "", "description": ""/*, "p_list":[{"name":"", "direction":""}]*/ });
142 | };
143 |
144 | $scope.removeBgpNeighbor = function (machine) {
145 | if (machine.routing.bgp.remote.length > 1 && confirm("Are you sure you want to remove the neighbor?")) {
146 | machine.routing.bgp.remote.pop();
147 | }
148 | };
149 |
150 | $scope.addBgpRule = function (rules) {
151 | rules.push("");
152 | };
153 |
154 | $scope.removeBgpRule = function (rules) {
155 | if (rules.length > 0 && confirm("Are you sure you want to remove the last rule?")) {
156 | rules.pop();
157 | }
158 | };
159 |
160 | $scope.makeDownload = function (text, filename) {
161 | var blob = new Blob([text], { type: "text/plain;charset=utf-8" });
162 | saveAs(blob, filename, true);
163 | };
164 |
165 | $scope.generateScript = function (netkitData, labInfoData) {
166 | return makeScript(makeFilesStructure(netkitData, labInfoData));
167 | };
168 |
169 | $scope.generateConfig = function (netkitData, labInfoData) {
170 | var all = [{ "labInfo": labInfoData, "netkit": netkitData }];
171 | return JSON.stringify(all, undefined, 4);
172 | };
173 |
174 | $scope.generateZip = function (netkitData, labInfoData) {
175 | return makeZip(makeFilesStructure(netkitData, labInfoData));
176 | };
177 |
178 | $scope.makeGraph = function (netkitData) {
179 | return makeGraph(netkitData);
180 | };
181 |
182 | $scope.makeGraphIfChanged = function (netkitData) {
183 | if (changed) {
184 | changed = false;
185 | return makeGraph(netkitData);
186 | }
187 | };
188 |
189 | $scope.toggleOVSwitchSelection = function (netkitData, thisType) {
190 | return netkitData.some(machine => machine.type == "controller") && thisType != "controller";
191 | };
192 |
193 | $scope.toggleControllerSelection = function (netkitData, thisType) {
194 | return !netkitData.some(machine => machine.type == "controller") || thisType == "controller";
195 | };
196 |
197 | $scope.toggleGraphUpdate = function () {
198 | if ($scope.labInfo.toggle == "disable") {
199 | $scope.labInfo.toggle = "enable";
200 | }
201 | else $scope.labInfo.toggle = "disable";
202 | };
203 |
204 | $scope.import = function () {
205 | let fileElement = document.getElementById("file");
206 | try {
207 | let filePath = fileElement.files[0];
208 | if (filePath) {
209 | let fileReader = new FileReader();
210 | fileReader.onloadend = function (e) {
211 | try {
212 | let app = JSON.parse(e.target.result.substring(e.target.result.indexOf("["))); // rimozione caratteri di codifica prima di '['
213 | $scope.netkit = app[0].netkit;
214 | $scope.counter = $scope.netkit.length;
215 | $scope.labInfo = app[0].labInfo;
216 | $scope.$apply();
217 |
218 | changed = true;
219 | }
220 | catch (err) {
221 | alert("Error: " + err);
222 | }
223 | };
224 | fileElement.value = "";
225 | fileReader.readAsBinaryString(filePath);
226 | } else {
227 | fileElement.click();
228 | }
229 | }
230 | catch (err) {
231 | alert("Error in File Reader: " + err);
232 | }
233 | };
234 | });
235 |
--------------------------------------------------------------------------------
/src/style/main.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --main-color: #337ab7;
3 | --secondary-color: #5b8fbd;
4 | --disclaimer-color: rgba(255, 0, 0, .2);
5 | }
6 |
7 | html {
8 | padding: 0;
9 | margin: 0;
10 | height: 100%;
11 | }
12 |
13 | body {
14 | height: 100%;
15 | padding: 0;
16 | margin: 0;
17 | top: 0;
18 | position: relative;
19 | width: 100%;
20 | padding-top: 45px;
21 | }
22 |
23 | .ng-scope,
24 | .tab-content {
25 | height: 100%;
26 | }
27 |
28 | .anchor-id {
29 | position: absolute;
30 | margin-top: -140px;
31 | }
32 |
33 | @-moz-document url-prefix() {
34 | /* firefox only */
35 | .btn-short {
36 | margin-top: 0;
37 | }
38 | }
39 |
40 | .browserupgrade {
41 | margin: 0.2em 0;
42 | background: #ffff00;
43 | color: black;
44 | padding: 0.2em 0;
45 | }
46 |
47 | .hidden {
48 | display: none;
49 | }
50 |
51 | .tab-pane {
52 | margin: 1em;
53 | }
54 |
55 | .disclaimer {
56 | text-align: center;
57 | padding: 10px;
58 | background-color: var(--disclaimer-color);
59 | border-radius: 1em;
60 | }
61 |
62 | hr {
63 | margin: 10px 0;
64 | }
65 |
66 | /* ------------------------------------------------------ */
67 | /* ----------------------- MODAL ----------------------- */
68 | /* ------------------------------------------------------ */
69 |
70 | .modal-kathara {
71 | display: block;
72 | z-index: 9999;
73 | background: rgba(189, 189, 189, 0.5);
74 | }
75 |
76 | .modal-kathara .modal-title {
77 | text-align: left;
78 | }
79 |
80 | .modal-kathara .modal-footer {
81 | margin: 5px 0;
82 | border: 0 !important;
83 | padding: 10px;
84 | clear: both;
85 | text-align: right;
86 | }
87 |
88 | /* ------------------------------------------------------ */
89 | /* ----------------------- NAVBAR ----------------------- */
90 | /* ------------------------------------------------------ */
91 |
92 | .navbar-kathara {
93 | position: fixed;
94 | border: none;
95 | border-radius: 0;
96 | width: 100%;
97 | min-height: 0;
98 | }
99 |
100 | .navbar-kathara-primary {
101 | z-index: 9996;
102 | top: 0;
103 | }
104 |
105 | .navbar-kathara,
106 | .navbar-kathara li a,
107 | .navbar-kathara li a:visited,
108 | .navbar-kathara li a:focus {
109 | background-color: var(--main-color);
110 | color: white;
111 | font-weight: bold;
112 | }
113 |
114 | .navbar-kathara li a {
115 | padding: 14px
116 | }
117 |
118 | .navbar-kathara-secondary {
119 | z-index: 9995;
120 | top: 48px;
121 | }
122 |
123 | .navbar-kathara li a:active,
124 | .navbar-kathara li a:hover,
125 | .navbar-kathara li.active a,
126 | .navbar-kathara-secondary,
127 | .navbar-kathara-secondary li a,
128 | .navbar-kathara-secondary li a:visited,
129 | .navbar-kathara-secondary li a:focus {
130 | color: white;
131 | background-color: var(--secondary-color);
132 | }
133 |
134 | .navbar-kathara-secondary li a:active,
135 | .navbar-kathara-secondary li a:hover {
136 | background-color: #7c9fbe;
137 | }
138 |
139 | .disabledLink {
140 | color: currentColor;
141 | cursor: not-allowed !important;
142 | opacity: 0.5;
143 | text-decoration: none;
144 | }
145 |
146 | /* ------------------------------------------------------ */
147 | /* ----------------------- INPUTS ----------------------- */
148 | /* ------------------------------------------------------ */
149 |
150 | .form-control {
151 | border-radius: 0;
152 | padding: 2px 6px;
153 | margin-top: -2px;
154 | margin-bottom: 2px;
155 | height: 26px;
156 | }
157 |
158 | textarea {
159 | width: 100%;
160 | font-size: 14px;
161 | color: #555;
162 | padding: 1em;
163 | border: 1px solid lightgray;
164 | }
165 |
166 | a {
167 | cursor: pointer;
168 | }
169 |
170 | #netkit input:required:invalid,
171 | #netkit input:focus:invalid,
172 | #netkit textarea:required:invalid,
173 | #netkit input:invalid {
174 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAT1JREFUeNpi/P//PwMpgImBRMACY/x7/uDX39sXt/67cMoDyOVgMjBjYFbV/8kkqcCBrIER5KS/967s+rmkXxzI5wJiRSBm/v8P7NTfHHFFl5mVdIzhGv4+u///x+xmuAlcdXPB9KeqeLgYd3bDU2ZpRRmwH4DOeAI07QXIRKipYPD35184/nn17CO4p/+cOfjl76+/X4GYAYThGn7/g+Mfh/ZZwjUA/aABpJVhpv6+dQUjZP78Z0YEK7OezS2gwltg64GmfTu6i+HL+mUMP34wgvGvL78ZOEysf8M1sGgZvQIqfA1SDAL8iUUMPIFRQLf+AmMQ4DQ0vYYSrL9vXDz2sq9LFsiX4dLRA0t8OX0SHKzi5bXf2HUMBVA0gN356N7p7xdOS3w5fAgcfNxWtn+BJi9gVVBOQfYPQIABABvRq3BwGT3OAAAAAElFTkSuQmCC');
175 | }
176 |
177 | #netkit input:required:invalid,
178 | #netkit textarea:required:invalid {
179 | box-shadow: 0 0 1px red;
180 | }
181 |
182 | #netkit input:required:valid {
183 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAZZJREFUeNpi/P//PwMpgImBRMAy58QshrNPTzP8+vOLIUInisFQyYjhz98/DB9/fmT48/+35v7H+8KNhE2+WclZd+G0gZmJmYGThUNz1fUVMZtvbWT59eUXG9wGZIWMUPj993eJ5VeWxuy8veM/CzPL3yfvH/9H0QBSBDYZyOVm4mGYfn6q4cory5lYmFh+MrEwM/76/YsR7mk2ZjbWP///WP37/y8cqIDhx58fjvtu7XV6//ndT34G/v8FasUsDjKO/+A2PP3wpGLd+TVsfOz8XH6KAT+nHpokcu7h6d9q/BoMxToVbBYqlt9///+1GO4/WVdpXqY/zMqXn13/+vTjI9mj94/y//v9/3e9ZRObvYbDT0Y2xnm///x+wsfHB3GSGLf41jb3rv0O8nbcR66d+HPvxf2/+YZFTHaqjl8YWBnm/vv37yly5LL8+vuLgYuVa3uf/4T/Kd8SnSTZpb6FGUXwcvJxbAPKP2VkZESNOBDx8+9PBm4OwR1TwmYwcfzjsBUQFLjOxs52A2YyKysrXANAgAEA7buhysQuIREAAAAASUVORK5CYII=');
184 | }
185 |
186 | #netkit input:valid {
187 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpCMUU2QjQ1RjhCRUUxMUU1ODdBQUU3MUJBOUZBMDU1MyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpCMUU2QjQ2MDhCRUUxMUU1ODdBQUU3MUJBOUZBMDU1MyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkIxRTZCNDVEOEJFRTExRTU4N0FBRTcxQkE5RkEwNTUzIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkIxRTZCNDVFOEJFRTExRTU4N0FBRTcxQkE5RkEwNTUzIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+WXXDRQAAAWBJREFUeNqUkT9rwlAUxW/eyx8kdHByCAFLhICgKYqDa8XNJX4LJ0c/kpBF8gG0Lg62FnHQZnARuySLOojEvCS9r9DQgh288JbHOff83nlCmqZwzxC4c0TXdcHzPIiiCFqtFpTLZWCMwfl8hjiOC6vVyjIMI6pUKi//JlBKQZblwnQ6rc1mM3K5XGiWcMsQhuHDeDyuzefzVBTF1Pd9uGkghICiKDAajbTJZCJgUozXAsfNNLiBJklSxPPEUa7Xa2mxWJSOxyPj5k6nQyzLyqoUgyB4xniqqqrUbDaZ4zjqZrNJNE2DbrdLURxhCe+ZYbfbfeq6bqzX63A4HOqHw4FzMy6uVqsMU98Q6ZQh5fP5j16vt8VN8nK5TPb7fWLbtlCv10MUvyLq6ffnEv4gZPX6/f7WNE2p0WhAu91WEHGLJZww7buIPy1xUy6X8waDAW/mEVN9FAU/myVJygxfAgwAp6GboqaPmLwAAAAASUVORK5CYII=');
188 | }
189 |
190 | input {
191 | background-position: right center;
192 | background-repeat: no-repeat;
193 | font-weight: normal;
194 | }
195 |
196 | /* ----------- BUTTONS ----------- */
197 |
198 | button {
199 | color: initial;
200 | font-weight: normal;
201 | }
202 |
203 | .btn {
204 | margin: 2px 0;
205 | }
206 |
207 | .btn-danger[disabled],
208 | .btn-danger[disabled].active,
209 | .btn-danger[disabled].focus,
210 | .btn-danger[disabled]:active,
211 | .btn-danger[disabled]:focus,
212 | .btn-danger[disabled]:hover,
213 | .btn-success[disabled],
214 | .btn-success[disabled].active,
215 | .btn-success[disabled].focus,
216 | .btn-success[disabled]:active,
217 | .btn-success[disabled]:focus,
218 | .btn-success[disabled]:hover {
219 | background-color: #9e9e9e;
220 | border-color: #808080;
221 | opacity: 0.2 !important;
222 | }
--------------------------------------------------------------------------------
/src/sdn-manager/vue-components/ruleMakerModal.js:
--------------------------------------------------------------------------------
1 | const ruleModal = new Vue({
2 | el: "#rule-modal",
3 | data: {
4 | visible: false,
5 | header: "",
6 | rule: {
7 | matches: [{
8 | name: "any",
9 | value: ""
10 | }],
11 | actions: [{
12 | name: "noselection",
13 | value: ""
14 | }],
15 | priority: 0,
16 | table: 0,
17 | idleTimeout: 0,
18 | hardTimeout: 0
19 | },
20 | originalRule: null,
21 | labels: [],
22 | deviceInfos: null
23 | },
24 | methods: {
25 | open(device, header) {
26 | this.visible = true;
27 | this.header = header || "Create new rule for " + device;
28 |
29 | this.labels = labelsSection.labels;
30 | this.deviceInfos = dataStore.getDeviceInfo(device);
31 | this._resetRuleValues();
32 | },
33 |
34 | close() {
35 | this.visible = false;
36 | this.header = "";
37 | this.originalRule = null;
38 | },
39 |
40 | _resetRuleValues() {
41 | this.rule = {
42 | matches: [{ name: "any", value: "" }],
43 | actions: [{ name: "noselection", value: "" }],
44 | priority: 0,
45 | table: 0,
46 | idleTimeout: 0,
47 | hardTimeout: 0
48 | };
49 | },
50 |
51 | /* ----------------------------------------------------------- */
52 | /* -------------------------- RULES -------------------------- */
53 | /* ----------------------------------------------------------- */
54 |
55 | makeRule() {
56 | if(!this.originalRule){
57 | if(!this.rule.actions.some(action => action.name == "noselection") &&
58 | !this.rule.matches.some(match => match.name == "noselection")){
59 | let isLabelRule = false;
60 |
61 | for(let field of this.rule.actions.concat(this.rule.matches)){
62 | if(field.name.includes("MPLS")){
63 | field.label = this.labels.find(label => label.name == field.value);
64 | isLabelRule = true;
65 | }
66 | }
67 |
68 | let rule = dataStore.createAndStoreRule(
69 | this.deviceInfos.name,
70 | this.rule.matches,
71 | this.rule.actions,
72 | this.rule.priority,
73 | this.rule.table,
74 | this.rule.idleTimeout,
75 | this.rule.hardTimeout,
76 | );
77 |
78 | if(isLabelRule) switchDetailsSection.labelRules.push(rule);
79 | else switchDetailsSection.packetRules.push(rule);
80 |
81 | this.close();
82 | }
83 | } else {
84 | Object.assign(this.originalRule, this.rule);
85 | this.originalRule.edited = true;
86 | this.close();
87 | }
88 | },
89 |
90 | editRule(rule) {
91 | if(!rule.deleted){
92 | // Uso original rule per memorizzare l'oggetto che voglio modificare. Dopo aver fatto le modifiche lo sovrascriverò
93 | this.originalRule = rule;
94 | let device = rule.device;
95 | this.open(device, "Modifica la regola per " + device);
96 | Object.assign(this.rule, rule);
97 | // TODO: ^^ Non copia l'oggetto completamente: azioni e match sono quelli della regola originale =>
98 | // ... ogni modifica nel modal viene immediatamente propagata senza aspettare la pressione del pulsante di conferma => correggi
99 | }
100 | },
101 |
102 | deleteRule(){
103 | this.originalRule.deleted = true;
104 | this.close();
105 | },
106 |
107 | /* ----------------------------------------------------------- */
108 | /* -------------------- MATCHES & ACTIONS -------------------- */
109 | /* ----------------------------------------------------------- */
110 |
111 | makeNewMatchLine() {
112 | this.rule.matches.push({ name: "noselection", value: "" });
113 | },
114 |
115 | removeLastMatchLine() {
116 | if (this.rule.matches.length > 1)
117 | this.rule.matches.pop();
118 | },
119 |
120 | makeNewActionLine(){
121 | this.rule.actions.push({ name: "noselection", value: "" });
122 | },
123 |
124 | removeLastActionLine(){
125 | if (this.rule.actions.length > 1)
126 | this.rule.actions.pop();
127 | }
128 | },
129 |
130 | components: {
131 | "dynamic-selection": {
132 | props: ["selection", "value"],
133 | data() {
134 | return { content: this.value };
135 | },
136 | template:
137 | "" +
138 |
139 | "" +
142 | "" +
143 | "{{ label.name }}" +
144 | " " +
145 | " " +
146 |
147 | "" +
150 | "" +
152 | "eth{{ interface.number }}" +
153 | " " +
154 | " " +
155 |
156 | " " +
160 |
161 | " " +
165 |
166 | " " +
170 |
171 | /* ---------------------------------------------------------------------------------------------- */
172 | /* ' ' + // TODO: Non ho messo il max perché non lo conosco
176 | /* ---------------------------------------------------------------------------------------------- */
177 | /* ---------------- Si può scegliere quale dei due blocchi usare (sopra o sotto) ---------------- */
178 | /* ---------------------------------------------------------------------------------------------- */
179 | /**/ "" +
182 | /**/ "ARP " +
183 | /**/ "IPv4 " +
184 | /**/ "MPLS " +
185 | /**/ "LLDP " +
186 | /**/ " " +
187 | /* ---------------------------------------------------------------------------------------------- */
188 |
189 | " " +
192 |
193 | " " +
197 |
198 | " " +
201 |
202 | "
"
203 | }
204 | }
205 | });
--------------------------------------------------------------------------------
/src/sdn-manager/vue-components/labelsSection.js:
--------------------------------------------------------------------------------
1 | const labelsSection = new Vue({
2 | el: "#labelsSection",
3 | data: {
4 | newLabel: {
5 | show: false,
6 | name: "",
7 | color: ""
8 | },
9 | labels: []
10 | },
11 | methods: {
12 | hideLabelMaker(){
13 | this.newLabel.show = false;
14 | this.newLabel.name = "";
15 | this.newLabel.color = "";
16 | enableButtons("b4");
17 | },
18 |
19 | showLabelMaker(){
20 | this.newLabel.show = true;
21 | disableButtons("b4");
22 | },
23 |
24 | setAllEditButtonsDisabled(){
25 | for(let child of this.$children) child.toggleEdit(true);
26 | },
27 |
28 | isEditing(){
29 | return Boolean(this._findActiveLabelComponent());
30 | },
31 |
32 | reset(){
33 | this.hideLabelMaker();
34 | this.labels = [];
35 |
36 | labelsSection.setAllEditButtonsDisabled();
37 | discardPath();
38 | },
39 |
40 | /* ------------------------------------------------------------ */
41 | /* -------------------------- LABELS -------------------------- */
42 | /* ------------------------------------------------------------ */
43 |
44 | createNewLabel(name, color) {
45 | if(!name || !color){
46 | if(!this.newLabel.name || !this.newLabel.color) return;
47 | this.createNewLabel(this.newLabel.name, this.newLabel.color);
48 | } else {
49 | if(!this.labels.some(el => el.name == name))
50 | this.labels.push({ name, color });
51 |
52 | this.hideLabelMaker();
53 | }
54 | },
55 |
56 | getLabelColor(labelName){
57 | let label = this.labels.find(label => label.name == labelName);
58 | if(label) return label.color;
59 | },
60 |
61 | /* ----------------------------------------------------------- */
62 | /* -------------------------- RULES -------------------------- */
63 | /* ----------------------------------------------------------- */
64 |
65 | addRuleStep(step){
66 | let rule = dataStore.createAndStoreRule(
67 | step.device,
68 | [{ name: "source port", value: step.ingressPort }],
69 | [{ name: "forward to port", value: step.egressPort }]
70 | );
71 | this._addRuleToActiveLabelList_and_addLabeltoRule(rule, step.isStart, step.isEnd);
72 | },
73 |
74 | _addRuleToActiveLabelList_and_addLabeltoRule(rule, isStart, isEnd){
75 | let labelComponent = this._findActiveLabelComponent();
76 |
77 | let label = this.labels.find(label => label.name == labelComponent.name);
78 |
79 | // AutoTag
80 | if(isStart) rule.actions.unshift({ name: "set MPLS label", value: label.name, label });
81 | else rule.matches.push({ name: "MPLS label", value: label.name, label });
82 |
83 | // AutoUnTag
84 | if(isEnd) rule.actions.unshift({ name: "pop MPLS label", value: label.name, label });
85 |
86 | labelComponent.rules.push(rule);
87 | },
88 |
89 | _findActiveLabelComponent(){
90 | return this.$children.find(el => el.buttons.edit.active);
91 | },
92 |
93 | addRuleToChosenLabel(rule, labelName){
94 | let labelComponent = this.$children.find(el => el.name == labelName);
95 | labelComponent.rules.push(rule);
96 | }
97 |
98 | // TODO: Aggiungere la possibilità di cancellare una label
99 | },
100 |
101 | components: {"label-div": {
102 | props: ["name", "color"],
103 | data: function(){
104 | return {
105 | buttons: {
106 | show: {
107 | active: false,
108 | text: "More"
109 | },
110 | edit: {
111 | active: false,
112 | text: "Edit..."
113 | },
114 | remove: {
115 | active: false,
116 | text: "Remove..."
117 | }
118 | },
119 | rules: []
120 | };
121 | },
122 | computed: {
123 | validRules() {
124 | return this.rules.filter(rule => !rule.deleted)
125 | }
126 | },
127 | template:
128 | "" +
129 | "
" +
130 | "
" +
131 | "
" +
132 | "
" +
134 | "{{ buttons.edit.text }} " +
135 | "
{{ buttons.show.text }} " +
137 | "
" +
138 |
139 | "
" +
140 | "" +
141 | "device " +
142 | "match " +
143 | "action " +
144 | "- " +
145 | " " +
146 | "" +
147 | "" +
148 | " " +
150 | "{{ rule.device }}" +
151 | " " +
152 | "" +
153 | "{{ rule.matches.find(match => match.name == \"source port\").name }} {{ rule.matches.find(match => match.name == \"source port\").value }} " +
154 | " 2\">" +
155 | "+ " +
156 | " " +
157 | " " +
158 | "forward to port {{ rule.actions.find(action => action.name == 'forward to port').value }} " +
159 | " 1\">" +
160 | "+ " +
161 | " " +
162 | " " +
163 | "" +
164 | "- " +
165 | " " +
166 | "No rules defined yet " +
167 | " " +
168 | "
" +
169 | "
" +
170 | "{{ buttons.remove.text }} " +
172 | "
" +
173 | "
" +
174 | "
",
175 | methods: {
176 | /* --------------------------------------------------------- */
177 | /* ------------------------ BUTTONS ------------------------ */
178 | /* --------------------------------------------------------- */
179 |
180 | toggleEdit(forceDisable) {
181 | if(this.buttons.edit.active || forceDisable){
182 | this.buttons.edit.active = false;
183 | this.buttons.edit.text = "Edit...";
184 | this.toggleRemove(true);
185 |
186 | disableDragging();
187 | } else {
188 | this.$parent.setAllEditButtonsDisabled();
189 |
190 | this.buttons.edit.active = true;
191 | this.buttons.edit.text = "EDITING";
192 | this.toggleExpand(true);
193 |
194 | enablePathSelection();
195 | }
196 | },
197 |
198 | toggleRemove(forceDisable) {
199 | if(this.buttons.remove.active || forceDisable){
200 | this.buttons.remove.active = false;
201 | this.buttons.remove.text = "Remove...";
202 | } else {
203 | this.buttons.remove.active = true;
204 | this.buttons.remove.text = "Done";
205 | }
206 | },
207 |
208 | toggleExpand(forceShow) {
209 | if(this.buttons.show.active && !forceShow){
210 | this.buttons.show.active = false;
211 | this.buttons.show.text = "More";
212 | } else {
213 | this.buttons.show.active = true;
214 | this.buttons.show.text = "Less";
215 | }
216 | },
217 |
218 | /* --------------------------------------------------------- */
219 | /* ------------------------- RULES ------------------------- */
220 | /* --------------------------------------------------------- */
221 |
222 | highlightAllRulesOnGraph(){
223 | removeNodesSelection();
224 | this.rules.filter(rule => !rule.deleted).forEach(rule => this.highlightRuleOnGraph(rule))
225 | },
226 |
227 | removeRule(rule){
228 | rule.deleted = true;
229 | if(this.validRules.length == 0) this.toggleRemove(true);
230 | },
231 |
232 | highlightRuleOnGraph(rule){
233 | highlightSegmentOnGraph(
234 | rule.device,
235 | rule.matches.find(match => match.name == "source port").value,
236 | rule.actions.find(action => action.name == "forward to port").value
237 | );
238 | }
239 | }
240 | }}
241 | });
--------------------------------------------------------------------------------
/src/sdn-manager/rules-utils/rulesMapper.js:
--------------------------------------------------------------------------------
1 | var rulesMapper = {
2 |
3 | /* ---------------------------------------------------------------- */
4 | /* ------------------------- RULES TO RYU ------------------------- */
5 | /* ---------------------------------------------------------------- */
6 |
7 | makeMatch: function (matches, offset) {
8 | if (![20000, 10000].includes(offset))
9 | throw new Error("Implicit protocol not recognized");
10 | var ofmatch = {};
11 | if (matches[0].name != "any") {
12 | for (var _i = 0, matches_1 = matches; _i < matches_1.length; _i++) {
13 | var match = matches_1[_i];
14 | switch (match.name) {
15 | case "MPLS label": // mpls_label {"mpls_label": 3, "eth_type": 34888}
16 | ofmatch.mpls_label = (this.mapperMPLS.nameToValue(match.value) + offset);
17 | ofmatch.eth_type = 34887;
18 | break;
19 | case "source port": // in_port {"in_port": 7}
20 | ofmatch.in_port = +match.value;
21 | if (offset)
22 | ofmatch.eth_type = (offset == 10000 ? 2054 : 2048);
23 | break;
24 | case "MAC source": // eth_src {"eth_src": "aa:bb:cc:11:22:33"}
25 | ofmatch.eth_src = match.value;
26 | break;
27 | case "MAC destination": // eth_dst {"eth_dst": "aa:bb:cc:11:22:33/00:00:00:00:ff:ff"}
28 | ofmatch.eth_dst = match.value;
29 | break;
30 | case "IPv4 source": // ipv4_src {"ipv4_src": "192.168.0.1", "eth_type": 2048}
31 | ofmatch.ipv4_src = match.value;
32 | ofmatch.eth_type = 2048;
33 | break;
34 | case "IPv4 destination": // ipv4_dst {"ipv4_dst": "192.168.10.10/255.255.255.0", "eth_type": 2048}
35 | ofmatch.ipv4_dst = match.value;
36 | ofmatch.eth_type = 2048;
37 | break;
38 | case "TCP source port": // tcp_src {"tcp_src": 3, "ip_proto": 6, "eth_type": 2048}
39 | ofmatch.tcp_src = +match.value;
40 | ofmatch.ip_proto = 6;
41 | ofmatch.eth_type = 2048;
42 | break;
43 | case "TCP destination port": // tcp_dst {"tcp_dst": 5, "ip_proto": 6, "eth_type": 2048}
44 | ofmatch.tcp_dst = +match.value;
45 | ofmatch.ip_proto = 6;
46 | ofmatch.eth_type = 2048;
47 | break;
48 | case "ethertype":
49 | console.warn("ethertype may be overwritten");
50 | if (match.value == "ARP")
51 | ofmatch.eth_type = 2054;
52 | else if (match.value == "IPv4")
53 | ofmatch.eth_type = 2048;
54 | else if (match.value == "MPLS")
55 | ofmatch.eth_type = 34887;
56 | else if (match.value == "LLDP")
57 | ofmatch.eth_type = 35020;
58 | else
59 | throw new Error("Did not find a mapping to a known ethertype (" + match.value + ")");
60 | break;
61 | default:
62 | throw new Error("Did not find a mapping to a known match (" + match.name + " " + match.value + ")");
63 | }
64 | }
65 | }
66 | return ofmatch;
67 | },
68 |
69 | makeActions: function (actions, offset) {
70 | var ofactions = [];
71 | if (actions[0].name != "drop") {
72 | for (var _i = 0, actions_1 = actions; _i < actions_1.length; _i++) {
73 | var action = actions_1[_i];
74 | switch (action.name) {
75 | case "set MPLS label": // PUSH_MPLS {"type": "PUSH_MPLS", "ethertype": 34887}
76 | ofactions.push({
77 | type: "PUSH_MPLS",
78 | ethertype: 34887
79 | });
80 | ofactions.push({
81 | type: "SET_FIELD",
82 | field: "mpls_label",
83 | value: (this.mapperMPLS.nameToValue(action.value) + offset)
84 | });
85 | break;
86 | case "pop MPLS label": // POP_MPLS {"type": "POP_MPLS", "ethertype": 2054}
87 | ofactions.push({
88 | type: "POP_MPLS",
89 | ethertype: (offset == 10000 ? "2054" : "2048")
90 | });
91 | break;
92 | case "forward to port": // OUTPUT {"type": "OUTPUT", "port": 3}
93 | ofactions.push({
94 | type: "OUTPUT",
95 | port: action.value
96 | });
97 | break;
98 | case "send to controller":
99 | ofactions.push({
100 | type: "OUTPUT",
101 | port: "CONTROLLER"
102 | });
103 | break;
104 | case "send to table": // GOTO_TABLE {"type": "GOTO_TABLE", "table_id": 8}
105 | ofactions.push({
106 | type: "GOTO_TABLE",
107 | table_id: action.value
108 | });
109 | break;
110 | default:
111 | throw new Error("Did not find a mapping to a known action (" + action.name + " " + action.value + ")");
112 | }
113 | }
114 | }
115 | return ofactions;
116 | },
117 |
118 | /* ---------------------------------------------------------------- */
119 | /* ------------------------- RYU TO RULES ------------------------- */
120 | /* ---------------------------------------------------------------- */
121 |
122 | reverseMatch: function (match) {
123 | var reversedMatches = [];
124 | for (var matchKey in match) {
125 | switch (matchKey) {
126 | case "dl_type":
127 | if (match.dl_type == 2054)
128 | reversedMatches.push({ name: "ethertype", value: "ARP" });
129 | else if (match.dl_type == 2048)
130 | reversedMatches.push({ name: "ethertype", value: "IPv4" });
131 | else if (match.dl_type == 34887)
132 | reversedMatches.push({ name: "ethertype", value: "MPLS" });
133 | else if (match.dl_type == 35020)
134 | reversedMatches.push({ name: "ethertype", value: "LLDP" });
135 | else
136 | throw new Error("Did not find a reverse mapping to a known ethertype (" + match[matchKey] + ")");
137 | break;
138 | case "dl_dst":
139 | reversedMatches.push({ name: "MAC destination", value: String(match.dl_dst) });
140 | break;
141 | case "in_port":
142 | reversedMatches.push({ name: "source port", value: String(match.in_port) });
143 | break;
144 | case "mpls_label":
145 | reversedMatches.push({ name: "MPLS label", value: this.mapperMPLS.valueToName(match.mpls_label) });
146 | break;
147 | default:
148 | throw new Error("OpenFlow Match not recognized. Cannot map to a valid simMatch");
149 | }
150 | }
151 | if (!reversedMatches.length) {
152 | reversedMatches.push({ name: "any", value: "" });
153 | }
154 | return reversedMatches;
155 | },
156 |
157 | reverseActions: function (actions) {
158 | var reversedActions = [];
159 | for (var _i = 0, actions_2 = actions; _i < actions_2.length; _i++) {
160 | var action = actions_2[_i];
161 | var columnIndex = action.indexOf(":");
162 | var name_1 = action.substring(0, columnIndex);
163 | var value = action.substring(columnIndex + 1);
164 | switch (name_1) {
165 | case "OUTPUT":
166 | if (value == "CONTROLLER") {
167 | reversedActions.push({ name: "send to controller", value: "" });
168 | }
169 | else {
170 | reversedActions.push({ name: "forward to port", value: +value });
171 | }
172 | break;
173 | case "PUSH_MPLS":
174 | reversedActions.push({ name: "set MPLS label", value: "err: see set-field" });
175 | break;
176 | case "SET_FIELD":
177 | var _a = value.replace(/[{} ]/g, "").split(":"), whichField = _a[0], otherValue = _a[1];
178 | if (whichField === "mpls_label") {
179 | var labelName = this.mapperMPLS.valueToName(otherValue);
180 | reversedActions.push({ name: "set field", value: "mpls:" + labelName });
181 | }
182 | else {
183 | throw new Error("Error while mapping rules @ SET_FIELD: cannot map this field to a known one");
184 | }
185 | break;
186 | case "POP_MPLS":
187 | var protocolName = (value === "2054" ? "ARP" :
188 | value === "2048" ? "IPv4" :
189 | "Unknown");
190 | reversedActions.push({ name: "pop MPLS label", value: protocolName });
191 | break;
192 | case "GOTO_TABLE":
193 | reversedActions.push({ name: "send to table", value: value });
194 | break;
195 | default:
196 | throw new Error("OpenFlow Action not recognized. Cannot map to a valid simAction");
197 | }
198 | }
199 | if (!reversedActions.length) {
200 | reversedActions.push({ name: "drop", value: "" });
201 | }
202 | return reversedActions;
203 | },
204 |
205 | /* --------------------------------------------------------------- */
206 | /* ---------------------------- OTHER ---------------------------- */
207 | /* --------------------------------------------------------------- */
208 |
209 | mapperMPLS: {
210 | _counter: Math.floor(Math.random() * 990) + 9,
211 | _map: {},
212 | nameToValue: function (labelName) {
213 | if (!this._map[labelName])
214 | this._map[labelName] = ++this._counter;
215 | return (+this._map[labelName]);
216 | },
217 | valueToName: function (value) {
218 | value = (value % 10000);
219 | for (var labelName in this._map)
220 | if (this._map[labelName] == value)
221 | return labelName;
222 | var newName = "L" + (++this._counter);
223 | this._map[newName] = +value;
224 | return newName;
225 | }
226 | }
227 | };
228 |
--------------------------------------------------------------------------------
/src/lab-generator/make_draw_model.js:
--------------------------------------------------------------------------------
1 | function ip_to_bin(ip) {
2 | let binary = "";
3 | for (let octet of ip.split(".")) {
4 | if (octet > 255) octet = 255;
5 | if (octet < 0) octet = 0;
6 |
7 | let app = parseInt(octet).toString(2);
8 | let pad = "00000000";
9 | app = pad.substring(0, pad.length - app.toString(2).length) + app.toString(2);
10 | binary += "" + app.toString(2);
11 | }
12 | return binary;
13 | }
14 |
15 | function network_from_binary_ip_mask(binary, netmask) {
16 | let network = "";
17 | for (let j = 0; j < 32; j++) {
18 | network += (netmask[j] == "1") ? binary[j] : "0";
19 | }
20 | return network;
21 | }
22 |
23 | function bin_to_ip(bin) {
24 | let ip = "";
25 | for (let i = 0; i < 32; i = i + 8) {
26 | let app = "";
27 | for (let k = 0; k < 8; k++)
28 | app += bin[i + k];
29 | ip += parseInt(app, 2) + ((i < 24) ? "." : "");
30 | }
31 | return ip;
32 | }
33 |
34 | function binary_netmask_from_decimal(dec) {
35 | let netmask = "";
36 | if (dec > 32) dec = 32;
37 | for (let j = 0; j < 32; j++) {
38 | netmask += ((j < dec) ? "1" : "0");
39 | }
40 | return netmask;
41 | }
42 |
43 | function get_network_from_ip_net(ip_net) {
44 | let [ip, net] = ip_net.split("/");
45 | if (net > 32) net = 32;
46 | if (net < 0) net = 0;
47 | let binary = ip_to_bin(ip);
48 | let netmask = binary_netmask_from_decimal(net);
49 | let network = network_from_binary_ip_mask(binary, netmask);
50 | let network_ip = bin_to_ip(network);
51 | return network_ip + "/" + net;
52 | }
53 |
54 | function find_destination_eth(lab, name, machine_name, eth_number) {
55 | for (let machineIndex in lab) {
56 | for (let interfaceIndex in lab[machineIndex].interfaces.if) {
57 | if (lab[machineIndex].interfaces.if[interfaceIndex].eth.domain == name && (lab[machineIndex].name != machine_name ||
58 | (lab[machineIndex].name != machine_name && lab[machineIndex].interfaces.if[interfaceIndex].eth.number != eth_number))) {
59 | return { "n": machineIndex, "f": interfaceIndex };
60 | }
61 | }
62 | }
63 | return null;
64 | }
65 |
66 | function get_eth_ip_difference(network, ip) {
67 | let net_split = network.split("/")[0];
68 | let ip_split = ip.split("/")[0];
69 | let net_split_i = net_split.split(".");
70 | let ip_split_i = ip_split.split(".");
71 | if (net_split_i.length != ip_split_i.length) return 0;
72 | for (let i in net_split_i) {
73 | if (net_split_i[i] != ip_split_i[i]) {
74 | if (i == 3) return ip_split_i[3];
75 | if (i == 2) return ip_split_i[2] + "." + ip_split_i[3];
76 | if (i == 1) return ip_split_i[1] + "." + ip_split_i[2] + "." + ip_split_i[3];
77 | if (i == 0) return ip_split;
78 | }
79 | }
80 | return 0;
81 | }
82 |
83 | function containsNodeWithID(id, list) {
84 | return list.some(el => el && el.id == id);
85 | }
86 |
87 | function containsEdge(from, to, list) {
88 | return list.some(el => el && el.from && el.from == from && el.to && el.to == to);
89 | }
90 |
91 | // ----- All nodes have, somehow, to be a source and a destination ------
92 | // ----------------------------------------------------------------------
93 | // NODES: each machine; each eth of each machine; each collision domain;
94 | // each ospf/rip/as is a sticknote, so it's a node too;
95 | // each ip on collision domain is a white note, so it's another node.
96 | function generate_nodes_edges(lab) {
97 | let nodes = [];
98 | let edges = [];
99 |
100 | let ifNameAt = document.getElementById("ifNameAt");
101 | let ifOspfCost = document.getElementById("ifOspfCost");
102 | let routingLabel = document.getElementById("routingLabel");
103 |
104 | let pendingDomainNodes = [];
105 |
106 | for (let m in lab) {
107 | let machine = lab[m];
108 | if (machine.name == "") continue;
109 | //each machine is a node. beware of duplicates
110 | let id = "machine-" + machine.name;
111 | if (!containsNodeWithID(id, nodes)) {
112 | nodes.push({
113 | id: id,
114 | label: (machine.type == "other") ? machine.name + " (" + machine.other.image + ")" : machine.name,
115 | group: machine.type
116 | });
117 | }
118 |
119 | if (machine.type == "router") {
120 | if (machine.routing.rip.en) {
121 | if (!containsNodeWithID("label-rip-" + machine.name, nodes) && routingLabel.checked) {
122 | nodes.push({
123 | id: "label-rip-" + machine.name,
124 | label: "RIP",
125 | group: "rip",
126 | value: 2
127 | });
128 | let r_app_to = "label-rip-" + machine.name;
129 | if (!containsEdge(id, r_app_to, edges))
130 | edges.push({
131 | from: id,
132 | to: r_app_to,
133 | length: LENGTH_CLOSE, width: WIDTH_SCALE / 100, dashes: true
134 | });
135 | }
136 | }
137 | if (machine.routing.ospf.en) {
138 | if (!containsNodeWithID("label-ospf-" + machine.name, nodes) && routingLabel.checked) {
139 | nodes.push({
140 | id: "label-ospf-" + machine.name,
141 | label: "OSPF",
142 | group: "ospf",
143 | value: 2
144 | });
145 | let r_app_to = "label-ospf-" + machine.name;
146 | if (!containsEdge(id, r_app_to, edges))
147 | edges.push({
148 | from: id,
149 | to: r_app_to,
150 | length: LENGTH_CLOSE, width: WIDTH_SCALE / 100, dashes: true
151 | });
152 | }
153 | }
154 | if (machine.routing.bgp.en) {
155 | if (!containsNodeWithID("label-bgp-" + machine.name, nodes) && routingLabel.checked) {
156 | nodes.push({
157 | id: "label-bgp-" + machine.name,
158 | label: "AS " + machine.routing.bgp.as + "\n" + machine.routing.bgp.network,
159 | group: "bgp",
160 | value: 2
161 | });
162 | let r_app_to = "label-bgp-" + machine.name;
163 | if (!containsEdge(id, r_app_to, edges))
164 | edges.push({
165 | from: id,
166 | to: r_app_to,
167 | length: LENGTH_CLOSE, width: WIDTH_SCALE / 100, dashes: true
168 | });
169 | }
170 | }
171 | }
172 | //for each interface of the machine
173 | for (let interface of machine.interfaces.if) {
174 | let domain_name = interface.eth.domain;
175 | if (!domain_name || domain_name == "") continue;
176 |
177 | let if_name = (ifNameAt.checked ? "@" : "eth") + interface.eth.number;
178 | let domain_id = "domain-" + domain_name;
179 | let app_to = "iplabel-" + domain_name + "-domain_ip";
180 | let domain_ip, if_ip;
181 | if(interface.ip){
182 | domain_ip = get_network_from_ip_net(interface.ip);
183 | if_ip = get_eth_ip_difference(domain_ip, interface.ip);
184 | }
185 |
186 | let ifCost = "";
187 | if (machine.type == "router") {
188 | if (machine.routing.ospf.en && ifOspfCost.checked) {
189 | let cost = machine.routing.ospf.if.filter(ifn => ifn.interface == interface.eth.number)[0];
190 | ifCost = cost ? cost.cost : "";
191 | }
192 | }
193 |
194 | // the domain is a new node. beware of duplicates.
195 | // domain should have a child node with the ip description
196 | // so edge for that and the eth
197 | if (!containsNodeWithID(domain_id, nodes)) {
198 | let domainNode = {
199 | id: domain_id,
200 | label: domain_name,
201 | group: "domain",
202 | value: 5
203 | };
204 | if(machine.type == "switch") pendingDomainNodes.push(domainNode);
205 | else nodes.push(domainNode);
206 |
207 | if(interface.ip){
208 | nodes.push({
209 | id: "iplabel-" + domain_name + "-domain_ip",
210 | label: domain_ip,
211 | group: "domain-ip",
212 | value: 4
213 | });
214 | //connecting domain and its label
215 | if (!containsEdge(domain_id, app_to, edges)) {
216 | edges.push({
217 | from: domain_id,
218 | to: app_to,
219 | length: LENGTH_CLOSE, width: WIDTH_SCALE / 100, dashes: true
220 | });
221 | }
222 | }
223 | }
224 | //each eth is a new node, linked to its domain and its machine. can't be duplicated
225 | let ifLabel = if_ip ? if_ip + (ifNameAt.checked ? "" : "\n") + if_name : if_name;
226 |
227 | if (ifCost) {
228 | ifLabel += "\nCost: " + ifCost;
229 | }
230 |
231 | nodes.push({
232 | id: "eth-" + id + "-" + if_name + "-" + m,
233 | label: ifLabel,
234 | group: "eth",
235 | value: 2
236 | });
237 | //eth to domain
238 | let app_to_eth = "eth-" + id + "-" + if_name + "-" + m;
239 | if (!containsEdge(domain_id, app_to_eth, edges)) {
240 | edges.push({
241 | from: domain_id,
242 | to: app_to_eth,
243 | length: LENGTH_SERVER, width: WIDTH_SCALE
244 | });
245 | }
246 | // eth to machine
247 | if (!containsEdge(id, app_to_eth, edges)) {
248 | edges.push({
249 | from: id,
250 | to: app_to_eth,
251 | length: LENGTH_CLOSE, width: WIDTH_SCALE
252 | });
253 | }
254 | }
255 | }
256 |
257 | pendingDomainNodes.forEach(domainNode => { if(!containsNodeWithID(domainNode.id, nodes)) nodes.push(domainNode);});
258 |
259 | return { nodes, edges };
260 | }
261 |
262 | function makeGraph(netkit) {
263 | var graph = generate_nodes_edges(netkit);
264 | draw(graph.nodes, graph.edges);
265 | }
266 |
--------------------------------------------------------------------------------
/src/sdn-manager/ryuActions.js:
--------------------------------------------------------------------------------
1 | const { exec } = require("child_process");
2 |
3 | const ryuActions = {
4 | getTopology: function () {
5 | let myhttp = this;
6 | return new Promise(function (resolve/* , reject */) {
7 | let machines = [];
8 | let statsAPIcounter = 0;
9 |
10 | myhttp.get("v1.0/topology/links", {}, function (response){
11 | let domainCounter = 1;
12 | let macMapping = {};
13 |
14 | for(let machine of JSON.parse(response)){ // TODO: Scrivi meglio qui e sotto (*)
15 | let {src, dst} = machine;
16 | if(!macMapping[src.hw_addr]){
17 | macMapping[src.hw_addr] = {
18 | domain: domainCounter++,
19 | dpid: src.dpid
20 | };
21 | }
22 | macMapping[dst.hw_addr] = {
23 | domain: macMapping[src.hw_addr].domain,
24 | dpid: dst.dpid
25 | };
26 | }
27 |
28 | myhttp.get("stats/switches", {}, function (response) {
29 | let switchNames = JSON.parse(response);
30 | for (let switchName of switchNames) {
31 | let machine = {
32 | type: "switch",
33 | name: switchName,
34 | interfaces: { if: [] }
35 | };
36 |
37 | myhttp.get("stats/portdesc/" + switchName, {}, function (response) {
38 | statsAPIcounter++;
39 | let switchDescription = JSON.parse(response);
40 | switchDescription[switchName].forEach(port => {
41 | let match = port.name.match(/^eth(\d)$/); // TODO: Va bene così? Se le porte non hanno questo nome non sono analizzate
42 | if (match && match[1]) {
43 | if(macMapping[port.hw_addr]) {
44 | machine.alternativeName = macMapping[port.hw_addr].dpid; // TODO: Se puoi migliora anche qui
45 | // TODO: Questo è anche il caso in cui la porta si affaccia sul mondo esterno => Aggiungere la classe 'edge' al nodo
46 | }
47 | machine.interfaces.if.push({
48 | eth: {
49 | domain: macMapping[port.hw_addr] ? // TODO: Così non mi piace (* vedi sopra).
50 | ("SDN " + macMapping[port.hw_addr].domain) : // TODO: C'è da intervinire anche in 'makeNodesAndEdges' di simulation.js
51 | ("Network " + domainCounter++),
52 | number: match[1]
53 | }
54 | });
55 | } else {
56 | console.warn("Ignoring port '" + port.name + "'");
57 | }
58 | });
59 |
60 | machines.push(machine);
61 | if (statsAPIcounter == switchNames.length) {
62 | resolve(machines);
63 | }
64 | });
65 | }
66 | });
67 | });
68 | });
69 | },
70 |
71 | getSwitchRules: function (switchName) {
72 | // Ottengo tutte le regole installate su uno switch
73 | return new Promise((resolve) => {
74 | this.getFromSwitchCustom("stats/flow", switchName)
75 | .then(switchRules => resolve(switchRules));
76 | });
77 | },
78 |
79 | getTablesFilteredNotEmpty: function (switchName) {
80 | // Ottengo tutte le tables di uno switch ma tolgo tutte quelle vuote
81 | return new Promise((resolve) => {
82 | this.getFromSwitchCustom("stats/table", switchName)
83 | .then(function (tables) {
84 | resolve(tables.filter(table => table["active_count"] != 0));
85 | });
86 | });
87 | },
88 |
89 | getFromSwitchCustom: function (what, switchName) {
90 | // Metodo generico per ottenre informazioni da uno switch
91 | let myhttp = this;
92 | return new Promise(function (resolve) {
93 | myhttp.get(what + "/" + switchName, {}, function (response) {
94 | resolve(JSON.parse(response)[switchName]);
95 | });
96 | });
97 | },
98 |
99 | updateStatisticsAll: function() {
100 | let myhttp = this;
101 | myhttp.get("stats/switches", {}, function (response) {
102 | let switchNames = JSON.parse(response);
103 | for (let switchName of switchNames) {
104 | myhttp.get("stats/flow/" + switchName, {}, function (response){
105 | response = JSON.parse(response);
106 | // TODO
107 | });
108 | }
109 | });
110 | },
111 |
112 | addFlowEntry: function (rule, callback) {
113 | let myhttp = this;
114 | let openflowRules = [];
115 | rules = this._splitMPLSRule(rule);
116 | rules.forEach((rule, index) => {
117 | let openflow_rule = ruleUtils.openFlowRules.makeOpenFlowRuleFromSimulatedOne(rule, 10000 * (index + 1));
118 | openflowRules.push(openflow_rule);
119 | // Installo la nuova regola nel controller
120 | myhttp.post("stats/flowentry/add", openflow_rule);
121 |
122 | });
123 |
124 | rule.openflowRules = openflowRules;
125 | callback();
126 | },
127 |
128 | _splitMPLSRule: function(rule){
129 | // Le regole MPLS devono essere sdoppiate perché ce ne deve essere 1 per protocollo.
130 | // Per ora i protocolli che vogliamo includere sono: IPv4, ARP
131 | if(rule.actions.concat(rule.matches).some(field => field.name.includes("MPLS"))){
132 | return [rule, rule];
133 | } else return [rule];
134 | },
135 |
136 | removeFlowEntry: function (rule, callback){
137 | let myhttp = this;
138 | // TODO: Non riesco a rimuovere la regola installata di default (quella che invia gli LLDP al controller). E' colpa mia o non si può fare?
139 | if(rule.openflowRules.length == 0) throw new Error("La regola non risulta installata");
140 |
141 | rule.openflowRules.forEach(openflowRule => {
142 | ruleUtils.openFlowRules.removeNonStaticFields(openflowRule);
143 | myhttp.post("stats/flowentry/delete_strict", openflowRule, (/*response, errorCode */) => callback());
144 | });
145 | },
146 |
147 | /* ----------------------------------------------------------- */
148 | /* ----------------- RYU COMMUNICATION ----------------------- */
149 | /* ----------------------------------------------------------- */
150 |
151 | nomeContainerController: null,
152 |
153 | setController: (nomeContainer) => this.nomeContainerController = nomeContainer,
154 |
155 | makeCustom(method, path, params, callback){
156 | if (method == "GET") this.get(path, params, callback);
157 | else if (method == "POST") this.post(path, params, callback);
158 | },
159 |
160 | get: function(path, params, callback){
161 | console.log("GET " + path + " <- " + JSON.stringify(params));
162 | this._myExec("curl http://localhost:8080/" + path + this._makeQueryString(params), callback);
163 | // callback(JSON.stringify(mockRequests.find(el => el.endpoint == path).answer)) // DEV
164 | },
165 |
166 | post: function(path, params, callback){
167 | console.log("POST " + path + " <- " + JSON.stringify(params));
168 | this._myExec("curl -X POST -d '" + JSON.stringify(params) + "' http://localhost:8080/" + path, callback);
169 | },
170 |
171 | _myExec: function (comando, callback){
172 | let dockerCMD = "docker exec " + nomeContainerController + " bash -c \"" + comando.replace(/"/g, '\\"') + "\"";
173 | console.log("EXEC: " + dockerCMD);
174 |
175 | exec(dockerCMD, function(err, stdout, stderr) {
176 | console.log("RES -> " + stdout);
177 | if(callback) callback(stdout);
178 | });
179 | },
180 |
181 | _makeQueryString: function (params) {
182 | var queryString = "?";
183 | for (let par in params) {
184 | queryString += par + "=" + params[par] + "&";
185 | }
186 | return queryString;
187 | }
188 | }
189 |
190 |
191 | // const mockRequests = [ // DEV
192 | // {
193 | // method: "GET",
194 | // endpoint: "v1.0/topology/links",
195 | // answer: [{ src: { hw_addr: "e6:80:13:d5:a8:87", name: "eth1", port_no: "00000001", dpid: "0000c6ed2074a040" }, dst: { hw_addr: "f6:8b:7a:94:bd:2f", name: "eth1", port_no: "00000001", dpid: "00003a443fcbef41" } }, { src: { hw_addr: "f6:8b:7a:94:bd:2f", name: "eth1", port_no: "00000001", dpid: "00003a443fcbef41" }, dst: { hw_addr: "e6:80:13:d5:a8:87", name: "eth1", port_no: "00000001", dpid: "0000c6ed2074a040" } }]
196 | // },
197 | // {
198 | // method: "GET",
199 | // endpoint: "stats/switches",
200 | // answer: [218721754062912, 64064802516801]
201 | // },
202 | // {
203 | // method: "GET",
204 | // endpoint: "stats/portdesc/218721754062912",
205 | // answer: { 218721754062912: [{ hw_addr: "c6:ed:20:74:a0:40", curr: 0, supported: 0, max_speed: 0, advertised: 0, peer: 0, port_no: "LOCAL", curr_speed: 0, name: "br0", state: 1, config: 1 }, { hw_addr: "e6:80:13:d5:a8:87", curr: 2112, supported: 0, max_speed: 0, advertised: 0, peer: 0, port_no: 1, curr_speed: 10000000, name: "eth1", state: 0, config: 0 }, { hw_addr: "e6:7f:53:cf:e0:3e", curr: 2112, supported: 0, max_speed: 0, advertised: 0, peer: 0, port_no: 2, curr_speed: 10000000, name: "eth2", state: 0, config: 0 }] }
206 | // },
207 | // {
208 | // method: "GET",
209 | // endpoint: "stats/portdesc/64064802516801",
210 | // answer: { 64064802516801: [{ hw_addr: "3a:44:3f:cb:ef:41", curr: 0, supported: 0, max_speed: 0, advertised: 0, peer: 0, port_no: "LOCAL", curr_speed: 0, name: "br0", state: 1, config: 1 }, { hw_addr: "f6:8b:7a:94:bd:2f", curr: 2112, supported: 0, max_speed: 0, advertised: 0, peer: 0, port_no: 1, curr_speed: 10000000, name: "eth1", state: 0, config: 0 }, { hw_addr: "62:73:3c:96:1a:ae", curr: 2112, supported: 0, max_speed: 0, advertised: 0, peer: 0, port_no: 2, curr_speed: 10000000, name: "eth2", state: 0, config: 0 }] }
211 | // },
212 | // {
213 | // method: "GET",
214 | // endpoint: "stats/flow/218721754062912",
215 | // answer: { 218721754062912: [{ actions: ["OUTPUT:CONTROLLER"], idle_timeout: 0, "cookie": 0, packet_count: 33, hard_timeout: 0, byte_count: 1980, duration_sec: 29, duration_nsec: 452000000, priority: 65535, length: 96, flags: 0, table_id: 0, match: { dl_type: 35020, dl_dst: "01:80:c2:00:00:0e" } }] }
216 | // },
217 | // {
218 | // method: "GET",
219 | // endpoint: "stats/flow/64064802516801",
220 | // answer: { "64064802516801": [{ actions: ["OUTPUT:CONTROLLER"], idle_timeout: 0, "cookie": 0, packet_count: 35, hard_timeout: 0, byte_count: 2100, duration_sec: 30, duration_nsec: 400000000, priority: 65535, length: 96, flags: 0, table_id: 0, match: { dl_type: 35020, dl_dst: "01:80:c2:00:00:0e" } }] }
221 | // }
222 | // ]
223 |
--------------------------------------------------------------------------------
/src/sdn-manager/simulation.js:
--------------------------------------------------------------------------------
1 | function loadSDN(forceStart) {
2 | ryuActions.setController(document.querySelector("#connect input").value);
3 |
4 | if(!dataStore.isReady() || forceStart) {
5 | ryuActions.getTopology().then(machinesConfig => { // TODO: Riscrivere 'getTopology' e i due metodi per la topologia che seguono in questo file
6 | // Preparo i dati
7 | let simulationData = makeNodesAndEdges(machinesConfig);
8 | dataStore.set(machinesConfig);
9 |
10 | // Preparo l'interfaccia
11 | hide(document.getElementById("connect"));
12 | unhide(document.getElementById("b1"),document.getElementById("b4"));
13 | hide(document.getElementById("b2"));
14 | disableButtons("b3");
15 |
16 | // Avvio la simulazione
17 | startSimulation(simulationData);
18 | });
19 | } else if (confirm("Are you sure?")) {
20 | // Resetto i dati e ricarico la simulazione
21 | for (let svg of document.getElementsByTagName("svg")) svg.innerHTML = "";
22 |
23 | labelsSection.reset();
24 | switchDetailsSection.close();
25 | controllerAndRulesSection.close();
26 |
27 | loadSDN(true);
28 | }
29 | }
30 |
31 | function makeNodesAndEdges(machines){
32 | let data = { nodes: [], links: [] };
33 | let networks = new Set();
34 |
35 | // Costruisco un nodo per ogni macchina con id e tipo
36 | for (let machine of machines) {
37 | if(machine.type != "controller"){
38 | let node = { id: machine.name, type: machine.type };
39 | data.nodes.push(node);
40 |
41 | // Scorro le interfacce
42 | machine.interfaces.if.forEach(function (interfaccia) {
43 | let nomeDominio = interfaccia.eth.domain;
44 | if(nomeDominio != "SDNRESERVED"){
45 | // Per ogni interfaccia creo un collegamento tra un nodo macchina e un nodo dominio
46 | data.links.push({ source: node, target: nomeDominio, porta: interfaccia.eth.number });
47 | // Mi salvo tutti i nomi delle reti che incontro
48 | networks.add(nomeDominio);
49 | }
50 | });
51 | }
52 | }
53 |
54 | // Creo i nodi dominio con id e tipo
55 | networks.forEach(domainName => {
56 | if(domainName.includes("Network")) data.nodes.push({ id: domainName, type: "network edge" }); // TODO: E' un rimedio temporaneo
57 | else data.nodes.push({ id: domainName, type: "network" });
58 | });
59 |
60 | findEdgeNetworks(data);
61 | return data;
62 | }
63 |
64 | function findEdgeNetworks(data) {
65 | // Divido le reti in base al loro tipo:
66 | // - edge = comunica con qualche macchinario non-sdn;
67 | // - external = non fa parte della sottorete sdn.
68 |
69 | data.nodes.forEach(function (node) {
70 | if (node.type == "network") {
71 | let isInternal = false;
72 | let isExternal = false;
73 | for (let link of data.links) {
74 | if (link.target == node.id) {
75 | if (link.source.type == "switch") isInternal = true;
76 | else if (link.source.type != "switch") isExternal = true;
77 | }
78 |
79 | if (isInternal && isExternal) {
80 | node.type += " edge";
81 | break;
82 | }
83 | }
84 | if (!isInternal) node.type += " external";
85 | }
86 | });
87 | }
88 |
89 | function startSimulation(data) {
90 | let svg = d3.select("#sdnGraph");
91 | svg.node().style.display = "";
92 |
93 | /* --------------------- PREPARE LINKS --------------------- */
94 |
95 | let linksGroup = svg.append("g")
96 | .attr("class", "links")
97 | .selectAll("line")
98 | .data(data.links)
99 | .enter().append("line");
100 |
101 | linksGroup.append("title").text(d => "eth" + d.porta);
102 |
103 | /* --------------------- PREPARE NODES --------------------- */
104 |
105 | let nodesGroup = svg.append("g")
106 | .attr("class", "nodes")
107 | .selectAll("circle")
108 | .data(data.nodes)
109 | .enter().append("circle")
110 | .attr("r", d => (d.type == "network" || d.type == "network edge") ? 15 : 25)
111 | .attr("class", function (d) { return d.type; })
112 |
113 | nodesGroup.append("title").text(d => d.id);
114 |
115 | /* ------------------- CREATE SIMULATION ------------------- */
116 |
117 | let simulation = d3.forceSimulation(data.nodes) // <-- Da ora ogni nodo ha in più: index, x, y, vx, vy
118 | .force("link", d3.forceLink(data.links)
119 | .id(d => d.id) // <-- specificando id posso riferirmi ai nodi attraverso il loro campo 'id' piuttosto che al loro indice nell'array dei nodi
120 | .distance(d => d.source.type == "switch" ? 80 : 60)
121 | )
122 | .force("anticollision", d3.forceCollide().radius(70))
123 | .force("X", d3.forceX(400).strength(0.06))
124 | .force("Y", d3.forceY(450).strength(0.06))
125 |
126 | /* ---------------------- */
127 |
128 | // Specifico come si aggiorna la simulazione ad ogni passo
129 | simulation.on("tick", function () {
130 | linksGroup
131 | .attr("x1", function (d) { return d.source.x; })
132 | .attr("y1", function (d) { return d.source.y; })
133 | .attr("x2", function (d) { return d.target.x; })
134 | .attr("y2", function (d) { return d.target.y; });
135 |
136 | nodesGroup
137 | .attr("cx", function (d) {
138 | if(d.x < 25) return 25;
139 | else if (d.x > 875) return 875; // 900 (dimensione orizzontale del SVG) - 25 (raggio massimo di un nodo)
140 | else return d.x;
141 | })
142 | .attr("cy", function (d) {
143 | if(d.y < 0) return 25;
144 | else if (d.y > 775) return 775; // 800 (dimensione verticale del SVG) - 25 (raggio massimo di un nodo)
145 | else return d.y;
146 | });
147 | });
148 |
149 | // Avvio la simulazione
150 | dataStore.setSimulation(simulation);
151 |
152 | // Classifico i link in base ai nodi che esso collega
153 | linksGroup.attr("class", function (d) { return d.target.type + " " + d.source.type; }); // <-- Solo dopo aver creato la simulazione ogni link è collegato ai suoi nodi
154 |
155 | // Aggiungo l'interazione al click ai nodi
156 | d3.selectAll("g.nodes circle.switch").on("click", d => switchDetailsSection.open(d.id));
157 |
158 | // Aggiungo al nodo HTML che rappresenta l'SVG alcune definizioni: non le metto direttamente nell'HTML per semplicita nel processo di reset del grafico
159 | appendMarkersDefinitions(svg);
160 | }
161 |
162 | function appendMarkersDefinitions(svg){
163 | // Creando ora una definizione di un marcatore potrò poi usara quando necessario.
164 | // Questi marcatori sono le punte delle frecce che indicheranno il verso del flusso.
165 |
166 | let defs = svg.append("defs");
167 |
168 | defs.append("marker") // Questo marcatore va bene con marker-start
169 | .attr("id", "markerArrow1")
170 | .attr("markerWidth", "10").attr("markerHeight", "10")
171 | .attr("refY", "3").attr("refX", "-5")
172 | .attr("orient", "auto")
173 | .append("path")
174 | .attr("d", "M5,1 L5,5 L1,3 L5,1") // Triangolo con punta a destra
175 | .style("fill", "red");
176 |
177 | defs.append("marker") // Questo marcatore va bene con marker-end
178 | .attr("id", "markerArrow2")
179 | .attr("markerWidth", "10").attr("markerHeight", "10")
180 | .attr("refY", "3").attr("refX", "9")
181 | .attr("orient", "auto")
182 | .append("path")
183 | .attr("d", "M1,1 L1,5 L5,3 L1,1") // Triangolo con punta a sinistra
184 | .style("fill", "red");
185 | }
186 |
187 | /* --------------------------------------------------- */
188 | /* --------------- INTERACT WITH GRAPH --------------- */
189 | /* --------------------------------------------------- */
190 |
191 | /* ---------------------- MOVE ---------------------- */
192 |
193 | function enableMovingNodes() {
194 | hide(document.getElementById("b1"));
195 | unhide(document.getElementById("b2"));
196 |
197 | let simulation = dataStore.getSimulation();
198 | simulation.force("X").strength(0.01);
199 | simulation.force("Y").strength(0.01);
200 |
201 | d3.selectAll("g.nodes circle").call( // call chiama la funzione passata per parametro esattamente una volta sola
202 | d3.drag().on("start", function () {
203 | if (!d3.event.active) simulation.alphaTarget(0.3).restart();
204 | }).on("drag", function (d) {
205 | d.fx = d3.event.x;
206 | d.fy = d3.event.y;
207 | }).on("end", function () {
208 | if (!d3.event.active) simulation.alphaTarget(0);
209 | enableButtons("b3");
210 | })
211 | );
212 | }
213 |
214 | function releaseNodes() {
215 | let released = false;
216 | d3.selectAll("g.nodes circle").each(function (d) {
217 | released = (released || d.fx);
218 | d.fx = null;
219 | d.fy = null;
220 | });
221 |
222 | if (released) {
223 | let simulation = dataStore.getSimulation();
224 | simulation.force("X").strength(0.05);
225 | simulation.force("Y").strength(0.05);
226 |
227 | disableButtons("b3");
228 | simulation.alphaTarget(0.1).restart();
229 | setTimeout(() => simulation.alphaTarget(0), 3000);
230 | }
231 | }
232 |
233 | /* ----------------- PATH SELECTION ----------------- */
234 |
235 | function disableDragging() {
236 | d3.selectAll("g.nodes circle").call(d3.drag());
237 | }
238 |
239 | function enablePathSelection() {
240 | unhide(document.getElementById("b1"));
241 | hide(document.getElementById("b2"));
242 |
243 | disableDragging();
244 |
245 | if (labelsSection.isEditing()) {
246 | let machineLocked = true;
247 | let networksLocked = true;
248 | let linkLock = 0;
249 | let lastSelection = null;
250 | let startsFromEdge = false;
251 | let lastSelectedIsEdge = false;
252 |
253 | d3.selectAll("circle.network:not(.external)").call(d3.drag()
254 | .on("start", function (d, i, data) {
255 | if(data[i].classList.contains("edge"))
256 | startsFromEdge = true;
257 | data[i].classList.add("selected");
258 |
259 | linkLock = 1;
260 | lastSelection = d.id;
261 | })
262 | .on("end", function (/* d, i, data */) {
263 | if (linkLock == 1 && dataStore.pathHasAtLeastOneStep()){
264 | if(startsFromEdge)
265 | dataStore.setEdgeProperties(true, false);
266 | if(lastSelectedIsEdge)
267 | dataStore.setEdgeProperties(false, true);
268 |
269 | let confirmBtnDivStyle = document.getElementById('confirm-buttons').style;
270 | confirmBtnDivStyle.top = null;
271 | confirmBtnDivStyle.opacity = null;
272 | } else discardPath();
273 |
274 | startsFromEdge = false;
275 | lastSelectedIsEdge = false;
276 | machineLocked = true;
277 | networksLocked = true;
278 | linkLock = 0;
279 | }));
280 |
281 | d3.selectAll("circle.network:not(.external)") // Link lock è 0
282 | .on("mouseover", function (d, i, data) {
283 | if (!networksLocked && d.id == lastSelection && !data[i].classList.contains("selected")) {
284 | linkLock++;
285 | networksLocked = true;
286 |
287 | if(data[i].classList.contains("edge"))
288 | lastSelectedIsEdge = true;
289 | else lastSelectedIsEdge = false;
290 |
291 | data[i].classList.add("selected");
292 | }
293 | });
294 |
295 | d3.selectAll("line.switch")
296 | .on("mouseover", function (d, i, data) {
297 | if (linkLock == 1 && d.target.id == lastSelection && !data[i].classList.contains("selected")) {
298 | linkLock++;
299 | machineLocked = false;
300 |
301 | lastSelection = d.source.id;
302 | data[i].classList.add("selected"); // Seleziona una rete. La prossima sarà una macchina
303 | dataStore.appendPathStep({ device: d.source.id, ingressPort: d.porta });
304 | } else if (linkLock == 3 && d.source.id == lastSelection) {
305 | linkLock = 0;
306 | networksLocked = false;
307 |
308 | lastSelection = d.target.id;
309 | data[i].classList.add("selected"); // Seleziona una macchina. La prossima sarà una rete
310 | dataStore.appendPathStep({ device: d.source.id, egressPort: d.porta });
311 | }
312 | });
313 |
314 | d3.selectAll("circle.switch") // linkLock è 2
315 | .on("mouseover", function (d, i, data) {
316 | if (!machineLocked && d.id == lastSelection) {
317 | linkLock++;
318 | machineLocked = true;
319 |
320 | data[i].classList.add("selected");
321 | }
322 | });
323 | }
324 | }
325 |
326 | function removeNodesSelection(forceRemove = false) {
327 | if(!dataStore.pathHasAtLeastOneStep() || forceRemove) {
328 | document.querySelectorAll("svg .selected")
329 | .forEach(function (el) { return el.classList.remove("selected", "straight", "reversed"); });
330 | }
331 | }
332 |
333 | function applyPath() {
334 | dataStore.getPath().forEach(step => labelsSection.addRuleStep(step));
335 | discardPath();
336 | }
337 |
338 | function discardPath() {
339 | dataStore.discardPath();
340 |
341 | let confirmBtnDivStyle = document.getElementById('confirm-buttons').style;
342 | confirmBtnDivStyle.top = '0';
343 | confirmBtnDivStyle.opacity = '0';
344 |
345 | removeNodesSelection(true);
346 | }
347 |
348 | /* ----------------- RULE HIGHLIGHT ----------------- */
349 |
350 | function highlightSegmentOnGraph(device, from, to) {
351 | d3.selectAll("circle.switch")
352 | .each(function(d, i, nodes){
353 | if (d.id == device) nodes[i].classList.add("selected");
354 | });
355 |
356 | d3.selectAll("line.switch")
357 | .each(function(d, i, nodes){
358 | if (d.source.id == device){
359 | if (d.porta == from){
360 | nodes[i].classList.add("selected");
361 | nodes[i].classList.add("straight");
362 | } else if (d.porta == to){
363 | nodes[i].classList.add("selected");
364 | nodes[i].classList.add("reversed");
365 | }
366 | }
367 | });
368 | }
--------------------------------------------------------------------------------
/src/sdn-manager/rules-utils/ruleUtils.js:
--------------------------------------------------------------------------------
1 | const ruleUtils = {
2 |
3 | /* ------------------------------------------------------------------- */
4 | /* ------------------------- SIMULATED RULES ------------------------- */
5 | /* ------------------------------------------------------------------- */
6 | simulatedRules: {
7 | /**
8 | * "Simulated" rules are easier to read because they have a graphical representation of their label
9 | * which consists in label name and label color. This method takes a rule and creates the field
10 | * (inside existing fields "matches" and "actions") that hosts this representation.
11 | * Eventually it adds new discovered labels to the list of known ones
12 | *
13 | * Le regole "simulated" sono di più semplice comprensione perché hanno una rappresentazione grafica
14 | * dell'etichetta (colore e nome). Questo metodo si occupa di creare il campo all'interno del match MPLS
15 | * o dell'azione MPLS che permette l'individuazione del nome e del colore corretti dell'etichetta.
16 | * Infine aggiunge le etichette nuove alla lista delle etichette conosciute.
17 | */
18 | addAPointerToLabelInTheRuleMPLSFields_andAddLablesToLabelsSection: function (rule, forceOverride){
19 | let labelNames = [];
20 | for(let field of rule.matches.concat(rule.actions)){
21 | if(!field.label || forceOverride){
22 | if(field.name.includes("MPLS")){
23 | // Cerco il nome dell'etichetta associata all'azione di POP
24 | if(field.name == "pop MPLS label"){
25 | // L'azione di POP ha il parametro ethertype e non l'etichetta => cerco l'etichetta nel match
26 | let mplsMatch = rule.matches.find(match => match.name == "MPLS label");
27 | if (mplsMatch) field.value = mplsMatch.value;
28 | }
29 |
30 | // Cerco il nome dell'etichetta associata all'azione di PUSH
31 | if(field.name == "set MPLS label"){
32 | // L'azione di PUSH è composta di due regole: 1) metti l'etichetta 2) edita il suo campo tag
33 | let mplsMatch = rule.actions.find(action => action.name == "set field" && action.value.includes("mpls"));
34 | if (mplsMatch) field.value = mplsMatch.value.match(/:(\w+)/)[1];
35 | }
36 |
37 | // Oss. Anche se non sto esaminando una POP o una PUSH posso trovare un'etichetta
38 | let labelName = field.value;
39 |
40 | // Ora ho il nome; cerco il colore oppure lo creo (se è un'etichetta non incontrata in precedenza)
41 | let labelColor = labelsSection.getLabelColor(labelName);
42 | if(!labelColor){
43 | labelColor = "#" + Math.floor(Math.random() * 899 + 100);
44 | labelsSection.createNewLabel(labelName, labelColor);
45 | }
46 |
47 | // Aggiungo un campo "label" all'azione o al match così potrò ricostruire nome e colore nella grafica
48 | field.label = { name: labelName, color: labelColor };
49 |
50 | // Metto da parte tutte le nuove etichette così dopo le potrò aggiungere all'elenco generale (laeblsSection)
51 | if(!labelNames.includes(labelName)){
52 | labelNames.push(labelName);
53 | }
54 |
55 | } else if(field.name == "set field" && field.value.includes("mpls")){
56 | // Attacco un campo "label" anche all'azione "set field"
57 | let labelName = field.value.match(/:(\w+)/)[1];
58 | field.label = {
59 | name: labelName,
60 | color: labelsSection.getLabelColor(labelName)
61 | };
62 | //Oss. Non aggiungo l'etichetta all'elenco perché immagino che già ci sia (ogni set field segue una PUSH)
63 | }
64 | }
65 | }
66 |
67 | setTimeout(() => {
68 | // Devo ritardare l'aggiunta perché Vue.js potrebbe non aver ancora creato l'elemento label
69 | labelNames.forEach( labelName => labelsSection.addRuleToChosenLabel(rule, labelName) );
70 | }, 1000);
71 | },
72 |
73 | /**
74 | * Creates and returns the structure of a new simulated rule after filling its filds.
75 | * Some arguments are optional but they must be: 3, 7 or 8.
76 | */
77 | createNewRule: function(...values){
78 | let device,
79 | matches,
80 | actions,
81 | priority = 0,
82 | table = 0,
83 | idleTimeout = 0,
84 | hardTimeout = 0,
85 | stats = 0,
86 | deleted = false,
87 | submitted = false,
88 | edited = false;
89 |
90 | if(values.length == 3){
91 | [device, matches, actions] = values;
92 | } else if(values.length == 7){
93 | [device, matches, actions, priority, table, idleTimeout, hardTimeout] = values;
94 | } else if(values.length == 8){
95 | [device, matches, actions, priority, table, idleTimeout, hardTimeout, stats] = values;
96 | submitted = true;
97 | } else throw new Error("dataStore createNewRule: arguments lenght mismatch");
98 |
99 | let { checkedMatches, checkedActions } = this.checkActionsAndMatches(matches, actions);
100 | matches = checkedMatches;
101 | actions = checkedActions;
102 |
103 | let rule = { device, matches, actions, priority, table, idleTimeout, hardTimeout, stats, deleted, submitted, edited };
104 |
105 | this.checkRuleHasOneLabelOnly(rule);
106 | return rule;
107 | },
108 |
109 | checkActionsAndMatches: function(matches, actions){
110 | matches = JSON.parse(JSON.stringify(matches));
111 | actions = JSON.parse(JSON.stringify(actions));
112 |
113 | if(!Array.isArray(matches)) matches = [matches];
114 | if(!Array.isArray(actions)) actions = [actions];
115 |
116 | let checkedFields = { checkedMatches: matches, checkedActions: actions };
117 | return checkedFields;
118 | },
119 |
120 | /**
121 | * Checks that the rule only sets 1 mpls label and
122 | * that it does not add a new label if it already has one
123 | */
124 | checkRuleHasOneLabelOnly: function(rule){
125 | let error = new Error("This version of Kathara-SDN only supports 1 label per rule");
126 |
127 | // Se la regola fa match su un'etichetta MPLS non posso settare un'altra etichetta
128 | if(rule.matches.some(match => match.name.includes("MPLS")) &&
129 | rule.actions.some(action => action.name == "set MPLS label")){
130 | throw error;
131 | }
132 |
133 | // Non posso fare due volte 'set MPLS label'
134 | let setsNewMPLS = false;
135 | for(let action of rule.actions){
136 | if (action.name == "set MPLS label") {
137 | if(setsNewMPLS) throw error;
138 | else setsNewMPLS = true;
139 | }
140 | }
141 | },
142 |
143 | makeSimulatedRuleFromOpenFlowOne: function(switchName, openflowRule){
144 | let newRule = this.createNewRule(
145 | switchName,
146 | rulesMapper.reverseMatch(openflowRule.match),
147 | rulesMapper.reverseActions(openflowRule.actions),
148 | openflowRule.priority,
149 | openflowRule.table_id,
150 | openflowRule.idle_timeout,
151 | openflowRule.hard_timeout,
152 | openflowRule.packet_count
153 | );
154 | newRule.openflowRules = [openflowRule];
155 |
156 | // Le regole openflow di PUSH hanno due azioni: 1) metti il protocollo MPLS 2) cambio il campo MPLS inserendo la label
157 | // Nelle regole simulate voglio che la label sia direttamente nell'azione di PUSH
158 | let setFieldAction = newRule.actions.find(action => action.name === "set field");
159 | if(setFieldAction){
160 | let [fieldName, fieldValue] = setFieldAction.value.split(":");
161 | if(fieldName === "mpls"){
162 | newRule.actions.forEach(action => {
163 | if (action.name === "set MPLS label") action.value = fieldValue;
164 | });
165 | }
166 | }
167 |
168 | return newRule;
169 | },
170 |
171 | mergeMPLSRulesWithDifferentProtocolMatch: function(rules){
172 | let matchedRules = [];
173 | let mergedRules = [];
174 | rules.forEach(rule1 => {
175 |
176 | // Con queste due righe evito di rileggere regole già analizzate o già accoppiate
177 | if(!matchedRules.includes(rule1)){
178 | matchedRules.push(rule1);
179 |
180 | // Cerco la regola corrispondente a rule1
181 | let sibling = rules.find(rule2 => !matchedRules.includes(rule2) && this.isSameRule(rule1, rule2, true));
182 |
183 | if(sibling){
184 | // Se la trovo la unisco a rule1 e ne memorizzo una sola
185 | matchedRules.push(sibling);
186 | mergedRules.push(this.mergeMPLSSiblingRules(rule1, sibling));
187 | } else {
188 | // Se non la trovo, la riporto in output perché è una regola a sé stante
189 | mergedRules.push(rule1);
190 | }
191 | }
192 | });
193 | return mergedRules;
194 | },
195 |
196 | isSameRule: function(rule1, rule2, ignoreDifferentProtocolsOfSameMPLSRule){
197 | let matches1 = rule1.matches,
198 | actions1 = rule1.actions;
199 |
200 | let matches2 = rule2.matches,
201 | actions2 = rule2.actions;
202 |
203 | let ignoreMatchEthertype = rule1.actions.some(action => action.name === "set MPLS label");
204 |
205 | return rule1.device === rule2.device && rule1.priority === rule2.priority
206 | && rule1.table === rule2.table && rule1.idleTimeout === rule2.idleTimeout
207 | && rule1.hardTimeout === rule2.hardTimeout
208 | && this.isSameActions(actions1, actions2, ignoreDifferentProtocolsOfSameMPLSRule)
209 | && this.isSameMatches(matches1, matches2, ignoreDifferentProtocolsOfSameMPLSRule && ignoreMatchEthertype);
210 | },
211 |
212 | isSameActions: function(actions1, actions2, ignoreDifferentProtocolsOfSameMPLSActions){
213 | // Conta anche l'indice: le azioni devono essere nello stesso ordine
214 | return actions1.length === actions2.length &&
215 | actions1.every((action1, index) => this.isSameAction(action1, actions2[index], ignoreDifferentProtocolsOfSameMPLSActions));
216 | },
217 |
218 | isSameMatches: function(matches1, matches2, ignoreDifferentProtocolsOfSameMPLSMatches){
219 | let ignoreEthertype = ignoreDifferentProtocolsOfSameMPLSMatches
220 | || matches1.some(match => match.name === "MPLS label");
221 |
222 | let match2CheHannoFattoMatch = [];
223 | return matches1.length === matches2.length &&
224 | matches1.every(match1 => {
225 | matches2.some(match2 => {
226 | if(match2CheHannoFattoMatch.includes(match2)) return false;
227 |
228 | if(this.isSameMatch(match1, match2, ignoreEthertype)){
229 | match2CheHannoFattoMatch.push(match2);
230 | return true;
231 | }
232 | });
233 | });
234 | },
235 |
236 | isSameAction: function(action1, action2, ignoreDifferentProtocolsOfSameMPLSAction){
237 | return (action1.name === action2.name) && (action1.value === action2.value
238 | || (ignoreDifferentProtocolsOfSameMPLSAction && action1.name === "pop MPLS label"));
239 | },
240 |
241 | isSameMatch: function(match1, match2, ignoreEthertype){
242 | return match1.name === match2.name
243 | && (match1.value === match2.value
244 | || (ignoreEthertype && match1.name === "ethertype"));
245 | },
246 |
247 | mergeMPLSSiblingRules: function(rule1, rule2){
248 | let isMPLSMatch = rule1.matches.some(match => match.name === "MPLS label");
249 | let isPOPMPLS = rule1.actions.some(action => action.name === "pop MPLS label"); // TODO: non lo uso. Serve?
250 | let isPUSHMPLS = rule1.actions.some(action => action.name === "set MPLS label");
251 |
252 | let mergedMatches = [];
253 | rule1.matches.forEach(match => {
254 | if(!(isMPLSMatch && match.name === "ethertype" && match.value === "MPLS")
255 | && !(isPUSHMPLS && match.name === "ethertype")){
256 | mergedMatches.push(match);
257 | }
258 | });
259 |
260 | // Unire le actions vuol dire unire "PUSH MPLS label" e "set field",
261 | // ovvero rimuovere "set field"
262 | let meregdActions = [];
263 | rule1.actions.forEach(action => {
264 | if(action.name != "set field") meregdActions.push(action);
265 | });
266 |
267 | let newRule = this.createNewRule(
268 | rule1.device,
269 | mergedMatches,
270 | meregdActions,
271 | rule1.priority,
272 | rule1.table,
273 | rule1.idleTimeout,
274 | rule1.hardTimeout,
275 | (+rule1.stats + +rule2.stats)
276 | );
277 | newRule.openflowRules = rule1.openflowRules.concat(rule2.openflowRules);
278 |
279 | return newRule;
280 | }
281 | },
282 |
283 |
284 |
285 | /* ------------------------------------------------------------------ */
286 | /* ------------------------- OPENFLOW RULES ------------------------- */
287 | /* ------------------------------------------------------------------ */
288 | openFlowRules: {
289 | makeOpenFlowRuleFromSimulatedOne: function(simRule, offset){
290 | return {
291 | dpid: simRule.device,
292 | table_id: +simRule.table,
293 | idle_timeout: simRule.idleTimeout,
294 | hard_timeout: simRule.hardTimeout,
295 | priority: simRule.priority,
296 | match: rulesMapper.makeMatch(simRule.matches, offset),
297 | actions: rulesMapper.makeActions(simRule.actions, offset)
298 | };
299 | },
300 |
301 | removeNonStaticFields: function(ofRule){
302 | // Una regola in uso ha molti campi dinamici come ad esempio le statistiche.
303 | // Per poter fare match su una regola esistente mi limito a cercare i campi statici,
304 | // quindi rimuovo quelli dinamici
305 | if(ofRule.packet_count != undefined) delete ofRule.packet_count;
306 | if(ofRule.byte_count != undefined) delete ofRule.byte_count;
307 | if(ofRule.duration_sec != undefined) delete ofRule.duration_sec;
308 | if(ofRule.duration_nsec != undefined) delete ofRule.duration_nsec;
309 | if(ofRule.length != undefined) delete ofRule.length;
310 |
311 | // TODO: Questi due non so se sono statici... Per ora li rimuovo
312 | if(ofRule.flags != undefined) delete ofRule.flags;
313 | if(ofRule.cookie != undefined) delete ofRule.cookie;
314 | }
315 | }
316 | };
--------------------------------------------------------------------------------
/examples/example_SDN.config:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "labInfo": {
4 | "description": "",
5 | "version": "",
6 | "author": "",
7 | "email": "",
8 | "web": "",
9 | "toggle": "enable"
10 | },
11 | "netkit": [
12 | {
13 | "name": "controller",
14 | "row": 1,
15 | "type": "controller",
16 | "interfaces": {
17 | "counter": 1,
18 | "if": [
19 | {
20 | "eth": {
21 | "number": 0,
22 | "domain": "SDNRESERVED"
23 | },
24 | "ip": "192.168.100.1/24",
25 | "$$hashKey": "object:20"
26 | }
27 | ],
28 | "free": ""
29 | },
30 | "gateways": {
31 | "counter": 1,
32 | "gw": [
33 | {
34 | "route": "",
35 | "if": 0,
36 | "$$hashKey": "object:23"
37 | }
38 | ]
39 | },
40 | "pc": {
41 | "dns": "-"
42 | },
43 | "ws": {
44 | "userdir": false
45 | },
46 | "ns": {
47 | "recursion": true,
48 | "authority": true
49 | },
50 | "other": {
51 | "image": "",
52 | "files": [],
53 | "fileCounter": 0
54 | },
55 | "ryu": {
56 | "simple": false,
57 | "stp": false,
58 | "rest": true,
59 | "topology": true,
60 | "custom": "",
61 | "observelinks": true
62 | },
63 | "routing": {
64 | "rip": {
65 | "en": false,
66 | "connected": false,
67 | "ospf": false,
68 | "bgp": false,
69 | "network": [
70 | ""
71 | ],
72 | "route": [
73 | ""
74 | ],
75 | "free": ""
76 | },
77 | "ospf": {
78 | "en": false,
79 | "connected": false,
80 | "rip": false,
81 | "bgp": false,
82 | "if": [],
83 | "network": [
84 | ""
85 | ],
86 | "area": [
87 | "0.0.0.0"
88 | ],
89 | "stub": [
90 | false
91 | ],
92 | "free": ""
93 | },
94 | "bgp": {
95 | "en": false,
96 | "as": "",
97 | "network": [
98 | ""
99 | ],
100 | "remote": [
101 | {
102 | "neighbor": "",
103 | "as": "",
104 | "description": ""
105 | }
106 | ],
107 | "free": ""
108 | }
109 | },
110 | "_uid": 991127739483138,
111 | "$$hashKey": "object:26"
112 | },
113 | {
114 | "name": "left",
115 | "row": 2,
116 | "type": "switch",
117 | "interfaces": {
118 | "counter": 3,
119 | "if": [
120 | {
121 | "eth": {
122 | "number": 0,
123 | "domain": "SDNRESERVED"
124 | },
125 | "ip": "192.168.100.2/24",
126 | "$$hashKey": "object:82"
127 | },
128 | {
129 | "eth": {
130 | "number": 1,
131 | "domain": "A"
132 | },
133 | "$$hashKey": "object:194"
134 | },
135 | {
136 | "eth": {
137 | "number": 2,
138 | "domain": "B"
139 | },
140 | "$$hashKey": "object:345"
141 | }
142 | ],
143 | "free": ""
144 | },
145 | "gateways": {
146 | "counter": 1,
147 | "gw": [
148 | {
149 | "route": "",
150 | "if": 0,
151 | "$$hashKey": "object:85"
152 | }
153 | ]
154 | },
155 | "pc": {
156 | "dns": "-"
157 | },
158 | "ws": {
159 | "userdir": false
160 | },
161 | "ns": {
162 | "recursion": true,
163 | "authority": true
164 | },
165 | "other": {
166 | "image": "",
167 | "files": [],
168 | "fileCounter": 0
169 | },
170 | "ryu": {
171 | "simple": false,
172 | "stp": false,
173 | "rest": true,
174 | "topology": true,
175 | "custom": "",
176 | "observelinks": true
177 | },
178 | "routing": {
179 | "rip": {
180 | "en": false,
181 | "connected": false,
182 | "ospf": false,
183 | "bgp": false,
184 | "network": [
185 | ""
186 | ],
187 | "route": [
188 | ""
189 | ],
190 | "free": ""
191 | },
192 | "ospf": {
193 | "en": false,
194 | "connected": false,
195 | "rip": false,
196 | "bgp": false,
197 | "if": [],
198 | "network": [
199 | ""
200 | ],
201 | "area": [
202 | "0.0.0.0"
203 | ],
204 | "stub": [
205 | false
206 | ],
207 | "free": ""
208 | },
209 | "bgp": {
210 | "en": false,
211 | "as": "",
212 | "network": [
213 | ""
214 | ],
215 | "remote": [
216 | {
217 | "neighbor": "",
218 | "as": "",
219 | "description": ""
220 | }
221 | ],
222 | "free": ""
223 | }
224 | },
225 | "_uid": 80198070964318,
226 | "$$hashKey": "object:74"
227 | },
228 | {
229 | "name": "right",
230 | "row": 3,
231 | "type": "switch",
232 | "interfaces": {
233 | "counter": 3,
234 | "if": [
235 | {
236 | "eth": {
237 | "number": 0,
238 | "domain": "SDNRESERVED"
239 | },
240 | "ip": "192.168.100.3/24",
241 | "$$hashKey": "object:145"
242 | },
243 | {
244 | "eth": {
245 | "number": 1,
246 | "domain": "A"
247 | },
248 | "$$hashKey": "object:208"
249 | },
250 | {
251 | "eth": {
252 | "number": 2,
253 | "domain": "C"
254 | },
255 | "$$hashKey": "object:361"
256 | }
257 | ],
258 | "free": ""
259 | },
260 | "gateways": {
261 | "counter": 1,
262 | "gw": [
263 | {
264 | "route": "",
265 | "if": 0,
266 | "$$hashKey": "object:148"
267 | }
268 | ]
269 | },
270 | "pc": {
271 | "dns": "-"
272 | },
273 | "ws": {
274 | "userdir": false
275 | },
276 | "ns": {
277 | "recursion": true,
278 | "authority": true
279 | },
280 | "other": {
281 | "image": "",
282 | "files": [],
283 | "fileCounter": 0
284 | },
285 | "ryu": {
286 | "simple": false,
287 | "stp": false,
288 | "rest": true,
289 | "topology": true,
290 | "custom": "",
291 | "observelinks": true
292 | },
293 | "routing": {
294 | "rip": {
295 | "en": false,
296 | "connected": false,
297 | "ospf": false,
298 | "bgp": false,
299 | "network": [
300 | ""
301 | ],
302 | "route": [
303 | ""
304 | ],
305 | "free": ""
306 | },
307 | "ospf": {
308 | "en": false,
309 | "connected": false,
310 | "rip": false,
311 | "bgp": false,
312 | "if": [],
313 | "network": [
314 | ""
315 | ],
316 | "area": [
317 | "0.0.0.0"
318 | ],
319 | "stub": [
320 | false
321 | ],
322 | "free": ""
323 | },
324 | "bgp": {
325 | "en": false,
326 | "as": "",
327 | "network": [
328 | ""
329 | ],
330 | "remote": [
331 | {
332 | "neighbor": "",
333 | "as": "",
334 | "description": ""
335 | }
336 | ],
337 | "free": ""
338 | }
339 | },
340 | "_uid": 61839701672449,
341 | "$$hashKey": "object:136"
342 | },
343 | {
344 | "name": "pcleft",
345 | "row": 4,
346 | "type": "terminal",
347 | "interfaces": {
348 | "counter": 1,
349 | "if": [
350 | {
351 | "eth": {
352 | "number": 0,
353 | "domain": "B"
354 | },
355 | "ip": "192.168.0.1/16",
356 | "$$hashKey": "object:230"
357 | }
358 | ],
359 | "free": ""
360 | },
361 | "gateways": {
362 | "counter": 1,
363 | "gw": [
364 | {
365 | "route": "",
366 | "if": 0,
367 | "$$hashKey": "object:233"
368 | }
369 | ]
370 | },
371 | "pc": {
372 | "dns": "-"
373 | },
374 | "ws": {
375 | "userdir": false
376 | },
377 | "ns": {
378 | "recursion": true,
379 | "authority": true
380 | },
381 | "other": {
382 | "image": "",
383 | "files": [],
384 | "fileCounter": 0
385 | },
386 | "ryu": {
387 | "simple": false,
388 | "stp": false,
389 | "rest": true,
390 | "topology": true,
391 | "custom": "",
392 | "observelinks": true
393 | },
394 | "routing": {
395 | "rip": {
396 | "en": false,
397 | "connected": false,
398 | "ospf": false,
399 | "bgp": false,
400 | "network": [
401 | ""
402 | ],
403 | "route": [
404 | ""
405 | ],
406 | "free": ""
407 | },
408 | "ospf": {
409 | "en": false,
410 | "connected": false,
411 | "rip": false,
412 | "bgp": false,
413 | "if": [],
414 | "network": [
415 | ""
416 | ],
417 | "area": [
418 | "0.0.0.0"
419 | ],
420 | "stub": [
421 | false
422 | ],
423 | "free": ""
424 | },
425 | "bgp": {
426 | "en": false,
427 | "as": "",
428 | "network": [
429 | ""
430 | ],
431 | "remote": [
432 | {
433 | "neighbor": "",
434 | "as": "",
435 | "description": ""
436 | }
437 | ],
438 | "free": ""
439 | }
440 | },
441 | "_uid": 289189061001735,
442 | "$$hashKey": "object:220"
443 | },
444 | {
445 | "name": "pcright",
446 | "row": 5,
447 | "type": "terminal",
448 | "interfaces": {
449 | "counter": 1,
450 | "if": [
451 | {
452 | "eth": {
453 | "number": 0,
454 | "domain": "C"
455 | },
456 | "ip": "192.168.0.2/16",
457 | "$$hashKey": "object:297"
458 | }
459 | ],
460 | "free": ""
461 | },
462 | "gateways": {
463 | "counter": 1,
464 | "gw": [
465 | {
466 | "route": "",
467 | "if": 0,
468 | "$$hashKey": "object:300"
469 | }
470 | ]
471 | },
472 | "pc": {
473 | "dns": "-"
474 | },
475 | "ws": {
476 | "userdir": false
477 | },
478 | "ns": {
479 | "recursion": true,
480 | "authority": true
481 | },
482 | "other": {
483 | "image": "",
484 | "files": [],
485 | "fileCounter": 0
486 | },
487 | "ryu": {
488 | "simple": false,
489 | "stp": false,
490 | "rest": true,
491 | "topology": true,
492 | "custom": "",
493 | "observelinks": true
494 | },
495 | "routing": {
496 | "rip": {
497 | "en": false,
498 | "connected": false,
499 | "ospf": false,
500 | "bgp": false,
501 | "network": [
502 | ""
503 | ],
504 | "route": [
505 | ""
506 | ],
507 | "free": ""
508 | },
509 | "ospf": {
510 | "en": false,
511 | "connected": false,
512 | "rip": false,
513 | "bgp": false,
514 | "if": [],
515 | "network": [
516 | ""
517 | ],
518 | "area": [
519 | "0.0.0.0"
520 | ],
521 | "stub": [
522 | false
523 | ],
524 | "free": ""
525 | },
526 | "bgp": {
527 | "en": false,
528 | "as": "",
529 | "network": [
530 | ""
531 | ],
532 | "remote": [
533 | {
534 | "neighbor": "",
535 | "as": "",
536 | "description": ""
537 | }
538 | ],
539 | "free": ""
540 | }
541 | },
542 | "_uid": 650848645507238,
543 | "$$hashKey": "object:286"
544 | }
545 | ]
546 | }
547 | ]
--------------------------------------------------------------------------------
/examples/example_lab.config:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "labInfo": {
4 | "description": "06-11-2015 Projector",
5 | "version": "1.0",
6 | "author": "",
7 | "email": "",
8 | "web": "",
9 | "toggle": "enable"
10 | },
11 | "netkit": [
12 | {
13 | "name": "ns",
14 | "row": 1,
15 | "type": "ns",
16 | "interfaces": {
17 | "counter": 1,
18 | "if": [
19 | {
20 | "eth": {
21 | "number": 0,
22 | "domain": "A"
23 | },
24 | "ip": "10.0.0.3/24",
25 | "$$hashKey": "object:10"
26 | }
27 | ],
28 | "free": ""
29 | },
30 | "gateways": {
31 | "counter": 1,
32 | "gw": [
33 | {
34 | "route": "",
35 | "if": "0",
36 | "$$hashKey": "object:12",
37 | "gw": "10.0.0.1"
38 | }
39 | ]
40 | },
41 | "pc": {
42 | "dns": ""
43 | },
44 | "ws": {
45 | "userdir": false
46 | },
47 | "ns": {
48 | "recursion": true,
49 | "authority": false
50 | },
51 | "routing": {
52 | "rip": {
53 | "en": false,
54 | "connected": false,
55 | "ospf": false,
56 | "bgp": false,
57 | "network": [
58 | ""
59 | ],
60 | "route": [
61 | ""
62 | ],
63 | "free": ""
64 | },
65 | "ospf": {
66 | "en": false,
67 | "connected": false,
68 | "rip": false,
69 | "bgp": false,
70 | "if": [],
71 | "network": [
72 | ""
73 | ],
74 | "area": [
75 | "0.0.0.0"
76 | ],
77 | "stub": [
78 | false
79 | ],
80 | "free": ""
81 | },
82 | "bgp": {
83 | "en": false,
84 | "as": "",
85 | "network": [
86 | ""
87 | ],
88 | "remote": [
89 | {
90 | "neighbor": "",
91 | "as": "",
92 | "description": ""
93 | }
94 | ],
95 | "free": ""
96 | }
97 | },
98 | "$$hashKey": "object:18"
99 | },
100 | {
101 | "name": "userpc",
102 | "row": 2,
103 | "type": "terminal",
104 | "interfaces": {
105 | "counter": 1,
106 | "if": [
107 | {
108 | "eth": {
109 | "number": 0,
110 | "domain": "A"
111 | },
112 | "ip": "10.0.0.2/24",
113 | "$$hashKey": "object:55"
114 | }
115 | ],
116 | "free": ""
117 | },
118 | "gateways": {
119 | "counter": 1,
120 | "gw": [
121 | {
122 | "route": "",
123 | "if": "0",
124 | "$$hashKey": "object:57",
125 | "gw": "10.0.0.1"
126 | }
127 | ]
128 | },
129 | "pc": {
130 | "dns": ""
131 | },
132 | "ws": {
133 | "userdir": false
134 | },
135 | "ns": {
136 | "recursion": true,
137 | "authority": true
138 | },
139 | "routing": {
140 | "rip": {
141 | "en": false,
142 | "connected": false,
143 | "ospf": false,
144 | "bgp": false,
145 | "network": [
146 | ""
147 | ],
148 | "route": [
149 | ""
150 | ],
151 | "free": ""
152 | },
153 | "ospf": {
154 | "en": false,
155 | "connected": false,
156 | "rip": false,
157 | "bgp": false,
158 | "if": [],
159 | "network": [
160 | ""
161 | ],
162 | "area": [
163 | "0.0.0.0"
164 | ],
165 | "stub": [
166 | false
167 | ],
168 | "free": ""
169 | },
170 | "bgp": {
171 | "en": false,
172 | "as": "",
173 | "network": [
174 | ""
175 | ],
176 | "remote": [
177 | {
178 | "neighbor": "",
179 | "as": "",
180 | "description": ""
181 | }
182 | ],
183 | "free": ""
184 | }
185 | },
186 | "$$hashKey": "object:52"
187 | },
188 | {
189 | "name": "r1",
190 | "row": 3,
191 | "type": "router",
192 | "interfaces": {
193 | "counter": 2,
194 | "if": [
195 | {
196 | "eth": {
197 | "number": 0,
198 | "domain": "B"
199 | },
200 | "ip": "15.0.0.1/24",
201 | "$$hashKey": "object:101"
202 | },
203 | {
204 | "eth": {
205 | "number": 1,
206 | "domain": "A"
207 | },
208 | "$$hashKey": "object:367",
209 | "ip": "10.0.0.1/24"
210 | }
211 | ],
212 | "free": ""
213 | },
214 | "gateways": {
215 | "counter": 1,
216 | "gw": [
217 | {
218 | "route": "20.0.0.0/24",
219 | "if": "0",
220 | "$$hashKey": "object:103",
221 | "gw": "15.0.0.2"
222 | }
223 | ]
224 | },
225 | "pc": {
226 | "dns": ""
227 | },
228 | "ws": {
229 | "userdir": false
230 | },
231 | "ns": {
232 | "recursion": true,
233 | "authority": true
234 | },
235 | "routing": {
236 | "rip": {
237 | "en": false,
238 | "connected": false,
239 | "ospf": false,
240 | "bgp": false,
241 | "network": [
242 | ""
243 | ],
244 | "route": [
245 | ""
246 | ],
247 | "free": ""
248 | },
249 | "ospf": {
250 | "en": false,
251 | "connected": false,
252 | "rip": false,
253 | "bgp": false,
254 | "if": [],
255 | "network": [
256 | ""
257 | ],
258 | "area": [
259 | "0.0.0.0"
260 | ],
261 | "stub": [
262 | false
263 | ],
264 | "free": ""
265 | },
266 | "bgp": {
267 | "en": false,
268 | "as": "",
269 | "network": [
270 | ""
271 | ],
272 | "remote": [
273 | {
274 | "neighbor": "",
275 | "as": "",
276 | "description": ""
277 | }
278 | ],
279 | "free": ""
280 | }
281 | },
282 | "$$hashKey": "object:97"
283 | },
284 | {
285 | "name": "rootns",
286 | "row": 4,
287 | "type": "ns",
288 | "interfaces": {
289 | "counter": 1,
290 | "if": [
291 | {
292 | "eth": {
293 | "number": 0,
294 | "domain": "B"
295 | },
296 | "ip": "15.0.0.10/24",
297 | "$$hashKey": "object:149",
298 | "name": "ROOT-SERVER."
299 | }
300 | ],
301 | "free": ""
302 | },
303 | "gateways": {
304 | "counter": 2,
305 | "gw": [
306 | {
307 | "route": "20.0.0.0/24",
308 | "if": "0",
309 | "$$hashKey": "object:151",
310 | "gw": "15.0.0.2"
311 | },
312 | {
313 | "route": "10.0.0.0/24",
314 | "if": 0,
315 | "$$hashKey": "object:386",
316 | "gw": "15.0.0.1"
317 | }
318 | ]
319 | },
320 | "pc": {
321 | "dns": ""
322 | },
323 | "ws": {
324 | "userdir": false
325 | },
326 | "ns": {
327 | "recursion": true,
328 | "authority": true,
329 | "zone": "."
330 | },
331 | "routing": {
332 | "rip": {
333 | "en": false,
334 | "connected": false,
335 | "ospf": false,
336 | "bgp": false,
337 | "network": [
338 | ""
339 | ],
340 | "route": [
341 | ""
342 | ],
343 | "free": ""
344 | },
345 | "ospf": {
346 | "en": false,
347 | "connected": false,
348 | "rip": false,
349 | "bgp": false,
350 | "if": [],
351 | "network": [
352 | ""
353 | ],
354 | "area": [
355 | "0.0.0.0"
356 | ],
357 | "stub": [
358 | false
359 | ],
360 | "free": ""
361 | },
362 | "bgp": {
363 | "en": false,
364 | "as": "",
365 | "network": [
366 | ""
367 | ],
368 | "remote": [
369 | {
370 | "neighbor": "",
371 | "as": "",
372 | "description": ""
373 | }
374 | ],
375 | "free": ""
376 | }
377 | },
378 | "$$hashKey": "object:144"
379 | },
380 | {
381 | "name": "srvns",
382 | "row": 5,
383 | "type": "ns",
384 | "interfaces": {
385 | "counter": 1,
386 | "if": [
387 | {
388 | "eth": {
389 | "number": 0,
390 | "domain": "B"
391 | },
392 | "ip": "15.0.0.11/24",
393 | "$$hashKey": "object:199"
394 | }
395 | ],
396 | "free": ""
397 | },
398 | "gateways": {
399 | "counter": 1,
400 | "gw": [
401 | {
402 | "route": "",
403 | "if": 0,
404 | "$$hashKey": "object:201"
405 | }
406 | ]
407 | },
408 | "pc": {
409 | "dns": ""
410 | },
411 | "ws": {
412 | "userdir": false
413 | },
414 | "ns": {
415 | "recursion": true,
416 | "authority": true,
417 | "zone": ".srv."
418 | },
419 | "routing": {
420 | "rip": {
421 | "en": false,
422 | "connected": false,
423 | "ospf": false,
424 | "bgp": false,
425 | "network": [
426 | ""
427 | ],
428 | "route": [
429 | ""
430 | ],
431 | "free": ""
432 | },
433 | "ospf": {
434 | "en": false,
435 | "connected": false,
436 | "rip": false,
437 | "bgp": false,
438 | "if": [],
439 | "network": [
440 | ""
441 | ],
442 | "area": [
443 | "0.0.0.0"
444 | ],
445 | "stub": [
446 | false
447 | ],
448 | "free": ""
449 | },
450 | "bgp": {
451 | "en": false,
452 | "as": "",
453 | "network": [
454 | ""
455 | ],
456 | "remote": [
457 | {
458 | "neighbor": "",
459 | "as": "",
460 | "description": ""
461 | }
462 | ],
463 | "free": ""
464 | }
465 | },
466 | "$$hashKey": "object:193"
467 | },
468 | {
469 | "name": "r2",
470 | "row": 6,
471 | "type": "router",
472 | "interfaces": {
473 | "counter": 2,
474 | "if": [
475 | {
476 | "eth": {
477 | "number": 0,
478 | "domain": "C"
479 | },
480 | "ip": "20.0.0.1/24",
481 | "$$hashKey": "object:251"
482 | },
483 | {
484 | "eth": {
485 | "number": 1,
486 | "domain": "B"
487 | },
488 | "$$hashKey": "object:424",
489 | "ip": "15.0.0.2/24"
490 | }
491 | ],
492 | "free": ""
493 | },
494 | "gateways": {
495 | "counter": 1,
496 | "gw": [
497 | {
498 | "route": "10.0.0.0/24",
499 | "if": "1",
500 | "$$hashKey": "object:253",
501 | "gw": "15.0.0.1"
502 | }
503 | ]
504 | },
505 | "pc": {
506 | "dns": ""
507 | },
508 | "ws": {
509 | "userdir": false
510 | },
511 | "ns": {
512 | "recursion": true,
513 | "authority": true
514 | },
515 | "routing": {
516 | "rip": {
517 | "en": false,
518 | "connected": false,
519 | "ospf": false,
520 | "bgp": false,
521 | "network": [
522 | ""
523 | ],
524 | "route": [
525 | ""
526 | ],
527 | "free": ""
528 | },
529 | "ospf": {
530 | "en": false,
531 | "connected": false,
532 | "rip": false,
533 | "bgp": false,
534 | "if": [],
535 | "network": [
536 | ""
537 | ],
538 | "area": [
539 | "0.0.0.0"
540 | ],
541 | "stub": [
542 | false
543 | ],
544 | "free": ""
545 | },
546 | "bgp": {
547 | "en": false,
548 | "as": "",
549 | "network": [
550 | ""
551 | ],
552 | "remote": [
553 | {
554 | "neighbor": "",
555 | "as": "",
556 | "description": ""
557 | }
558 | ],
559 | "free": ""
560 | }
561 | },
562 | "$$hashKey": "object:244"
563 | },
564 | {
565 | "name": "websrv",
566 | "row": 7,
567 | "type": "ws",
568 | "interfaces": {
569 | "counter": 1,
570 | "if": [
571 | {
572 | "eth": {
573 | "number": 0,
574 | "domain": "C"
575 | },
576 | "ip": "20.0.0.2/24",
577 | "$$hashKey": "object:305",
578 | "name": "web.srv."
579 | }
580 | ],
581 | "free": ""
582 | },
583 | "gateways": {
584 | "counter": 1,
585 | "gw": [
586 | {
587 | "route": "",
588 | "if": "0",
589 | "$$hashKey": "object:307",
590 | "gw": "20.0.0.1"
591 | }
592 | ]
593 | },
594 | "pc": {
595 | "dns": ""
596 | },
597 | "ws": {
598 | "userdir": true
599 | },
600 | "ns": {
601 | "recursion": true,
602 | "authority": true
603 | },
604 | "routing": {
605 | "rip": {
606 | "en": false,
607 | "connected": false,
608 | "ospf": false,
609 | "bgp": false,
610 | "network": [
611 | ""
612 | ],
613 | "route": [
614 | ""
615 | ],
616 | "free": ""
617 | },
618 | "ospf": {
619 | "en": false,
620 | "connected": false,
621 | "rip": false,
622 | "bgp": false,
623 | "if": [],
624 | "network": [
625 | ""
626 | ],
627 | "area": [
628 | "0.0.0.0"
629 | ],
630 | "stub": [
631 | false
632 | ],
633 | "free": ""
634 | },
635 | "bgp": {
636 | "en": false,
637 | "as": "",
638 | "network": [
639 | ""
640 | ],
641 | "remote": [
642 | {
643 | "neighbor": "",
644 | "as": "",
645 | "description": ""
646 | }
647 | ],
648 | "free": ""
649 | }
650 | },
651 | "$$hashKey": "object:297"
652 | }
653 | ]
654 | }
655 | ]
--------------------------------------------------------------------------------
/src/sdn-manager/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Netkit SDN Manager
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | Connect to:
36 |
37 |
40 |
41 | Connect
42 |
43 |
44 |
45 | Please insert the name of the Docker node hosting the Ryu controller
46 |
47 |
48 |
49 |
50 |
51 |
52 |
54 | Create new label
55 |
56 | Enable nodes repositioning
57 |
58 | Disable nodes repositioning
59 |
60 | Release fixed nodes
61 | ...
62 | HOW TO
63 | RELOAD
64 |
65 |
66 |
67 | Submit all
68 | Update statistics
69 | Show rules
70 | Controller
71 |
72 |
73 |
74 |
75 |
76 |
Please increase the size of the window to fit its contents!
77 | click to close
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
92 |
95 | Add
96 | Ignore
97 |
98 |
99 |
101 | No label defined yet
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | Confirm
112 | Discard
113 |
114 |
115 |
116 |
117 |
118 |
119 |
X
120 |
{{ device }}
121 |
122 |
123 |
125 | Rules
126 |
127 |
130 | Statistics
131 |
132 |
134 | Inspect
135 |
136 |
139 | Update statistics
140 |
141 |
142 |
143 |
144 |
145 |
Create new rule
146 |
Packet rules
147 |
148 |
149 | #
150 | match
151 | action
152 | table id
153 | priority
154 | idle timeout
155 | hard timeout
156 | stats
157 | status
158 |
159 |
160 |
170 | {{ (index + 1) }}
171 |
172 |
177 |
178 |
179 |
180 |
185 |
186 |
187 | {{ rule.table }}
188 | {{ rule.priority }}
189 | {{ rule.idleTimeout }}
190 | {{ rule.hardTimeout }}
191 | {{ rule.stats }}
192 |
193 | {{ rule.submitted ? 'submitted' : 'not-submitted' }}
194 |
196 | submit
197 |
198 |
200 | remove
201 |
202 |
204 | update
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
Label rules
213 |
214 |
215 | #
216 | match
217 | action
218 | table id
219 | priority
220 | idle timeout
221 | hard timeout
222 | stats
223 | status
224 |
225 |
226 |
237 | {{ (packetRules.length + index + 1) }}
238 |
239 |
244 |
245 |
246 |
247 |
252 |
253 |
254 | {{ rule.table }}
255 | {{ rule.priority }}
256 | {{ rule.idleTimeout }}
257 | {{ rule.hardTimeout }}
258 | {{ rule.stats }}
259 |
260 | {{ rule.submitted ? 'submitted' : 'not-submitted' }}
261 |
263 | submit
264 |
265 |
267 | remove
268 |
269 |
271 | update
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 | Update graph
282 |
283 |
284 |
285 |
286 |
287 | Nessuna regola installata sullo switch {{ device }}
288 |
289 |
290 |
294 | Get overall statistics
295 |
296 |
300 | Get ports statistics
301 |
302 |
306 | Get ports descriptions
307 |
308 |
312 | Get tables statistics
313 |
314 |
318 | Get flow-entries
319 |
320 |
323 |
324 |
325 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
X
340 |
341 |
Instructions:
342 |
343 |
344 | This tool allows you to configure the network flow of a software defined network (SDN) built on Kathará. In this page you
345 | can se the actual topology of the network: blue circles are the switches, black and white ones are the network domains. The controller
346 | node is hidden, but it exists and it is connected to all the switches through a dedicated network!
347 |
348 |
349 |
350 | MPLS labels represent a flow inside the network. Each label is like a tunnel in which packets moves accordingly to certain rules.
351 | Colors and paths are arbitrary. After you create a label, you can click on edit and drag a path on the graph. The path must start
352 | From a domain (either white or black) and must end on a domain. A white domain are edge domanins: they are shared with an external network.
353 | For this reason, when a white circle is included in the flow, the program will automatically add a rule to
354 | tag/untag the packets with the MPLS label. The default match for tagging packets is the port from where the packet is received.
355 |
356 |
357 |
358 | You can also create a custom rule by clicking on a switch and then on the 'create new rule' button. This will prompt a window where
359 | you can specify which packet should match the rule and which action will be applied on it. You can edit or remove a
360 | rule created before by opening the switch details and clicking on the row of the table which represents that rule:
361 | the modal will popup again.
362 |
363 |
364 |
365 | Remember that rules are not automatically submitted to the controller. This allows you check twice they are correct berfore
366 | installing them. The status of a rule is represented by its color in the switch details.
367 |
368 |
369 |
370 |
Controller
371 |
372 |
Input
373 |
374 |
375 | GET
376 | POST
377 |
378 |
383 |
384 |
391 |
392 |
393 |
output
394 |
399 |
400 |
history
401 |
402 |
403 |
404 |
405 | {{ command.method }}
406 | {{ command.path }}
407 | Repeat
408 |
409 |
410 |
411 |
412 |
413 |
Rules
414 |
415 |
417 | Simulated
418 |
419 |
421 | Submitted
422 |
423 |
425 |
426 |
427 |
428 | Export JSON
429 |
430 |
431 | Import
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
451 |
452 |
453 |
454 |
Match
455 |
456 |
459 | Add
460 |
461 |
464 | Remove
465 |
466 |
467 |
468 |
469 |
473 | Select...
474 | Any
475 | MPLS label
476 | Source port
477 | Protocol ethertype
478 | MAC src
479 | MAC dst
480 | IPv4 src
481 | IPv4 dst
482 | TCP sport
483 | TCP dport
484 |
485 |
486 |
489 |
490 |
491 |
492 |
493 |
494 |
Action
495 |
496 |
499 | Add
500 |
501 |
504 | Remove
505 |
506 |
507 |
508 |
509 |
513 | Select...
514 | Drop
515 | Set MPLS label
516 | Pop MPLS label
517 | Forward to port
518 | Send to controller
519 | Send to table
520 | Set field
521 |
522 |
523 |
526 |
527 |
528 |
529 |
530 |
531 |
Table ID
532 |
533 |
534 |
535 |
536 |
Priority
537 |
538 |
539 |
540 |
541 |
Idle timeout
542 |
543 |
544 |
545 |
546 |
Hard timeout
547 |
548 |
549 |
550 |
551 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
--------------------------------------------------------------------------------