├── .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 | ![Screenshot (graph)](https://raw.githubusercontent.com/Kidel/Netkit-Lab-Generator/master/images/screencapture-201801143.png) 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 | "" + 146 | 147 | "" + 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 | /**/ "" + 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 | "" + 135 | "" + 137 | "
" + 138 | 139 | "" + 140 | "" + 141 | "" + 142 | "" + 143 | "" + 144 | "" + 145 | "" + 146 | "" + 147 | "" + 148 | "" + 152 | "" + 158 | "" + 163 | "" + 165 | "" + 166 | "" + 167 | "" + 168 | "
devicematchaction-
" + 150 | "{{ rule.device }}" + 151 | "" + 153 | "{{ rule.matches.find(match => match.name == \"source port\").name }} {{ rule.matches.find(match => match.name == \"source port\").value }} " + 154 | " 2\">" + 155 | "+ " + 156 | "" + 157 | "forward to port {{ rule.actions.find(action => action.name == 'forward to port').value }} " + 159 | " 1\">" + 160 | "+ " + 161 | "" + 162 | "" + 164 | "-
No rules defined yet
" + 169 | "
" + 170 | "" + 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 | 43 |
44 |

45 | Please insert the name of the Docker node hosting the Ryu controller 46 |

47 |
48 |
49 | 50 | 51 |
52 | 56 | 57 | 60 | 61 | 62 | 63 | 64 |
65 | 66 |
67 | 68 | 69 | 70 | 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 | 96 | 97 |
98 | 99 |

101 | No label defined yet 102 |

103 |
104 | 105 | 106 | 107 | 108 |
109 | 110 |
111 | 112 | 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 | 141 |
142 |
143 |
144 |
145 | 146 |

Packet rules

147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 170 | 171 | 179 | 187 | 188 | 189 | 190 | 191 | 192 | 207 | 208 | 209 |
#matchactiontable idpriorityidle timeouthard timeoutstatsstatus
{{ (index + 1) }} 172 | 177 | 178 | 180 | 185 | 186 | {{ rule.table }}{{ rule.priority }}{{ rule.idleTimeout }}{{ rule.hardTimeout }}{{ rule.stats }} 193 | {{ rule.submitted ? 'submitted' : 'not-submitted' }} 194 | 198 | 202 | 206 |
210 |
211 |
212 |

Label rules

213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 237 | 238 | 246 | 254 | 255 | 256 | 257 | 258 | 259 | 274 | 275 | 276 |
#matchactiontable idpriorityidle timeouthard timeoutstatsstatus
{{ (packetRules.length + index + 1) }} 239 | 244 | 245 | 247 | 252 | 253 | {{ rule.table }}{{ rule.priority }}{{ rule.idleTimeout }}{{ rule.hardTimeout }}{{ rule.stats }} 260 | {{ rule.submitted ? 'submitted' : 'not-submitted' }} 261 | 265 | 269 | 273 |
277 |
278 |
279 |
280 | 283 | 284 | 285 |
286 |

287 | Nessuna regola installata sullo switch {{ device }} 288 |

289 |
290 | 296 | 302 | 308 | 314 | 320 | 323 |
324 | 325 |
326 |

327 | idle timeout 328 | hard timeout 329 | table 330 | statistics 331 |

332 |
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 | 378 | 383 |
384 | 391 | 392 |
393 |

output

394 | 399 |
400 |

history

401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 |
{{ command.method }}{{ command.path }}Repeat
411 |
412 |
413 |

Rules

414 |
415 | 417 | Simulated 418 | 419 | 421 | Submitted 422 | 423 | 425 | 426 | 427 | 430 | 433 | 434 |
435 |
436 | 437 | 438 | 439 | 440 | 559 | 560 |
561 |
562 | 563 | 564 | 565 | --------------------------------------------------------------------------------