From 5323d67683bd6d42f46c831683bce694b6c09779 Mon Sep 17 00:00:00 2001 From: Emanuele Di Pascale Date: Wed, 26 Nov 2025 11:10:29 +0100 Subject: [PATCH] Add virtual externals VLAB page Signed-off-by: Emanuele Di Pascale --- docs/vlab/.pages | 1 + docs/vlab/virt-externals.md | 457 ++++++++++++++++++++++++++++++++++++ 2 files changed, 458 insertions(+) create mode 100644 docs/vlab/virt-externals.md diff --git a/docs/vlab/.pages b/docs/vlab/.pages index f657cf6d..67276b8d 100644 --- a/docs/vlab/.pages +++ b/docs/vlab/.pages @@ -2,4 +2,5 @@ nav: - overview.md - running.md - demo.md + - virt-externals.md - ... diff --git a/docs/vlab/virt-externals.md b/docs/vlab/virt-externals.md new file mode 100644 index 00000000..31d0de12 --- /dev/null +++ b/docs/vlab/virt-externals.md @@ -0,0 +1,457 @@ +# Virtual Externals + +VLAB also supports virtual [externals](../user-guide/external.md). These are implemented using +a virtual machine running [FRR](https://frrouting.org/), which is automatically configured +from the topology information to behave like one or more BGP instances advertising a default +route towards the internet (assuming the host running the VLAB is connected to the internet). + +Here we will explain how to generate a topology with a virtual external, and how to test it +using either Fabric or Gateway external peerings. + +## Creating the topology + +Externals can be connected to any non-MCLAG leaf, whether in a spine-leaf topology or +a mesh one. + +!!! warning + Due to a [known limitation](../known-limitations/known-limitations.md#external-peering-over-a-connection-originating-from-an-mclag-switch-can-fail) + with MCLAG and external peerings, connecting externals to MCLAG leaves is not recommended. + + Additionally, due to a limitation of the SONiC virtual switch implementation, having more than one + external connection between virtual switches and an external will cause issues. + This limitation does not apply to physical switches. + +The example below shows how to initialize and generate a spine-leaf topology with two +ESLAG leaves, one orphan leaf with a virtual external attached to it, and a gateway +attached to the two spines: + +``` +ubuntu@docs:~/hhfab$ ./hhfab init -f --dev --gw +09:27:35 INF Hedgehog Fabricator version=v0.43.1 +09:27:35 INF Generated initial config +09:27:35 INF Adjust configs (incl. credentials, modes, subnets, etc.) file=fab.yaml +09:27:35 INF Include wiring (fabric/gateway) files (.yaml) or adjust imported ones dir=include +ubuntu@docs:~/hhfab$ ./hhfab vlab gen --mclag-leafs-count=0 --eslag-leaf-groups=2 --orphan-leafs-count=1 --externals=1 --external-orphan-connections=1 +09:28:36 INF Hedgehog Fabricator version=v0.43.1 +09:28:36 INF Building VLAB wiring diagram fabricMode=spine-leaf +09:28:36 INF >>> spinesCount=2 fabricLinksCount=2 meshLinksCount=0 +09:28:36 INF >>> eslagLeafGroups=2 +09:28:36 INF >>> gatewayUplinks=2 gatewayDriver=kernel +09:28:36 INF >>> mclagLeafsCount=0 mclagSessionLinks=0 mclagPeerLinks=0 +09:28:36 INF >>> orphanLeafsCount=1 +09:28:36 INF >>> mclagServers=0 eslagServers=2 unbundledServers=1 bundledServers=1 +09:28:36 INF >>> externalCount=1 externalMclagConnCount=0 externalEslagConnCount=0 externalOrphanConnCount=1 +09:28:36 INF Generated wiring file name=vlab.generated.yaml +ubuntu@docs:~/hhfab$ ./hhfab vlab up -f -m=manual -r=wait +09:31:53 INF Hedgehog Fabricator version=v0.43.1 +[...] +09:50:14 INF All switches and gateways are ready took=10m6.844960653s +09:50:14 INF All on-ready commands finished took=10m6.848788372s +``` + +Here's a graph of the resulting topology: +```mermaid +graph TD + +%% Style definitions +classDef gateway fill:#FFF2CC,stroke:#999,stroke-width:1px,color:#000 +classDef spine fill:#F8CECC,stroke:#B85450,stroke-width:1px,color:#000 +classDef leaf fill:#DAE8FC,stroke:#6C8EBF,stroke-width:1px,color:#000 +classDef server fill:#D5E8D4,stroke:#82B366,stroke-width:1px,color:#000 +classDef mclag fill:#F0F8FF,stroke:#6C8EBF,stroke-width:1px,color:#000 +classDef eslag fill:#FFF8E8,stroke:#CC9900,stroke-width:1px,color:#000 +classDef external fill:#FFCC99,stroke:#D79B00,stroke-width:1px,color:#000 +classDef hidden fill:none,stroke:none +classDef legendBox fill:white,stroke:#999,stroke-width:1px,color:#000 + +%% Network diagram +subgraph Gateways[" "] + direction LR + Gateway_1["gateway-1"] +end + +subgraph Spines[" "] + direction LR + subgraph Spine_01_Group [" "] + direction TB + Spine_01["spine-01
spine"] + end + subgraph Spine_02_Group [" "] + direction TB + Spine_02["spine-02
spine"] + end +end + +subgraph ExternalsRight[" "] + direction TB + External_01["external-01"] +end + +subgraph Leaves[" "] + direction LR + subgraph Eslag_1 ["eslag-1"] + direction LR + Leaf_01["leaf-01
server-leaf"] + Leaf_02["leaf-02
server-leaf"] + end + + Leaf_03["leaf-03
server-leaf"] +end + +subgraph Servers[" "] + direction TB + Server_03["server-03"] + Server_01["server-01"] + Server_02["server-02"] + Server_04["server-04"] + Server_05["server-05"] + Server_06["server-06"] +end + +%% Connections + +%% Gateway connections +Gateway_1 --- Spine_02 +Gateway_1 --- Spine_01 + +%% Spine_01 -> Leaves +Spine_01 --- Leaf_01 +Spine_01 --- Leaf_02 +Spine_01 --- Leaf_03 + +%% Spine_02 -> Leaves +Spine_02 --- Leaf_02 +Spine_02 --- Leaf_01 +Spine_02 --- Leaf_03 + +%% Leaves -> Servers +Leaf_01 --- Server_01 +Leaf_01 --- Server_03 +Leaf_01 --- Server_02 + +Leaf_02 --- Server_02 +Leaf_02 --- Server_04 +Leaf_02 --- Server_01 + +Leaf_03 --- Server_06 +Leaf_03 --- Server_05 + +%% Mesh connections + +%% External connections +Leaf_03 --- External_01 + +subgraph Legend["Network Connection Types"] + direction LR + %% Create invisible nodes for the start and end of each line + L1(( )) --- |"Fabric Links"| L2(( )) + L5(( )) --- |"Bundled Server Links (x2)"| L6(( )) + L7(( )) --- |"Unbundled Server Links"| L8(( )) + L9(( )) --- |"ESLAG Server Links"| L10(( )) + L11(( )) --- |"Gateway Links"| L12(( )) + L13(( )) --- |"External Links"| L14(( )) + P1(( )) --- P2(( )) +end + +class Gateway_1 gateway +class Spine_01,Spine_02 spine +class Leaf_01,Leaf_02,Leaf_03 leaf +class Server_03,Server_01,Server_02,Server_04,Server_05,Server_06 server +class External_01 external +class Eslag_1 eslag +class L1,L2,L3,L4,L5,L6,L7,L8,L9,L10,L11,L12,P1,P2,L13,L14 hidden +class Legend legendBox +linkStyle default stroke:#666,stroke-width:2px +linkStyle 0,1 stroke:#CC9900,stroke-width:2px +linkStyle 2,3,4,5,6,7 stroke:#CC3333,stroke-width:4px +linkStyle 12,14 stroke:#66CC66,stroke-width:4px +linkStyle 8,10,11,13 stroke:#CC9900,stroke-width:4px,stroke-dasharray:5 5 +linkStyle 9,15 stroke:#999999,stroke-width:2px +linkStyle 16 stroke:#D79B00,stroke-width:2px +linkStyle 17 stroke:#B85450,stroke-width:2px +linkStyle 18 stroke:#82B366,stroke-width:2px +linkStyle 19 stroke:#000000,stroke-width:2px +linkStyle 20 stroke:#CC9900,stroke-width:2px,stroke-dasharray:5 5 +linkStyle 21 stroke:#CC9900,stroke-width:2px +linkStyle 22 stroke:#D79B00,stroke-width:2px +linkStyle 23 stroke:#FFFFFF + +%% Make subgraph containers invisible +style Gateways fill:none,stroke:none +style Spines fill:none,stroke:none +style ExternalsRight fill:none,stroke:none +style Leaves fill:none,stroke:none +style Servers fill:none,stroke:none +style Spine_01_Group fill:none,stroke:none +style Spine_02_Group fill:none,stroke:none +``` + +From the control node, we can verify that both the external and its attachment +to the orphan leaf have been created: + +``` +core@control-1 ~ $ kubectl get external +NAME EXTERNAL CONNECTION AGE +externalattachment.vpc.githedgehog.com/leaf-03--external-01 external-01 leaf-03--external 43m + +NAME IPV4NS INCOMM OUTCOMM AGE +external.vpc.githedgehog.com/external-01 default 65102:1000 64102:1000 43m +``` + +## Demonstrating external peering + +Let's first create some VPCs, using any of the methods described in [the demo page](demo.md), +e.g.: + +``` +ubuntu@docs:~/hhfab$ ./hhfab vlab setup-vpcs +10:29:26 INF Hedgehog Fabricator version=v0.43.1 +10:29:26 INF Wiring hydrated successfully mode=if-not-present +10:29:26 INF VLAB config loaded file=vlab/config.yaml +10:29:26 INF Setting up VPCs and VPCAttachments mode="" perSubnet=1 perVPC=1 wait=true cleanup=false +10:29:26 INF Removing all peerings (VPC, External and Gateway) +10:29:27 INF Waiting for switches and gateways ready appliedFor=15s timeout=10m0s +10:29:27 INF Expected switches agent=v0.96.2 switches="[leaf-01 leaf-02 leaf-03 spine-01 spine-02]" +10:29:27 INF Expected gateways agent=v0.27.1 gateways=[gateway-1] +10:29:27 INF All switches and gateways are ready took=710.610466ms +10:29:27 INF Configuring VPCs and VPCAttachments +10:29:28 INF Created vpc=vpc-01 subnets=1 +10:29:28 INF Created vpc=vpc-02 subnets=1 +10:29:28 INF Created vpc=vpc-03 subnets=1 +10:29:28 INF Created vpc=vpc-04 subnets=1 +10:29:28 INF Created vpc=vpc-05 subnets=1 +10:29:28 INF Created vpc=vpc-06 subnets=1 +10:29:28 INF Created vpcattachment=server-01--eslag--leaf-01--leaf-02--vpc-01--subnet-01 +10:29:28 INF Created vpcattachment=server-02--eslag--leaf-01--leaf-02--vpc-02--subnet-01 +10:29:28 INF Created vpcattachment=server-03--unbundled--leaf-01--vpc-03--subnet-01 +10:29:28 INF Created vpcattachment=server-04--bundled--leaf-02--vpc-04--subnet-01 +10:29:28 INF Created vpcattachment=server-05--unbundled--leaf-03--vpc-05--subnet-01 +10:29:28 INF Created vpcattachment=server-06--bundled--leaf-03--vpc-06--subnet-01 +10:29:43 INF Waiting for switches and gateways ready appliedFor=15s timeout=10m0s +10:29:43 INF Expected switches agent=v0.96.2 switches="[leaf-01 leaf-02 leaf-03 spine-01 spine-02]" +10:29:43 INF Expected gateways agent=v0.27.1 gateways=[gateway-1] +10:29:43 INF Switches status notReady="[leaf-01 leaf-02 leaf-03]" notUpdated=[] +10:29:43 INF Gateways status notReady=[gateway-1] notUpdated=[] +10:30:13 INF Switches status notReady=[leaf-02] notUpdated=[] +10:30:18 INF All switches and gateways are ready took=35.188868988s +10:30:18 INF Configuring networking on servers +10:30:31 INF Configured server=server-05 addr=10.0.5.2/24 netconf="vlan 1005 enp2s1" +10:30:31 INF Configured server=server-03 addr=10.0.3.2/24 netconf="vlan 1003 enp2s1" +10:30:39 INF Configured server=server-02 addr=10.0.2.2/24 netconf="bond 1002 layer2+3 enp2s1 enp2s2" +10:30:40 INF Configured server=server-06 addr=10.0.6.2/24 netconf="bond 1006 layer2+3 enp2s1 enp2s2" +10:30:40 INF Configured server=server-01 addr=10.0.1.2/24 netconf="bond 1001 layer2+3 enp2s1 enp2s2" +10:30:40 INF Configured server=server-04 addr=10.0.4.2/24 netconf="bond 1004 layer2+3 enp2s1 enp2s2" +10:30:40 INF All servers configured and verified took=1m14.334925108s +``` + +By default, servers in VLAB are isolated from the internet; we can verify that either +using the test-connectivity utility of hhfab: + +``` +ubuntu@docs:~/hhfab$ ./hhfab vlab conns -v --curls=1 --pings=0 --iperfs=0 +11:09:47 INF Hedgehog Fabricator version=v0.43.1 +[...] +11:09:49 DBG Checking external connectivity from=server-03 expected=false +11:09:49 DBG Checking external connectivity from=server-01 expected=false +11:09:49 DBG Checking external connectivity from=server-02 expected=false +11:09:49 DBG Checking external connectivity from=server-04 expected=false +11:09:49 DBG Checking external connectivity from=server-06 expected=false +11:09:49 DBG Checking external connectivity from=server-05 expected=false +[...] +11:09:49 INF Test connectivity passed took=1.974696842s +``` + +Or by attempting to ping any well-known publicly available address from a server: +``` +core@server-01 ~ $ ping -c 3 -W 1 8.8.8.8 +PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. +From 10.0.4.1 icmp_seq=1 Destination Net Unreachable +From 10.0.4.1 icmp_seq=2 Destination Net Unreachable +From 10.0.4.1 icmp_seq=3 Destination Net Unreachable + +--- 8.8.8.8 ping statistics --- +3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 2004ms +``` + +Let's now connect some of the VPCs to the internet using the virtual external we created. + +!!! warning + Both types of peering rely on ACLs to prevent crosstalk between VPCs + that are peered to the same external. Since virtual switches do not + actually implement ACLs, in our VLAB peering multiple VPCs to the + same external will result in those VPCs being able to talk to each + other. This issue does not affect physical switches. + +### External peering via the Fabric + +We can create an external peering via the Fabric either using the hhfab utility or +by manually creating the necessary kubernetes objects. For example, using hhfab: + +``` +ubuntu@docs:~/hhfab$ ./hhfab vlab setup-peerings 1~external-01:s=subnet-01 +10:49:42 INF Hedgehog Fabricator version=v0.43.1 +10:49:42 INF Wiring hydrated successfully mode=if-not-present +10:49:42 INF VLAB config loaded file=vlab/config.yaml +10:49:42 INF Setting up VPC and External Peerings numRequests=1 +10:49:42 INF Waiting for switches and gateways ready appliedFor=15s timeout=10m0s +10:49:43 INF Expected switches agent=v0.96.2 switches="[spine-02 leaf-01 leaf-02 leaf-03 spine-01]" +10:49:43 INF Expected gateways agent=v0.27.1 gateways=[gateway-1] +10:49:43 INF All switches and gateways are ready took=833.210714ms +10:49:44 INF Enforcing External Peering name=vpc-01--external-01 vpc=vpc-01 vpcSubnets=[subnet-01] external=external-01 externalPrefixes=[{Prefix:0.0.0.0/0}] +10:49:44 INF Created externalpeering=vpc-01--external-01 +10:49:59 INF Waiting for switches and gateways ready appliedFor=15s timeout=10m0s +10:49:59 INF Expected switches agent=v0.96.2 switches="[leaf-03 spine-01 spine-02 leaf-01 leaf-02]" +10:49:59 INF Expected gateways agent=v0.27.1 gateways=[gateway-1] +10:49:59 INF Switches status notReady=[leaf-03] notUpdated=[] +10:50:24 INF All switches and gateways are ready took=25.016399952s +10:50:24 INF VPC and External Peerings setup complete took=41.484322857s +``` + +We can verify that the above command is equivalent to creating the following kubernetes object: + +``` +core@control-1 ~ $ kubectl get externalpeering vpc-01--external-01 -o yaml +apiVersion: vpc.githedgehog.com/v1beta1 +kind: ExternalPeering +metadata: + creationTimestamp: "2025-11-26T10:49:44Z" + generation: 1 + labels: + fabric.githedgehog.com/external: external-01 + fabric.githedgehog.com/vpc: vpc-01 + name: vpc-01--external-01 + namespace: default + resourceVersion: "24079" + uid: 6ceb8e2a-c4a8-4275-9106-ddfcf8d401ab +spec: + permit: + external: + name: external-01 + prefixes: + - prefix: 0.0.0.0/0 + vpc: + name: vpc-01 + subnets: + - subnet-01 +``` + +We can now verify that server-01, attached to vpc-01, is able to reach the internet +via the virtual external, e.g.: + +``` +ubuntu@docs:~/hhfab$ ./hhfab vlab conns -v --source=server-01 --curls=1 --pings=0 --iperfs=0 +10:53:36 INF Hedgehog Fabricator version=v0.43.1 +[...] +10:53:38 DBG Checking external connectivity from=server-01 expected=true reason=switch-peering +10:53:38 DBG Running curls from=server-01 to=1.0.0.1 count=1 +10:53:38 DBG Curl result from=server-01 to=1.0.0.1 expected=true ok=true fail=false err= stdout="\n301 Moved Permanently\n\n

