diff --git a/client/Dockerfile b/client/Dockerfile index 12b40a8ec..33a29b4bc 100644 --- a/client/Dockerfile +++ b/client/Dockerfile @@ -6,8 +6,16 @@ WORKDIR /usr/src/app/frontend RUN npm install RUN npm install -D webpack-cli RUN ./node_modules/.bin/webpack --mode production + +WORKDIR /usr/src/app/common +RUN npm install +RUN npm install ip-num +RUN npm install -D typescript +RUN ./node_modules/.bin/tsc + WORKDIR /usr/src/app/backend RUN npm install +RUN npm install ip-num RUN npm install -D typescript RUN ./node_modules/.bin/tsc ENTRYPOINT ["sh", "/start.sh"] diff --git a/client/backend/package-lock.json b/client/backend/package-lock.json index 6840ae63d..6d63b5d9f 100644 --- a/client/backend/package-lock.json +++ b/client/backend/package-lock.json @@ -2042,6 +2042,11 @@ "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==" }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, "node_modules/ssh2": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.15.0.tgz", @@ -4131,6 +4136,11 @@ "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==" }, + "sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, "ssh2": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.15.0.tgz", diff --git a/client/backend/package.json b/client/backend/package.json index 15dc22637..811afacc0 100644 --- a/client/backend/package.json +++ b/client/backend/package.json @@ -9,6 +9,7 @@ "author": "nat ", "license": "MIT", "dependencies": { + "ip-num": "^1.5.1", "@types/dockerode": "^3.3.23", "@types/express": "^4.17.21", "@types/express-ws": "^3.0.4", diff --git a/client/backend/src/api/v1/main.ts b/client/backend/src/api/v1/main.ts index f13693812..9c3463c7f 100644 --- a/client/backend/src/api/v1/main.ts +++ b/client/backend/src/api/v1/main.ts @@ -5,7 +5,9 @@ import { SeedContainerInfo, Emulator, SeedNetInfo } from '../../utils/seedemu-me import { Sniffer } from '../../utils/sniffer'; import WebSocket from 'ws'; import { Controller } from '../../utils/controller'; - +import {SCIONAddress} from '../../../../common/scion'; +import { IPv4 ,IPv6} from "ip-num/IPNumber"; +import * as types from '../../../../common/types'; const router = express.Router(); const docker = new dockerode(); const socketHandler = new SocketHandler(docker); @@ -102,7 +104,149 @@ router.get('/container/:id', async function(req, res, next) { next(); }); -router.get('/container/:id/net', async function(req, res, next) { +/* +Example for scion/S03-bandwidth-test + curl -H "Accept: application/json" + http://localhost:8080/api/v1/traceroute/abafb9f7c4b1/scion/?path=1-150%232%201-151%231%2C2%201-152%231\&remote=1-152%2C10.152.0.30 +Response Body: +{"ok":true, + "result": + {"segments": + [ {"from": {"id": abafb9f7c4b1}, "to": {"isd":1,"asn":150,"ip":"10.0.0.1"} }, + {"from": {"isd":1,"asn":150,"ip":"10.0.0.1"},"to": {"isd":1,"asn":151,"ip":"10.0.0.2"} }, + {"from": {"isd":1,"asn":151,"ip":"10.0.0.2"},"to": {"isd":1,"asn":151,"ip":"10.0.0.2"} }, + {"from": {"isd":1,"asn":151,"ip":"10.0.0.2"},"to": {"isd":1,"asn":152,"ip":"10.0.0.3"} }, + {"from": {"isd":1,"asn":152,"ip":"10.0.0.3"},"to": {"isd":1,"asn":152,"ip":"10.152.0.30"} } + ] + } +} + ?remote=1-152%2C10.152.0.30 -> 1-152,10.152.0.30 // who to SCMP-ping + ?sequence ?path=1-150%232%201-151%231%2C2%201-152%231 -> [1-150#2 1-151#1,2 1-152#1 ] // on what SCION-path +*/ +router.get('/traceroute/:id/scion/', async function (req, res, next) { + let id = req.params.id; + let remote : any; + let scion_remote: SCIONAddress | undefined; + let path: any; + + if("path" in req.query ) + { + path = req.query.path; + } + if("remote" in req.query ) + { + remote = req.query.remote; + } + // check if remote is a valid SCION address, and return error if not + try { + scion_remote = SCIONAddress.fromStr(remote); + } catch (err) { + res.json({ + ok: false, + result: `invalid traceroute parameter: ${err}` + }); + next(); + return; + } + + var candidates = (await docker.listContainers()) + .filter(c => c.Id.startsWith(id)); + + if (candidates.length != 1) { + res.json({ + ok: false, + result: `no match or multiple match for container ID ${id}.` + }); + next(); + return; + } + + let node = candidates[0]; + + + try { + let p: types.Path = await controller.getSCIONTraceroute(node.Id, remote ,path ); + + //TODO: add source ip here if given + // parse ASN from first hop in path + p.segments.unshift({from: {id: id }, to: p.segments[0].from } ); + + p.segments.push( {from: p.segments[p.segments.length-1].to , to: {ip: scion_remote.getIP().toString(), isd: scion_remote.getIA()[0], asn: scion_remote.getIA()[1] } } ); + res.json({ + ok: true, + result: p + }); + + next(); + + + } catch (err) { + res.json({ + ok: false, + result: `Error: ${err}` + }); + next(); + return; + } + + +}); + +/* +Example from scion/S03-bandwidth-test: Host 10.150.0.30 (container 'abafb9f7c4b1' ) resolving the hops to its ControlService 10.150.0.71 +curl -H "Accept: application/json" http://localhost:8080/api/v1/traceroute/abafb9f7c4b1/v4/10.150.0.71?source=10.150.0.30 + +Response Body: +{"ok":true, +"result":[ {"ip":"10.150.0.30"}, + {"ip":"10.150.0.71"} + ] +} +// -> single hop, since they both are on the same net +*/ +router.get('/traceroute/:id/v4/:remote', async function (req, res, next) { + let id = req.params.id; + let remote = req.params.remote; + let source: any; + // check if remote is a valid IPv4 address, and return error if not + try { + let ip4: IPv4 = IPv4.fromDecimalDottedString(remote); + } catch (err) { + res.json({ + ok: false, + result: `invalid traceroute parameter: ${err}` + }); + next(); + return; + } + if( "source" in req.query) + { + source = req.query.source; + } + + var candidates = (await docker.listContainers()) + .filter(c => c.Id.startsWith(id)); + + if (candidates.length != 1) { + res.json({ + ok: false, + result: `no match or multiple match for container ID ${id}.` + }); + next(); + return; + } + + let node = candidates[0]; + + res.json({ + ok: true, + result: await controller.getTraceroute(node.Id, remote,source) + }); + + next(); +}); + +router.get('/container/:id/net', async function (req, res, next) { let id = req.params.id; var candidates = (await docker.listContainers()) diff --git a/client/backend/src/utils/controller.ts b/client/backend/src/utils/controller.ts index 7bb3f5967..f493f8969 100644 --- a/client/backend/src/utils/controller.ts +++ b/client/backend/src/utils/controller.ts @@ -2,6 +2,7 @@ import dockerode from 'dockerode'; import { LogProducer , ILogObj } from '../interfaces/log-producer'; import { Logger } from 'tslog'; import { Session, SessionManager } from './session-manager'; +import * as types from '../../../common/types'; interface ExecutionResult { id: number, @@ -23,6 +24,12 @@ export interface BgpPeer { bgpState: string; } +// only used to parse the output of seedemu_worker::mytraceroute function +interface TraceRouteResult { + ttl: number, + hop: string // an ip address +} + /** * controller class. * @@ -141,6 +148,8 @@ export class Controller implements LogProducer { * * @param node id of node to run on. * @param command command. + * TODO: maybe make arguments for the command explicit here. + * currently they are a ';' separated field in 'command' * @returns execution result. */ private async _run(node: string, command: string): Promise { @@ -201,6 +210,94 @@ export class Controller implements LogProducer { await this._run(node, connected ? 'net_up' : 'net_down'); } + /** + * resolve a coarse SCION interdomain path from 'node' to 'remote' + * + * @param source is the --local IP address to listen on + * @param sequence SCION hop predicate , what path to use i.e. '1-150#2 1-151#1,2 1-152#1' + * @param remote SCION address, who to SCMP-ping i.e. 1-152,10.152.0.30 + * + */ + async getSCIONTraceroute(node: string, remote: string, sequence: string, source?: string): Promise { + let path: types.Path; + + let command: string = `sciontraceroute; --sequence "${sequence}" ${remote} `; + + /*if (typeof source !== 'undefined') { + // check that it is actually a valid ip address here + command.concat(` --local ${source}`); + } */ + + let result = await this._run(node, command); + + if (result.return_value == 0) { + let hops: types.Segment[] = JSON.parse(result.output); + + + path = { segments: hops }; + + // TODO: now the frontent datasource can loop over each segment and refine it with the intra-domain hops from getTraceroute() + // with its address2nodeId() it can translate the addresses of Points to nodeIds which it can then use as a parameter to getTraceroute() + // The getTraceroute() requests for each segment can run in parallel + + return path; + } else { + throw result.output; + } + } + + /** + * @brief do Intra domain IP traceroute from source A to remote B + * (for SCION only within the same AS) + * @param node node id + * @param remote IP address that the node shall invoke traceroute with + * @param interf interface or net-name on which the ICMP echo requests shall be send + * @param source source-address that shall be used by the node + * @returns a list of individual hops from the node to the remote + * TODO: maybe add optional ISD, AS parameters, if they are known + */ + async getTraceroute(node: string, remote: string, source?: string, interf?: string ) : Promise { + this._logger.debug(`to traceroute to remote ${remote} on node ${node}`); + + let points: types.Point[] = []; + + let command: string = `traceroute;${remote} `; + if (typeof source !== 'undefined') { + command.concat(` --source ${source}`); + // add the node itself with TTL of 0 + points.push({ip: source, id: node }); + } else { + // TODO: find out the ip address through the node-id + // add the node itself with TTL of 0 + points.push({ id: node }); + } + + if (typeof interf !== 'undefined') { + command.concat(` --interface ${interf}`); + } + + let result = await this._run(node,command); + let hops: TraceRouteResult[] = JSON.parse(result.output); + + // check that ttl's are continous ( no omissions due to timeout i.e.) + + // sort ascending by ttl (should be Noop but just to be sure) + hops.sort((a, b) => { return a.ttl - b.ttl ; }); + + let hop2point = function( hop: TraceRouteResult ){ return {ip: hop.hop };}; + + points.concat( hops.map(hop2point) ); + + // check if last hop is the remote, as expected. If not add it. + if( points[points.length -1 ].ip !== remote ) + { + this._logger.debug(`traceroute of node ${node} to remote ${remote} didnt succeed`); + points.push({ip: remote}) + } + + return points; + } + /** * get the network connection state of a node. * diff --git a/client/common/package-lock.json b/client/common/package-lock.json new file mode 100644 index 000000000..b5df56f48 --- /dev/null +++ b/client/common/package-lock.json @@ -0,0 +1,69 @@ +{ + "name": "common", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "common", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "ip-num": "^1.5.1" + }, + "devDependencies": { + "@types/jsbn": "^1.2.33", + "@types/node": "^20.11.24" + } + }, + "node_modules/@types/jsbn": { + "version": "1.2.33", + "resolved": "https://registry.npmjs.org/@types/jsbn/-/jsbn-1.2.33.tgz", + "integrity": "sha512-ZlLkHfu8xqqVFSbCe1FSPtAMUs7LKxk7TPskMb+sI5IbuzqyVqIEt9SVaQfFD2vrFcQunqKAmEBOuBEkoNLw4g==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.11.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz", + "integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-num": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ip-num/-/ip-num-1.5.1.tgz", + "integrity": "sha512-QziFxgxq3mjIf5CuwlzXFYscHxgLqdEdJKRo2UJ5GurL5zrSRMzT/O+nK0ABimoFH8MWF8YwIiwECYsHc1LpUQ==" + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + } + } +} diff --git a/client/common/package.json b/client/common/package.json new file mode 100644 index 000000000..791da2fc1 --- /dev/null +++ b/client/common/package.json @@ -0,0 +1,18 @@ +{ + "name": "common", + "version": "0.0.1", + "description": "stuff shared by front and backend", + "main": "scion.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "nat ", + "license": "MIT", + "dependencies": { + "ip-num": "^1.5.1" + }, + "devDependencies": { + "@types/jsbn": "^1.2.33", + "@types/node": "^20.11.24" + } +} diff --git a/client/common/scion.ts b/client/common/scion.ts new file mode 100644 index 000000000..0d6d7f22a --- /dev/null +++ b/client/common/scion.ts @@ -0,0 +1,145 @@ +import { IPv4, IPv6 } from 'ip-num'; +import {Validator} from 'ip-num/Validator'; + +function tokenize(string: string, pattern: string): string[] { + const tokenized = string.split(new RegExp(pattern)); + return tokenized.filter(token => token !== ''); // Remove empty strings +} + +function paddTo4(x: string | number): string { + return `${x}`.padStart(4, '0'); +} + +// ffaa:1:1067 -> 0xFFAA00011067 -> 281105609592935 +function AsFromDottedHex(string: string): number { + const tokens = tokenize(string, '[:]+'); + const hexStr = '0x' + tokens.map(token => paddTo4(token)).join(''); + return parseInt(hexStr, 16); +} +// output for 281105609592935 -> ffaa:1:1067 +function ASToDottedHex(as_num: number): string { + const result: string[] = []; + const hexStr = as_num.toString(16); + let begin = true; + let encounteredZerosInRow = 0; + for (let pos = 0; pos < hexStr.length; ) { + const s = hexStr[pos]; + if (pos !== 0 && pos % 4 === 0 && !begin) { + result.push(":"); + encounteredZerosInRow = 0; + begin = true; + } + if (begin) { + if (s === "0") { + pos++; + encounteredZerosInRow++; + if (encounteredZerosInRow === 4) { + result.push("0", ":"); + begin = true; + encounteredZerosInRow = 0; + } + continue; + } else { + result.push(s); + encounteredZerosInRow = 0; + begin = false; + pos++; + } + } else { + result.push(s); + pos++; + } + } + return result.join(""); + } +/* +// output for 281105609592935 -> ffaa:0001:1067 +function ASToDottedHexFull(as_num: number): string { + const result: string[] = []; + const hexStr = as_num.toString(16); + for (let pos = 0; pos < hexStr.length; pos++) { + const s = hexStr[pos]; + if (pos !== 0 && pos % 4 === 0) { + result.push(':'); + } + result.push(s); + } + return result.join(''); +}*/ + +/* +representation of SCION internet addresses +i.e. +19-ffaa:1:1067,127.0.0.1:443 +17-fe4:1:1067,[2003:f7:a705:50db:de1:a65f:c51:d089] +19-150,[2003:f7:a705:50db:de1:a65f:c51:d089]:8080 +*/ +export class SCIONAddress { + static fromStr(addr: string): SCIONAddress { + const reg = /^(?:(\d+)-([\d:A-Fa-f]+)),(?:\[([^\]]+)\]|([^\[\]:]+))(?::(\d+))?$/; + const m = addr.match(reg); // group 1 - isd , group 2 - asn , group 3 - host_ip , group 4 - port + let port = 0; + let asnMatch = m?.[2] ?? ''; + let asn = 0; + // ASN is dotted hex + if (asnMatch.includes(':')) { + asn = AsFromDottedHex(asnMatch); + } else { + // ASN is decimal number + asn = parseInt(asnMatch, 10); + } + + if ( (m?.length ?? 0 > 4) && (m?.[5] != null) ) { + port = parseInt(m[5], 10); + } + + const isd = parseInt(m?.[1] ?? '', 10); + const ip = Validator. isValidIPv4String(m?.[4] ?? '')[0] ? new IPv4(m?.[4]) : Validator.isValidIPv6String(m?.[3] ?? '')[0] ? new IPv6(m?.[3]) : undefined; + + return new SCIONAddress(isd, asn, ip, port); + } + + ia: bigint; + buf: string; + port: number; + + constructor(isd: number, asn: number, ip: IPv4 | IPv6, port = 0) { + this.ia = BigInt( BigInt(asn) | (BigInt(isd) << BigInt(48))); + this.buf = ip.toString(); + this.port = port; + } + + static fromIAIP(ia: bigint, ip: IPv4 | IPv6, port = 0): SCIONAddress { + const asn: bigint = ia & BigInt("0x0000FFFFFFFFFFFF"); + const isd: bigint = (ia >> BigInt(48)) & BigInt("0x0000FFFF"); + + return new SCIONAddress(Number(isd), Number(asn), ip, port); + } + + toString(): string { + const [isd, asn] = this.getIA(); + let asnStr = ''; + if (asn <= 65000) { // asn is BGP AS + asnStr = asn.toString(); + } else { + asnStr = ASToDottedHex(asn); + } + const ipStr = Validator.isValidIPv6String(this.buf)[0] ? `[${this.buf}]` : this.buf; + return `${isd}-${asnStr},${ipStr}${this.getPort() !== 0 ? `:${this.getPort()}` : ''}`; + } + + getIA(): [number, number] { + const asn: bigint = this.ia & BigInt("0x0000FFFFFFFFFFFF"); + const isd: bigint = (this.ia >> BigInt(48)) & BigInt("0x0000FFFF"); + + return [Number(isd), Number(asn)]; + } + + getIP(): IPv4 | IPv6 { + return Validator.isValidIPv6String(this.buf)[0] ? new IPv6(this.buf) : new IPv4(this.buf); + } + + getPort(): number { + return this.port; + } +} diff --git a/client/common/tsconfig.json b/client/common/tsconfig.json new file mode 100644 index 000000000..dfd4bd9d9 --- /dev/null +++ b/client/common/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES6", + "noImplicitAny": true, + "removeComments": true, + "preserveConstEnums": true, + "esModuleInterop": true, + "sourceMap": true, + "types": ["node"], + "outDir": "bin/" + }, + "include": ["./**/*"], + "exclude": ["node_modules"] + } \ No newline at end of file diff --git a/client/common/types.ts b/client/common/types.ts new file mode 100644 index 000000000..3496cadc1 --- /dev/null +++ b/client/common/types.ts @@ -0,0 +1,108 @@ +/** + this file contains types, that represent the response messages for traceroute endpoints + */ + + +export interface Point +{ + id?: string; // node id + isd?: number; + asn?: number; + ip?: string; +} + +export interface ProtoEdge{ + from: string; + to: string; +} + +export interface Segment { + id?: number; + from: Point; + to: Point; + hops?: Point[]; + // might be empty/non existent at first + // as these are not contained in scion-traceroute output + + // toEdges(): ProtoEdge[]; +} + +/* +a datatype to represent both SCION and IP paths from a source A to a destination B + +Goal: contain enough information about each hop, + that it can be translated to a list of connected nodeIDs needed by the map to draw an underlay path +*/ +export interface Path +{ + id?: number; + segments: Segment[]; + + // toEdges(): ProtoEdge[]; +} + +/** EXAMPLE: + + Output of root@Host_A$ scion traceroute "[1-152#1 1-151#2,1 1-150#1 ] Host_B": + IFID + Host_A 1-152,10.152.37.71 + 0 1-152,10.0.0.143 1 + 1 1-151,10.0.0.94 2 + 2 1-151,10.0.0.51 1 + 3 1-150,10.0.0.54 1 + Host_B 1-150,10.150.17.71 + +this gives a first coarse path, which has to be 'refined' +by calls to /traceroute/:id/v4/:nexthop to resolve the intra AS ip hops of each segment + +It will become + +path = +{ + segments: [ + { + from: { isd: 1 ,asn: 152, ip: 10.152.37.71 } // Host_A + to: { isd: 1 ,asn: 152, ip: 10.0.0.143 } + hops: [ + {"ttl": 1, "hop": "10.152.37.254"}, // default gateway of Host_A + {..}, + {"ttl": 5, "hop": "10.0.0.143" } + ] + }, + { + from: {isd: 1 ,asn: 152, ip: 10.0.0.143 } + to: {isd: 1 ,asn: 151, ip: 10.0.0.94 } + hops: [ + {..}, + {..} + ] + }, + { + from: {isd: 1, asn: 151, ip: 10.0.0.94 } + to: {isd: 1, asn: 151, ip: 10.0.0.51 } + hops: [ + {..} + ] + }, + { + from: {isd: 1, asn: 151, ip: 10.0.0.51 } + to: {isd: 1, asn: 150, ip: 10.0.0.54 } + hops: [ + {..} + ] + }, + { + from: {isd: 1, asn: 150, ip: 10.0.0.54 } + to: {isd: 1, asn: 150, ip: 10.150.17.71} // Host_B + hops: [ + {..}, + {..} + ] + } + ] +} + +A Non-Scion IP path will have only segments {from: {}, to: {} hops: []} with zero hops +also isd and asn fields need not be filled + + */ diff --git a/client/frontend/src/common/types.ts b/client/frontend/src/common/types.ts index 35c2ff3b5..40748235c 100644 --- a/client/frontend/src/common/types.ts +++ b/client/frontend/src/common/types.ts @@ -44,4 +44,4 @@ export interface BgpPeer { name: string; protocolState: string; bgpState: string; -}; \ No newline at end of file +}; diff --git a/client/start.sh b/client/start.sh index 6a4ab6ebe..9cf690c9b 100644 --- a/client/start.sh +++ b/client/start.sh @@ -1,3 +1,3 @@ #!/bin/sh cd /usr/src/app/backend -while true; do node ./bin/main.js; done +while true; do node ./bin/backend/src/main.js; done diff --git a/seedemu/compiler/Docker.py b/seedemu/compiler/Docker.py index 49bc91c6a..f9c0e5f10 100644 --- a/seedemu/compiler/Docker.py +++ b/seedemu/compiler/Docker.py @@ -43,8 +43,46 @@ [ "$last_pid" != 0 ] && kill $last_pid """ -DockerCompilerFileTemplates['seedemu_worker'] = """\ -#!/bin/bash +DockerCompilerFileTemplates['seedemu_worker'] = r"""#!/bin/bash + +mysciontraceroute() { +# a list of json 'points' +local input_string="$1" +local tokens=($input_string) # Split input_string by space into an array +command="scion traceroute ${tokens[@]}" +input="`eval $command | grep -Eo '([0-9]+)-([0-9:A-Fa-f]+),(\[[^-. \]]+\]|[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3})(:([0-9]+))?' | sed -E 's/([0-9]+)-([0-9:A-Fa-f]+),(\[[^-. \]]+\]|[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3})(:([0-9]+))?/{ "isd": \1, "asn": \2, "ip": "\3" }/g' `" + +# Initialize an empty array +declare -a entries + +# Read the input into the array +readarray -t entries <<< "$input" + +# Calculate the length of the array +length=${#entries[@]} + +# Initialize an empty array to store json 'segments' +pairs=() + +# Loop over the array to create json 'segments' from every pair of adjacent points +for ((i = 0; i < length - 1; i++)); do + pair="{ \"from\": ${entries[i]}, \"to\": ${entries[i+1]} }" + pairs+=("$pair") +done + +# Print the resulting json segment array + +IFS=','; echo "[ ${pairs[*]} ]" +return +} + +# interface (-i) and or source (-s) parameters are included in '$1' if present +mytraceroute() { +data="`traceroute --icmp "$1" | sed -nE 's/^ ([[:digit:]]{1,2})[^\(\)]*\(([[:digit:]]{1,3}.[[:digit:]]{1,3}.[[:digit:]]{1,3}.[[:digit:]]{1,3})\) [0123456789.ms ]*$/{ "ttl": \1, "hop": "\2" }/p' | tr '' ',$' | sed 's/,$//'`" +out="[ ${data} ]" # a JSON array of Hop objects +echo "${out}" +return +} net() { [ "$1" = "status" ] && { @@ -65,9 +103,11 @@ while read -sr line; do { id="`cut -d ';' -f1 <<< "$line"`" cmd="`cut -d ';' -f2 <<< "$line"`" + args="`cut -d ';' -f3 <<< "$line"`" output="no such command." - + [ "$cmd" = "sciontraceroute" ] && output="`mysciontraceroute "$args" 2>&1`" + [ "$cmd" = "traceroute" ] && output="`mytraceroute "$args" 2>&1`" [ "$cmd" = "net_down" ] && output="`net down 2>&1`" [ "$cmd" = "net_up" ] && output="`net up 2>&1`" [ "$cmd" = "net_status" ] && output="`net status 2>&1`" diff --git a/seedemu/layers/ScionRouting.py b/seedemu/layers/ScionRouting.py index debf83618..0d9b13d06 100644 --- a/seedemu/layers/ScionRouting.py +++ b/seedemu/layers/ScionRouting.py @@ -109,7 +109,7 @@ def __install_scion(self, node: Node): "apt-get update && apt-get install -y" " scion-border-router scion-control-service scion-daemon scion-dispatcher scion-tools" " scion-apps-bwtester") - node.addSoftware("apt-transport-https") + node.addSoftware("apt-transport-https traceroute") node.addSoftware("ca-certificates") def __append_scion_command(self, node: Node):