Skip to content

Commit

Permalink
traceroute enpoints for overlay path drawing functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
amdfxlucas committed Mar 7, 2024
1 parent 0b954d8 commit 2e2a321
Show file tree
Hide file tree
Showing 14 changed files with 663 additions and 8 deletions.
8 changes: 8 additions & 0 deletions client/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
10 changes: 10 additions & 0 deletions client/backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"author": "nat <[email protected]>",
"license": "MIT",
"dependencies": {
"ip-num": "^1.5.1",
"@types/dockerode": "^3.3.23",
"@types/express": "^4.17.21",
"@types/express-ws": "^3.0.4",
Expand Down
148 changes: 146 additions & 2 deletions client/backend/src/api/v1/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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())
Expand Down
97 changes: 97 additions & 0 deletions client/backend/src/utils/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
*
Expand Down Expand Up @@ -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<ExecutionResult> {
Expand Down Expand Up @@ -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<types.Path> {
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<types.Point[]> {
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.
*
Expand Down
Loading

0 comments on commit 2e2a321

Please sign in to comment.