301 Moved Permanently

\n
cloudflare
\n\n\n" stderr="" +10:53:38 INF Test connectivity passed took=1.98032113s +``` + +Or alternatively, we can ssh to server-01 and manually check connectivity, e.g. with ping: + +``` +ubuntu@docs:~/hhfab$ ./hhfab vlab ssh -bn server-01 +Last login: Wed Nov 26 10:53:36 UTC 2025 from 172.31.3.2 on ssh +Flatcar Container Linux by Kinvolk stable 4230.2.4 for QEMU +Hedgehog VLAB Server +Update Strategy: No Reboots +core@server-01 ~ $ ping -c 3 -W 1 8.8.8.8 +PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. +64 bytes from 8.8.8.8: icmp_seq=1 ttl=252 time=10.6 ms +64 bytes from 8.8.8.8: icmp_seq=2 ttl=252 time=9.29 ms +64 bytes from 8.8.8.8: icmp_seq=3 ttl=252 time=15.6 ms + +--- 8.8.8.8 ping statistics --- +3 packets transmitted, 3 received, 0% packet loss, time 2004ms +rtt min/avg/max/mdev = 9.286/11.825/15.591/2.716 ms +``` + +### External peering via the Gateway + +We can repeat the above experiment with another VPC, this time peering +it to the external via the gateway. Once again, we can do so either +using the hhfab utility: + +``` +ubuntu@docs:~/hhfab$ ./hhfab vlab setup-peerings 4~external-01:s=subnet-01:gw +11:03:12 INF Hedgehog Fabricator version=v0.43.1 +11:03:12 INF Wiring hydrated successfully mode=if-not-present +11:03:12 INF VLAB config loaded file=vlab/config.yaml +11:03:12 INF Setting up VPC and External Peerings numRequests=1 +11:03:12 INF Waiting for switches and gateways ready appliedFor=15s timeout=10m0s +11:03:12 INF Expected switches agent=v0.96.2 switches="[leaf-02 leaf-03 spine-01 spine-02 leaf-01]" +11:03:12 INF Expected gateways agent=v0.27.1 gateways=[gateway-1] +11:03:13 INF All switches and gateways are ready took=747.345082ms +11:03:13 INF Deleting ExternalPeering name=vpc-01--external-01 +11:03:13 INF Enforcing GatewayPeering name=vpc-04--external-01 vpc1=vpc-04 vpc2=ext.external-01 +11:03:13 INF Created gwpeering=vpc-04--external-01 +11:03:28 INF Waiting for switches and gateways ready appliedFor=15s timeout=10m0s +11:03:28 INF Expected switches agent=v0.96.2 switches="[leaf-01 leaf-02 leaf-03 spine-01 spine-02]" +11:03:28 INF Expected gateways agent=v0.27.1 gateways=[gateway-1] +11:03:28 INF Switches status notReady=[leaf-03] notUpdated=[] +11:03:28 INF Gateways status notReady=[gateway-1] notUpdated=[] +11:03:48 INF All switches and gateways are ready took=20.008712634s +11:03:48 INF VPC and External Peerings setup complete took=36.467660151s +``` + +or by creating a gateway peering object like the one that we just generated: + +``` +core@control-1 ~ $ kubectl get peering vpc-04--external-01 -o yaml +apiVersion: gateway.githedgehog.com/v1alpha1 +kind: Peering +metadata: + creationTimestamp: "2025-11-26T11:03:13Z" + generation: 1 + labels: + vpc.gateway.githedgehog.com/ext.external-01: "true" + vpc.gateway.githedgehog.com/vpc-04: "true" + name: vpc-04--external-01 + namespace: default + resourceVersion: "28024" + uid: 6e04519f-b599-488e-913b-7a28e01899d5 +spec: + peering: + ext.external-01: + expose: + - ips: + - cidr: 0.0.0.0/0 + vpc-04: + expose: + - ips: + - cidr: 10.0.4.0/24 +``` + +In this case, traffic from server-04 destined to the internet will go first +to the gateway and then to the external via the attached orphan leaf. We can +verify that this works in the same way we just did with Fabric peerings: + +``` +ubuntu@docs:~/hhfab$ ./hhfab vlab conns -v --source=server-04 --curls=1 --pings=0 --iperfs=0 +11:06:49 INF Hedgehog Fabricator version=v0.43.1 +[...] +11:06:51 DBG Checking external connectivity from=server-04 expected=true reason=gateway-peering peering=vpc-04--external-01 +11:06:51 DBG Running curls from=server-04 to=1.0.0.1 count=1 +11:06:51 DBG Curl result from=server-04 to=1.0.0.1 expected=true ok=true fail=false err= stdout="\n301 Moved Permanently\n\n

301 Moved Permanently

\n
cloudflare
\n\n\n" stderr="" +11:06:51 INF Test connectivity passed took=1.961998153s +``` + +Or manually on server-04: + +``` +ubuntu@docs:~/hhfab$ ./hhfab vlab ssh -bn server-04 +Last login: Wed Nov 26 11:06:50 UTC 2025 from 172.31.6.2 on ssh +Flatcar Container Linux by Kinvolk stable 4230.2.4 for QEMU +Hedgehog VLAB Server +Update Strategy: No Reboots +core@server-04 ~ $ ping -c 3 -W 1 8.8.8.8 +PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. +64 bytes from 8.8.8.8: icmp_seq=1 ttl=251 time=13.7 ms +64 bytes from 8.8.8.8: icmp_seq=2 ttl=251 time=21.6 ms +64 bytes from 8.8.8.8: icmp_seq=3 ttl=251 time=57.8 ms + +--- 8.8.8.8 ping statistics --- +3 packets transmitted, 3 received, 0% packet loss, time 2003ms +rtt min/avg/max/mdev = 13.678/31.019/57.824/19.224 ms +```