Skip to content

Commit 1f3a812

Browse files
committed
DevService
- changes to RealWorldRouting -DevelopmentService for remote development with VSCode on the simulation nodes
1 parent 3aac138 commit 1f3a812

File tree

10 files changed

+129
-90
lines changed

10 files changed

+129
-90
lines changed
+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Networking Application Development in SEED
2+
3+
4+
The DevelopmentService prepares a node for [Remote Development with VSCode](https://code.visualstudio.com/docs/remote/remote-overview).
5+
It installs the VSCode Remote Server on the Node during the docker image build, which allows to ssh into the running container later i.e. to debug software.
6+
7+
As of now the primary anticipated use of the seed-emulator has been the deployment of fully developed applications into an emulated szenario i.e. to observe its behaviour.
8+
9+
The DevService adapts the emulators primary paradigm of deploying fully develped applications into an emulated szenario,
10+
to meet the need of developing i.e. P2P software to maturity in a distributed environment.
11+
12+
The DevService allows for each individual node that it is installed on, to specify one or more Git repositories that shall be checked out
13+
(along with the desired filesystem path and branch) as well as the VSCode Extensions required for the projects software stack
14+
(i.e. golang.Go for the SCION-proto implementation which is written in Go)
15+
The DevService takes care to install the entirety of build and analysis tools that are needed for a given programming language at docker image build time (for Go this being i.e. the compiler, language server gopls, debugger delve) so that no annoying time delay arises once the emulator is running and you want to attach to a container.
16+
Any specified Git repositories are checked out on its own separate docker volume, for the changes to persist between runs of the simulator in case one forgets to push.
17+
18+
Software development requires a 'real Internet' connection of the container be it to git push/pull or fetch project dependencies for a build (i.e. go get, cargo build etc. )
19+
This is achieved by promoting the nodes default gateway ( router node ) in the simulation into a 'RealWorldRouter' (which has access to the `000_svc` service network)
20+
Use of a separate service network inhibits 'short-circuiting' the simulated network topology (i.e. any crosstalk past the intended network topo among nodes)
21+
22+
23+
24+
### Usage
25+
This example is meant to showcase how SEED can be made into a testbed for development of features for SCION.
26+
This is mainly achieved by two steps:
27+
- deploy your custom SCION stack inside the simulation
28+
This is achieved by setting the `SetupSpecification` option on the `ScionRouting`layer and
29+
make it point to the specific repo checkout you want to use.
30+
As a consequence a named docker volume containing the binary distributables is mounted into every node in the simulation
31+
under the `/bin/scion` path.
32+
33+
- install a GolangDevelopmentServer on every node in the emulation
34+
This turns every simulation node into a Golang development environment (with go, git, delve, gopls, vscode-server),
35+
by mounting a bunch of docker volumes into each container containing the installation.
36+
(they'll even share the module/build cache)
37+
For each repo that you check out a named docker volume will be generated that accommodates the code and is shared between all nodes.
38+
If you now set up your project build-task to output the distributables into `/bin/scion`, you just need to restart the simulation docker containers,
39+
to run and test a SCION stack with your latest changes in place.
40+
Moreover changes done to the code in one container, are visible to all others (if checkout mode is `AccessMode.shared` ),
41+
so it doesn't matter on which of the nodes you trigger a recompile. The changes will take effect on all simulation nodes.
42+
43+
44+
```
45+
spec = SetupSpecification.LOCAL_BUILD(
46+
CheckoutSpecification(
47+
mode = "build",
48+
git_repo_url = "https://github.com/johndoe/scion.git", # your SCION fork
49+
checkout = "v0.12.0" # could be tag, branch or commit-hash
50+
))
51+
routing = ScionRouting(setup_spec=OptionRegistry().scion_setup_spec(spec))
52+
```
53+
54+
55+
```
56+
devsvc = GolangDevService('jane.doe', '[email protected]')
57+
repo_url = 'https://github.com/scionproto/scion.git'
58+
repo_branch = 'v0.12.0'
59+
repo_path = '/home/root/repos/scion'
60+
61+
svc = devsvc.install(f'dev_152_cs1')
62+
svc.checkoutRepo(repo_url, repo_path, repo_branch, AccessMode.shared)
63+
emu.addBinding(Binding(f'dev_152_cs1', filter=Filter(nodeName=as152_cs1.getName(), asn=152)))
64+
65+
svc3 = devsvc.install(f'dev_153_cs1')
66+
svc3.checkoutRepo(repo_url, repo_path, repo_branch, AccessMode.shared)
67+
emu.addBinding(Binding(f'dev_153_cs1', filter=Filter(nodeName=as153_cs1.getName(), asn=153)))
68+
69+
```
70+
71+
72+
To do any development you first have to bring up the simulation `docker compose up` and then attach to one of the containers with DevServer installation
73+
(i.e. from within VSCode IDE via the [RemoteDevelopment Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack) ).
74+
You can attach to multiple nodes at the same time and i.e. send a packet from the integrated terminal in one window,
75+
and hit a breakpoint set in the border router of any on-path AS once the packet eventually arrives there and step through to see if its correctly processed the way you intended.
76+
77+
![Attach to Emulator](media/attach_to_emu_container.png)
78+
79+
80+
![Develop your own SCION features in SEED](media/inside_dev_container.png)
Loading
Loading

examples/scion/S08_real_world/scion_dev_service.py renamed to examples/scion/S08_dev_service/scion_dev_service.py

+16-10
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
from seedemu.services import GolangDevService, AccessMode
66
from seedemu.core import Emulator, Binding, Filter
7-
7+
88
from seedemu.compiler import Docker
9-
from seedemu.core import Emulator
10-
from seedemu.layers import ScionBase, ScionRouting, ScionIsd, Scion
9+
from seedemu.core import Emulator, OptionRegistry
10+
from seedemu.layers import ScionBase, ScionRouting, ScionIsd, Scion, CheckoutSpecification, SetupSpecification
1111
from seedemu.layers.Scion import LinkType as ScLinkType
1212

1313
from seedemu.compiler import Docker, Platform
@@ -32,19 +32,25 @@ def run(dumpfile = None):
3232
else:
3333
print(f"Usage: {script_name} amd|arm")
3434
sys.exit(1)
35-
35+
3636
# Initialize
3737
emu = Emulator()
3838
base = ScionBase()
39-
routing = ScionRouting()
39+
spec = SetupSpecification.LOCAL_BUILD(
40+
CheckoutSpecification(
41+
mode = "build",
42+
git_repo_url = "https://github.com/scionproto/scion.git",
43+
checkout = "v0.12.0" # could be tag, branch or commit-hash
44+
))
45+
routing = ScionRouting(setup_spec=OptionRegistry().scion_setup_spec(spec))
4046
scion_isd = ScionIsd()
4147
scion = Scion()
4248

4349
devsvc = GolangDevService('jane.doe', '[email protected]')
4450
repo_url = 'https://github.com/scionproto/scion.git'
4551
repo_branch = 'v0.12.0'
4652
repo_path = '/home/root/repos/scion'
47-
53+
4854

4955

5056
# SCION ISDs
@@ -58,7 +64,7 @@ def run(dumpfile = None):
5864
scion_isd.addIsdAs(1, 150, is_core=True)
5965
as150.createNetwork('net0')
6066
as150.createControlService('cs1').joinNetwork('net0')
61-
as150_router = as150.createRealWorldRouter('br0', prefixes=['10.150.0.0/24'])# the 'auto' gen prefix of net0
67+
as150_router = as150.createRealWorldRouter('br0', prefixes=['0.0.0.0/1', '128.0.0.0/1'])
6268
# expectation: hosts from within AS150 can ping outside world i.e. 8.8.8.8
6369
# Hosts in the other ASes can't!!
6470
as150_router.joinNetwork('net0').joinNetwork('ix100')
@@ -83,8 +89,8 @@ def run(dumpfile = None):
8389
scion_isd.addIsdAs(1, 153, is_core=False)
8490
scion_isd.setCertIssuer((1, 153), issuer=150)
8591
as153.createNetwork('net0')
86-
as153_cs1 = as153.createControlService('cs1').joinNetwork('net0')
87-
92+
as153_cs1 = as153.createControlService('cs1').joinNetwork('net0')
93+
8894
as153_router = as153.createRouter('br0')
8995
as153_router.joinNetwork('net0')
9096
as153_router.crossConnect(150, 'br0', '10.50.0.3/29')
@@ -117,7 +123,7 @@ def run(dumpfile = None):
117123
emu.render()
118124

119125
###############################################################################
120-
# Compilation
126+
# Compilation
121127

122128
emu.compile(Docker(platform=platform), './output', override=True)
123129

examples/scion/S08_real_world/README.md

-50
This file was deleted.

seedemu/core/Network.py

+3
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,9 @@ def getRemoteAccessProvider(self) -> RemoteAccessProvider:
305305
def getExternalConnectivityProvider(self) -> ExternalConnectivityProvider:
306306
return self.__ecp
307307

308+
def getExternalConnectivityProvider(self) -> ExternalConnectivityProvider:
309+
return self.__ecp
310+
308311
def print(self, indent: int) -> str:
309312
out = ' ' * indent
310313
out += 'Network {} ({}):\n'.format(self.__name, self.__type)

seedemu/core/Node.py

+23-15
Original file line numberDiff line numberDiff line change
@@ -1184,7 +1184,7 @@ class RealWorldRouterMixin():
11841184
@todo real world access.
11851185
"""
11861186

1187-
__realworld_routes: List[str]
1187+
__realworld_routes: List[Tuple[str, Optional[List[str]]]]
11881188
__sealed: bool
11891189
__hide_hops: bool
11901190

@@ -1198,18 +1198,18 @@ def initRealWorld(self, hideHops: bool):
11981198
self.__hide_hops = hideHops
11991199
self.addSoftware('iptables')
12001200

1201-
def addRealWorldRoute(self, prefix: str) -> 'RealWorldRouter':
1201+
def addRealWorldRoute(self, prefix: str, route_clientele: str = None) -> 'RealWorldRouter':
12021202
"""!
12031203
@brief Add real world route.
12041204
1205-
@param prefix prefix.
1206-
1205+
@param prefix prefix i.e. ['0.0.0.0/1', '128.0.0.0/1'] for the 'real' real whole world global internet
1206+
@param route_clientele the network/s who shall be able to route to the given 'real world' prefix
12071207
@throws AssertionError if sealed.
12081208
12091209
@returns self, for chaining API calls.
12101210
"""
12111211
assert not self.__sealed, 'Node sealed.'
1212-
self.__realworld_routes.append(prefix)
1212+
self.__realworld_routes.append( (prefix, route_clientele) )
12131213

12141214
return self
12151215

@@ -1219,10 +1219,10 @@ def getRealWorldRoutes(self) -> List[str]:
12191219
12201220
@returns list of prefixes.
12211221
"""
1222-
return self.__realworld_routes
1222+
return [r for (r, _) in self.__realworld_routes]
12231223

12241224
# called in RoutingLayer::render() - That is AFTER ::configure() :)
1225-
# where its been decided if this node has to run BIRD routing daemon
1225+
# where its been decided if this node has to run BIRD routing daemon
12261226
# because it is connected to more than one local(direct) network
12271227
def seal(self, svc_net: Network):
12281228
"""!
@@ -1238,10 +1238,17 @@ def seal(self, svc_net: Network):
12381238
# position 0-1 is '/interface_setup' (and chmod +x)
12391239
self.insertStartCommand(2, '/rw_configure_script')
12401240
self.insertStartCommand(2, 'chmod +x /rw_configure_script')
1241-
1242-
for prefix in self.__realworld_routes:
1241+
1242+
for prefix, route_clientele in self.__realworld_routes:
1243+
if route_clientele != None:
1244+
if isinstance(route_clientele, list):
1245+
for src in route_clientele:
1246+
self.appendFile('/rw_configure_script', 'iptables -t nat -A POSTROUTING -d {} -s {} -j MASQUERADE\n'.format(prefix, src))
1247+
else:
1248+
self.appendFile('/rw_configure_script', 'iptables -t nat -A POSTROUTING -d {} -s {} -j MASQUERADE\n'.format(prefix, route_clientele))
12431249
# nat matched only
1244-
self.appendFile('/rw_configure_script', 'iptables -t nat -A POSTROUTING -d {} -j MASQUERADE\n'.format(prefix))
1250+
else:
1251+
self.appendFile('/rw_configure_script', 'iptables -t nat -A POSTROUTING -d {} -j MASQUERADE\n'.format(prefix))
12451252

12461253
if self.__hide_hops:
12471254
# remove realworld hops
@@ -1251,15 +1258,14 @@ def seal(self, svc_net: Network):
12511258
# some might run SCION BR instead
12521259
if 'bird2' in self.getSoftware():
12531260
# if this check is too dirty/hacky, we could use Attributes like in: "if hasattr(self, '__sealed'):" but this is still hacky
1254-
self.appendFile('/rw_configure_script', )
12551261
fill_placeholder = """\
12561262
gw="`ip rou show default | cut -d' ' -f3`"
12571263
sed -i 's/!__default_gw__!/'"$gw"'/g' /etc/bird/bird.conf
12581264
"""
12591265
self.appendFile('/rw_configure_script', fill_placeholder)
12601266

12611267
self.addTable('t_rw')
1262-
statics = '\n ipv4 { table t_rw; import all; };\n route ' + ' via !__default_gw__!;\n route '.join(self.__realworld_routes)
1268+
statics = '\n ipv4 { table t_rw; import all; };\n route ' + ' via !__default_gw__!;\n route '.join(self.getRealWorldRoutes())
12631269
statics += ' via !__default_gw__!;\n'
12641270
self.addProtocol('static', 'real_world', statics)
12651271
self.addTablePipe('t_rw', 't_bgp', exportFilter = 'filter { bgp_large_community.add(LOCAL_COMM); bgp_local_pref = 40; accept; }')
@@ -1287,7 +1293,9 @@ def print(self, indent: int) -> str:
12871293
return out
12881294

12891295
def promote_to_real_world_router(node: Node, hideHops: bool):
1290-
"""Dynamically inject RealWorldRouterMixin into a Node instance"""
1296+
"""!@brief Dynamically inject RealWorldRouterMixin into a Node instance
1297+
to augment it by RealWorld routing capabilities
1298+
"""
12911299

12921300
if not isinstance(node, RealWorldRouterMixin): # Prevent double-mixing
12931301
node.__class__ = type("RealWorldRouter", (RealWorldRouterMixin, node.__class__), {})
@@ -1304,7 +1312,7 @@ class ScionRouterMixin():
13041312

13051313
__interfaces: Dict[int, Dict] # IFID to interface
13061314
__next_port: int # Next free UDP port
1307-
1315+
13081316
# Never been used anyway
13091317
#def __init__(self):
13101318
# super().__init__()
@@ -1357,7 +1365,7 @@ def print(self, indent: int) -> str:
13571365
out += ' ' * indent
13581366
out += 'SCION border router'
13591367
return out
1360-
1368+
13611369
def promote_to_scion_router(node: Node):
13621370
"""Dynamically inject ScionRouterMixin into a Node instance"""
13631371
if not isinstance(node, ScionRouterMixin): # Prevent double-mixing

seedemu/core/ScionAutonomousSystem.py

-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ def createRealWorldRouter(self, name: str, hideHops: bool = True, prefixes: List
170170
# this is fine, since the ScionRouter will be 'mixed-in' in later in the ::configure() step,
171171
# if the router is actually found to be a border router
172172
return super().createRealWorldRouter(name, hideHops, prefixes)
173-
174173
def registerNodes(self, emulator: Emulator):
175174
"""!
176175
@copydoc AutonomousSystem.registerNodes()

seedemu/layers/Routing.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def render(self, emulator: Emulator):
163163
hit: bool = False
164164
for ((scope, type, name), obj) in reg.getAll().items():
165165
# make sure that on each externaly connected net (those with at least one host who requested it)
166-
# (I): there is at least one RealWorldRouter
166+
# (I): there is at least one RealWorldRouter
167167
# (II): the RWR is the default gateway of the requesters on this net
168168
if type == 'net' and obj.getType() == NetworkType.Local:
169169
if (p := obj.getExternalConnectivityProvider() ):
@@ -172,7 +172,9 @@ def render(self, emulator: Emulator):
172172
for r in rwr_candidates:
173173
r = promote_to_real_world_router(r, False)
174174
route = obj.getPrefix()
175-
r.addRealWorldRoute(route)
175+
# only for hosts on THIS network ('route') the RWA is provided
176+
r.addRealWorldRoute('0.0.0.0/1', str(route))
177+
r.addRealWorldRoute('128.0.0.0/1', str(route))
176178
for h, gw in new_gateway_constraints.items():
177179
assert h not in gateway_constraints, 'multihomed host ?!'
178180
gateway_constraints[h] = gw
@@ -187,7 +189,7 @@ def render(self, emulator: Emulator):
187189
if type == 'rnode':
188190
rnode: Router = obj
189191
if issubclass(rnode.__class__, RealWorldRouterMixin): # could also be ScionRouter which needs RealWorldAccess
190-
192+
191193
# this is an exception - Only for service net (not part of simulation)
192194
rnode._Node__joinNetwork(svc_net)
193195
[l, b, d] = svc_net.getDefaultLinkProperties()
@@ -210,7 +212,7 @@ def render(self, emulator: Emulator):
210212
else:
211213
cur_scope = ScopedRegistry(scope, reg)
212214
candidates = cur_scope.getByType('rnode')
213-
215+
214216

215217
for router in candidates:
216218
if rif != None: break

0 commit comments

Comments
 (0)