mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
Merge pull request #1544 from kernelkit/misc
This commit is contained in:
@@ -272,7 +272,7 @@ other contributions that are not aligned to this Code of Conduct."*
|
||||
[PEP-8]: https://peps.python.org/pep-0008/
|
||||
[RDD]: https://tom.preston-werner.com/2010/08/23/readme-driven-development
|
||||
[cbeams]: https://cbea.ms/git-commit/#seven-rules
|
||||
[conduct]: CODE-OF-CONDUCT.md
|
||||
[conduct]: CODE_OF_CONDUCT.md
|
||||
[DCO]: https://developercertificate.org/
|
||||
[closing]: https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests
|
||||
[gpg-verify]: https://docs.github.com/en/authentication/managing-commit-signature-verification
|
||||
|
||||
@@ -113,12 +113,6 @@ jobs:
|
||||
pattern: "artifact-*"
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create checksums ...
|
||||
run: |
|
||||
for file in *.tar.gz; do
|
||||
sha256sum $file > $file.sha256
|
||||
done
|
||||
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
allowUpdates: true
|
||||
@@ -128,7 +122,7 @@ jobs:
|
||||
prerelease: true
|
||||
tag: "latest-boot"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
artifacts: "*.tar.gz*"
|
||||
artifacts: "*.tar.gz"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
|
||||
@@ -223,15 +223,6 @@ jobs:
|
||||
output/images/*-emmc.img*
|
||||
retention-days: 30
|
||||
|
||||
- name: Create checksums
|
||||
run: |
|
||||
cd output/images/
|
||||
for file in *-sdcard.img *-emmc.img; do
|
||||
if [ -f "$file" ]; then
|
||||
sha256sum "$file" > "$file.sha256"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Upload to release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
@@ -242,7 +233,7 @@ jobs:
|
||||
prerelease: true
|
||||
tag: "latest-boot"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
artifacts: "output/images/*-sdcard.img*,output/images/*-emmc.img*"
|
||||
artifacts: "output/images/*-sdcard.img,output/images/*-emmc.img"
|
||||
|
||||
- name: Generate summary
|
||||
run: |
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
name: Manny the Manager
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
checkout:
|
||||
required: true
|
||||
type: boolean
|
||||
cleanup:
|
||||
required: true
|
||||
type: boolean
|
||||
peek:
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
inventory:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Disk usage ...
|
||||
run: |
|
||||
cd
|
||||
du -hs .[^.]*
|
||||
- name: Disk inventory (1/2) ...
|
||||
run: |
|
||||
echo "df -h ========================================================================="
|
||||
df -h
|
||||
echo "mounts ========================================================================"
|
||||
mount
|
||||
- name: File inventory (1/2) ...
|
||||
run: |
|
||||
echo "Current directory: $(pwd)"
|
||||
echo "Files in $HOME ================================================================"
|
||||
ls $HOME
|
||||
echo "Find $HOME ===================================================================="
|
||||
find $HOME
|
||||
- name: Container inventory ...
|
||||
run: |
|
||||
echo "Available container images: ==================================================="
|
||||
docker images
|
||||
echo "Available containers: ========================================================="
|
||||
docker ps -a
|
||||
|
||||
checkout:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: kernelkit/actions/cache-restore@v1
|
||||
with:
|
||||
target: x86_64
|
||||
dl-prefix: dl-netconf
|
||||
- name: Disk inventory (2/2) ...
|
||||
run: |
|
||||
echo "df -h ========================================================================="
|
||||
df -h
|
||||
echo "mounts ========================================================================"
|
||||
mount
|
||||
- name: File inventory (2/2) ...
|
||||
run: |
|
||||
echo "Current directory: $(pwd)"
|
||||
echo "Files in $HOME ================================================================"
|
||||
ls $HOME
|
||||
echo "Find $HOME ===================================================================="
|
||||
find $HOME
|
||||
|
||||
peeky:
|
||||
if: ${{ inputs.peek }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Peek & Poke ...
|
||||
run: |
|
||||
whoami
|
||||
ls -l /mnt/
|
||||
cat /mnt/DATALOSS_WARNING_README.txt
|
||||
sudo mkdir /mnt/x-aarch64
|
||||
sudo chown $(id -un):$(id -gn) /mnt/x-aarch64
|
||||
ls -l /mnt/
|
||||
|
||||
cleanup:
|
||||
if: ${{ inputs.cleanup }}
|
||||
needs: [inventory, peeky]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cleaning up cruft ...
|
||||
run: |
|
||||
docker image prune -af
|
||||
docker volume prune -f
|
||||
docker container prune -f
|
||||
@@ -16,12 +16,6 @@ jobs:
|
||||
pattern: "artifact-*"
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create checksums ...
|
||||
run: |
|
||||
for file in *.tar.gz; do
|
||||
sha256sum $file > $file.sha256
|
||||
done
|
||||
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
allowUpdates: true
|
||||
@@ -32,7 +26,7 @@ jobs:
|
||||
prerelease: true
|
||||
tag: "latest"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
artifacts: "*.tar.gz*"
|
||||
artifacts: "*.tar.gz"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
|
||||
@@ -83,17 +83,6 @@ jobs:
|
||||
pattern: "artifact-*"
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create checksums ...
|
||||
run: |
|
||||
for file in *.tar.gz; do
|
||||
sha256sum $file > $file.sha256
|
||||
done
|
||||
if ls *.qcow2 &>/dev/null; then
|
||||
for file in *.qcow2; do
|
||||
sha256sum "$file" > "$file.sha256"
|
||||
done
|
||||
fi
|
||||
|
||||
- name: Extract ChangeLog entry ...
|
||||
run: |
|
||||
cat doc/ChangeLog.md | ./utils/extract-changelog.sh > release.md
|
||||
@@ -106,7 +95,7 @@ jobs:
|
||||
makeLatest: ${{ steps.rel.outputs.latest }}
|
||||
discussionCategory: ${{ steps.rel.outputs.cat }}
|
||||
bodyFile: release.md
|
||||
artifacts: "*.tar.gz*,*.qcow2*"
|
||||
artifacts: "*.tar.gz,*.qcow2"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
|
||||
@@ -27,17 +27,6 @@ jobs:
|
||||
pattern: "artifact-*"
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create checksums
|
||||
run: |
|
||||
for file in *.tar.gz; do
|
||||
sha256sum $file > $file.sha256
|
||||
done
|
||||
if ls *.qcow2 &>/dev/null; then
|
||||
for file in *.qcow2; do
|
||||
sha256sum "$file" > "$file.sha256"
|
||||
done
|
||||
fi
|
||||
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
tag: latest
|
||||
@@ -54,7 +43,7 @@ jobs:
|
||||
|
||||
**Commit:** ${{ github.sha }}
|
||||
**Built:** ${{ github.run_id }}
|
||||
artifacts: "*.tar.gz*,*.qcow2*"
|
||||
artifacts: "*.tar.gz,*.qcow2"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
|
||||
@@ -1,63 +1,21 @@
|
||||
[![License Badge][]][License] [![Release Badge][]][Release] [![GitHub Status][]][GitHub] [![Coverity Status][]][Coverity Scan] [![Discord][discord-badge]][discord-url]
|
||||
[![License Badge][]][License] [![Release Badge][]][Release] [![GitHub Status][]][GitHub] [![Discord][discord-badge]][discord-url]
|
||||
|
||||
<img align="right" src="doc/logo.png" alt="Infix — Immutable.Friendly.Secure" width=480 padding=10>
|
||||
<img align="right" src="doc/logo.png" alt="Infix — Immutable.Friendly.Secure" width=380 padding=10>
|
||||
|
||||
Turn any ARM or x86 device into a powerful, manageable network appliance
|
||||
in minutes. From $35 Raspberry Pi boards to enterprise switches — deploy
|
||||
routers, IoT gateways, edge devices, or custom network solutions that
|
||||
just work.
|
||||
Infix turns an ARM or x86 device into a managed network appliance. The
|
||||
same OS runs on a $35 Raspberry Pi and on enterprise switching hardware,
|
||||
so you can build a router, an IoT gateway, or an edge device on whatever
|
||||
you have on hand.
|
||||
|
||||
## Our Values
|
||||
More in-depth material is available in our blog and User Guide:
|
||||
|
||||
**🔒 Immutable**
|
||||
Your system never breaks. Read-only filesystem with atomic upgrades
|
||||
means no configuration drift, no corrupted updates, and instant rollback
|
||||
if something goes wrong. Deploy once, trust forever.
|
||||
- <https://www.kernelkit.org/>
|
||||
- <https://www.kernelkit.org/infix/>
|
||||
|
||||
**🤝 Friendly**
|
||||
Actually easy to use. Auto-generated CLI from standard YANG models comes
|
||||
with built-in help for every command — just hit <kbd>?</kbd> or
|
||||
<kbd>TAB</kbd> for context-aware assistance.
|
||||
## See it in action
|
||||
|
||||
Familiar NETCONF & RESTCONF APIs and [comprehensive documentation][4]
|
||||
mean you're never stuck. Whether you're learning networking or managing
|
||||
enterprise infrastructure.
|
||||
|
||||
**🛡️ Secure**
|
||||
Built with security as a foundation, not an afterthought. Minimal
|
||||
attack surface, separation between system and data, and container
|
||||
isolation. Sleep better knowing your infrastructure is protected.
|
||||
|
||||
## Why Choose Infix
|
||||
|
||||
**Hardware Flexibility**: Start with a $35 Raspberry Pi, scale to
|
||||
enterprise switching hardware. Same OS, same tools, same reliability.
|
||||
|
||||
**Standards-Based**: Built around YANG models and IETF standards. Learn
|
||||
once, use everywhere - no vendor lock-in.
|
||||
|
||||
**Container Ready**: Run your applications alongside networking
|
||||
functions. GPIO access, dedicated Ethernet ports, custom protocols —
|
||||
your device, your rules.
|
||||
|
||||
## Use Cases
|
||||
|
||||
1. **Home Labs & Hobbyists**:
|
||||
Transform a Raspberry Pi into a full-featured router with WiFi
|
||||
1. **IoT & Edge Computing**:
|
||||
Bridge devices to the cloud with reliable, updatable gateways
|
||||
1. **Small Business Networks**:
|
||||
Enterprise-grade features without the complexity or cost
|
||||
1. **Developers & Makers**:
|
||||
Test networking concepts, prototype IoT solutions, or build custom
|
||||
appliances
|
||||
1. **Network Professionals**:
|
||||
Consistent tooling from development to production deployment.
|
||||
How about a digital twin using raw Qemu or [GNS3](https://gns3.com/infix)!
|
||||
|
||||
## Quick Example
|
||||
|
||||
Configure an interface in seconds - the CLI guides you with built-in help:
|
||||
The CLI is generated from the [YANG models][inside], so it guides you with
|
||||
built-in help. Here's setting an IP address on an interface:
|
||||
|
||||
<pre><code>admin@infix-12-34-56:/> <b>configure</b>
|
||||
admin@infix-12-34-56:/config/> <b>edit interface eth0</b>
|
||||
@@ -94,38 +52,94 @@ eth0 ethernet UP 52:54:00:12:34:56
|
||||
admin@infix-12-34-56:/> <b>copy running startup</b>
|
||||
</code></pre>
|
||||
|
||||
Notice how <kbd>TAB</kbd> completion shows available options, `show`
|
||||
displays current config, and `diff` shows exactly what changed before
|
||||
you commit your changes with the `leave` command.
|
||||
<kbd>TAB</kbd> completes available options and <kbd>?</kbd> shows online help
|
||||
for each option and argument. `show` displays the current config, and `diff`
|
||||
shows exactly what changed before you commit it with `leave`. See the [CLI
|
||||
documentation][3] for more.
|
||||
|
||||
For more information, see [CLI documentation][3].
|
||||
## Web interface
|
||||
|
||||
## Get Started
|
||||
If the CLI isn't your style, the same configuration is available through the
|
||||
web interface. Log in from a browser, keep an eye on your device from the
|
||||
Status dashboard and use the Configure > Interface setup wizard to create more
|
||||
advanced setups, or just fold out an interface to add an IP address.
|
||||
|
||||
Get [pre-built images][5] for your hardware. Use the CLI, web
|
||||
interface, or standard NETCONF/RESTCONF tools, e.g., `curl`. Add
|
||||
containers for any custom functionality you need.
|
||||
<p>
|
||||
<a href="doc/img/webui-login.png"><img src="doc/img/webui-login.png" alt="Login" align="top" width=220></a>
|
||||
<a href="doc/img/webui-dashboard.png"><img src="doc/img/webui-dashboard.png" alt="Dashboard" width=290></a>
|
||||
<a href="doc/img/webui-wizard.png"><img src="doc/img/webui-wizard.png" alt="Setup wizard" width=260></a>
|
||||
</p>
|
||||
|
||||
### Supported Platforms
|
||||
The web interface is built on the same concepts as the CLI, so operational
|
||||
status and state are kept separate from configuration and commands.
|
||||
|
||||
- **Raspberry Pi 2B/3B/4B/CM4** - Perfect for home labs, learning, and prototyping
|
||||
- **Banana Pi-R3** - Your next home router and gateway
|
||||
- **NanoPi R2S** - Compact dual-port router in a tiny package
|
||||
- **x86_64** - Run in VMs or on mini PCs for development and testing
|
||||
- **Marvell CN9130 CRB, EspressoBIN** - High-performance ARM64 platforms
|
||||
- **Microchip SparX-5i** - Enterprise switching capabilities
|
||||
- **Microchip SAMA7G54-EK** - ARM Cortex-A7
|
||||
- **NXP i.MX8MP EVK** - Highly capable ARM64 SoC
|
||||
- **StarFive VisionFive2** - RISC-V architecture support
|
||||
## Try it in 5 minutes
|
||||
|
||||
*Why start with Raspberry Pi?* It's affordable, widely available, has
|
||||
built-in WiFi + Ethernet, and runs the exact same Infix OS you'd deploy
|
||||
in production. Perfect for learning, prototyping, or even small-scale
|
||||
deployments.
|
||||
You don't need hardware to get started:
|
||||
|
||||
- **In a virtual lab** — run a full topology in [GNS3][gns3-post] and test
|
||||
networks entirely in software.
|
||||
- **From source** — [build it and `make run`][build-post] to boot Infix in
|
||||
QEMU, from `git clone` to pinging the internet.
|
||||
- **On real hardware** — grab a [pre-built image][5] for your board, or run
|
||||
the `x86_64` image in any VM.
|
||||
|
||||
Log in with `admin` / `admin` on the virtual and pre-built images. On
|
||||
shipped products the factory-reset credentials are customizable — we
|
||||
typically provision a unique per-device password stored in EEPROM/VPD.
|
||||
|
||||
## Supported hardware
|
||||
|
||||
- **Raspberry Pi 2B/3B/4B/CM4** - a good starting point; built-in WiFi and Ethernet
|
||||
- **Banana Pi-R64/R3/R3 Mini/R4** - multi-port routers and gateways
|
||||
- **NanoPi R2S** - compact dual-port router
|
||||
- **x86_64** - VMs and mini PCs, for development or production
|
||||
- **Marvell CN9130 CRB, EspressoBIN** - ARM64 development boards
|
||||
- **Microchip SparX-5i** - enterprise switching
|
||||
- **Microchip SAMA7G54-EK** - ARM Cortex-A7 evaluation kit
|
||||
- **NXP i.MX8MP EVK** - ARM64 SoC evaluation kit
|
||||
- **StarFive VisionFive2** - RISC-V board
|
||||
|
||||
*Why start with Raspberry Pi?* It's cheap, easy to get hold of, has
|
||||
built-in WiFi and Ethernet, and runs the same Infix you'd deploy in
|
||||
production — so what you learn on it carries straight over.
|
||||
|
||||
> [!TIP]
|
||||
> 📖 **[Complete documentation][4]** • 💬 **[Join our Discord][discord-url]**
|
||||
|
||||
## Technical Details
|
||||
## Why Infix
|
||||
|
||||
**🔒 Immutable**
|
||||
Read-only filesystem with atomic upgrades. An update either applies
|
||||
cleanly or rolls back, so a failed upgrade or a power cut midway through
|
||||
won't leave you with a half-broken system.
|
||||
|
||||
**🤝 Friendly**
|
||||
The CLI is generated from the YANG models, so every command carries its
|
||||
own help — hit <kbd>?</kbd> or <kbd>TAB</kbd> to see what's available.
|
||||
The same models are reachable over NETCONF and RESTCONF, with
|
||||
[documentation][4] for when you get stuck.
|
||||
|
||||
**🛡️ Secure**
|
||||
A small attack surface, separation between system and data, and
|
||||
container isolation. Since the system partition is read-only, a
|
||||
compromised service or container can't rewrite the OS underneath it.
|
||||
|
||||
## Use cases
|
||||
|
||||
1. **Home labs & hobbyists**:
|
||||
Turn a Raspberry Pi into a router with WiFi
|
||||
1. **IoT & edge**:
|
||||
Build gateways you can update in the field
|
||||
1. **Small business networks**:
|
||||
Routing, firewalling, and VLANs on affordable hardware
|
||||
1. **Developers & makers**:
|
||||
Prototype networking ideas, or build a custom appliance with containers
|
||||
1. **Network professionals**:
|
||||
The same tooling from lab to production — spin up a digital twin in raw
|
||||
Qemu or [GNS3](https://gns3.com/infix)
|
||||
|
||||
## Under the hood
|
||||
|
||||
<a href="https://bitsign.se">
|
||||
<picture>
|
||||
@@ -135,27 +149,29 @@ deployments.
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
Built on proven open-source foundations: [Linux][0], [Buildroot][1], and
|
||||
[sysrepo][2] — for reliability you can trust:
|
||||
Built on [Linux][0], [Buildroot][1], and [sysrepo][2]:
|
||||
|
||||
- **Immutable OS**: Read-only filesystem, atomic updates, instant rollback
|
||||
- **YANG Configuration**: Industry-standard models with auto-generated tooling
|
||||
- **Hardware Acceleration**: Linux switchdev support for wire-speed packet processing
|
||||
- **Container Integration**: Docker support with flexible network and hardware access
|
||||
- **Memory Efficient**: Runs comfortably on devices with as little as 256 MB RAM
|
||||
- **Code Signing**: Releases are cryptographically signed for integrity verification
|
||||
- **Immutable OS**: read-only filesystem, atomic updates, rollback on failure
|
||||
- **YANG configuration**: standard models with an auto-generated CLI and APIs
|
||||
- **Hardware acceleration**: switchdev offload for wire-speed forwarding
|
||||
- **Container integration**: Docker, with access to host network and hardware
|
||||
- **Memory efficient**: runs on devices with as little as 256 MB RAM
|
||||
- **Code signing**: releases are cryptographically signed
|
||||
|
||||
Perfect for everything from resource-constrained edge devices to
|
||||
high-throughput network appliances.
|
||||
|
||||
With the entire system modeled in YANG, scalability is no longer an
|
||||
issue, be it in development, testing, or end users deploying and
|
||||
monitoring their devices. All knobs and dials are accessible from the
|
||||
CLI (console/SSH), or remotely using the native NETCONF or RESTCONF
|
||||
APIs.
|
||||
Because the whole system is modeled in YANG, every setting is reachable
|
||||
the same way: from the CLI over console or SSH, or remotely over the
|
||||
native NETCONF and RESTCONF APIs. The same models drive development,
|
||||
testing, and day-to-day monitoring.
|
||||
|
||||
> Check the *[Latest Build][]* for bleeding-edge features.
|
||||
|
||||
## Contributing
|
||||
|
||||
Bug reports, ideas, and pull requests are welcome. Start with
|
||||
[CONTRIBUTING][contributing] and the [code of conduct][coc]. Found a
|
||||
security issue? Follow the [security policy][security]. Need a hand?
|
||||
See [support options][support] or [join us on Discord][discord-url].
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
@@ -171,6 +187,13 @@ APIs.
|
||||
[3]: https://www.kernelkit.org/infix/latest/cli/introduction/
|
||||
[4]: https://www.kernelkit.org/infix/
|
||||
[5]: https://github.com/kernelkit/infix/releases/latest
|
||||
[inside]: https://www.kernelkit.org/posts/inside-infix/
|
||||
[gns3-post]: https://www.kernelkit.org/posts/infix-in-gns3/
|
||||
[build-post]: https://www.kernelkit.org/posts/building-infix-from-source/
|
||||
[contributing]: .github/CONTRIBUTING.md
|
||||
[coc]: .github/CODE_OF_CONDUCT.md
|
||||
[security]: .github/SECURITY.md
|
||||
[support]: .github/SUPPORT.md
|
||||
[Latest Build]: https://github.com/kernelkit/infix/releases/tag/latest "Latest build"
|
||||
[License]: https://en.wikipedia.org/wiki/GPL_license
|
||||
[License Badge]: https://img.shields.io/badge/License-GPL%20v2-blue.svg
|
||||
|
||||
@@ -30,6 +30,8 @@ All notable changes to the project are documented in this file.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix #941: a VETH pair can now connect two containers directly, with both
|
||||
ends assigned to containers.
|
||||
- Enabling IP masquerading in the firewall no longer enables IP forwarding on
|
||||
all interfaces. This has been an issue ever since the firewall support was
|
||||
introduced in v25.10.0
|
||||
|
||||
+3
-5
@@ -668,11 +668,9 @@ set:
|
||||
|
||||
For an example of both, see the next section.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **VETH Pair Limitation:** When using VETH pairs with containers, at least
|
||||
> one side of the pair must remain in the host namespace. It is currently
|
||||
> not possible to create VETH pairs where both ends are assigned to different
|
||||
> containers. One end must always be accessible from the host.
|
||||
> [!TIP]
|
||||
> Both ends of a VETH pair may be assigned to containers, connecting two
|
||||
> containers directly without involving the host namespace.
|
||||
|
||||
[^3]: Something which the container bridge network type does behind the
|
||||
scenes with one end of an automatically created VETH pair.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
@@ -544,6 +544,13 @@ def mdns(args: List[str]) -> None:
|
||||
cli_pretty(data, "show-mdns")
|
||||
|
||||
|
||||
# Sensor names that represent the SoC/CPU temperature (not per-port PHYs).
|
||||
# Matches "cpu"/"soc"/"core", and Marvell CN913x "ap-*" / "cp<N>-*" zones.
|
||||
# Note the hyphen after "cp<N>" so mangled PHY names like "cp0busbus…" never
|
||||
# match.
|
||||
SOC_TEMP_RE = re.compile(r'^(cpu|soc|core|ap-|cp\d+-)')
|
||||
|
||||
|
||||
def system(args: List[str]) -> None:
|
||||
# Get system state from sysrepo
|
||||
data = get_json("/ietf-system:system-state")
|
||||
@@ -562,6 +569,7 @@ def system(args: List[str]) -> None:
|
||||
fan_rpm = None
|
||||
if hardware_data and "ietf-hardware:hardware" in hardware_data:
|
||||
components = hardware_data.get("ietf-hardware:hardware", {}).get("component", [])
|
||||
soc_temps = []
|
||||
for component in components:
|
||||
sensor_data = component.get("sensor-data", {})
|
||||
if not sensor_data:
|
||||
@@ -570,16 +578,21 @@ def system(args: List[str]) -> None:
|
||||
name = component.get("name", "")
|
||||
value_type = sensor_data.get("value-type")
|
||||
|
||||
# Only capture CPU/SoC temperature (ignore phy, sfp, etc.)
|
||||
# Different platforms use different names: cpu, soc, core, etc.
|
||||
if value_type == "celsius" and name in ("cpu", "soc", "core") and cpu_temp is None:
|
||||
temp_millidegrees = sensor_data.get("value", 0)
|
||||
cpu_temp = temp_millidegrees / 1000.0
|
||||
# Capture SoC/CPU temperature, ignoring per-port phy, sfp, etc.
|
||||
# Platforms name the zone differently: a plain "cpu"/"soc"/"core",
|
||||
# or, on Marvell CN913x, an "ap-*" (application processor) or
|
||||
# "cp<N>-*" (communication processor) cluster. Collect them all
|
||||
# and report the hottest as the representative SoC temperature.
|
||||
if value_type == "celsius" and SOC_TEMP_RE.match(name):
|
||||
soc_temps.append(sensor_data.get("value", 0) / 1000.0)
|
||||
|
||||
# Capture fan speed if available
|
||||
elif value_type == "rpm" and fan_rpm is None:
|
||||
fan_rpm = sensor_data.get("value", 0)
|
||||
|
||||
if soc_temps:
|
||||
cpu_temp = max(soc_temps)
|
||||
|
||||
if cpu_temp is not None:
|
||||
runtime["cpu_temp"] = cpu_temp
|
||||
if fan_rpm is not None:
|
||||
|
||||
@@ -414,6 +414,16 @@ int cni_netdag_gen_iface(struct dagger *net, const char *ifname,
|
||||
return -EIO;
|
||||
|
||||
fprintf(fp, "container -a -f delete network %s >/dev/null\n", ifname);
|
||||
|
||||
/* If this end belongs to a veth pair, the kernel keeps the pair
|
||||
* alive after CNI host-device returns the interface to the host
|
||||
* namespace. Remove it here, once the container is gone, so the
|
||||
* pair does not linger and block a later re-creation. Tolerant:
|
||||
* the peer's teardown may already have removed it.
|
||||
*/
|
||||
if (lydx_get_child(dif, "veth"))
|
||||
fprintf(fp, "ip link del dev %s 2>/dev/null || true\n", ifname);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
if (cni_type == IFT_BRIDGE)
|
||||
|
||||
+15
-10
@@ -21,23 +21,28 @@
|
||||
bool veth_is_primary(struct lyd_node *cif)
|
||||
{
|
||||
struct lyd_node *peer, *veth;
|
||||
bool self_cni, peer_cni;
|
||||
const char *peername;
|
||||
|
||||
veth = lydx_get_child(cif, "veth");
|
||||
peername = lydx_get_cattr(veth, "peer");
|
||||
peer = lydx_find_by_name(lyd_parent(cif), "interface", peername);
|
||||
|
||||
/* At the moment, CNI code relies on one side of the pair
|
||||
* remaining in the host namespace, and that that interface
|
||||
* takes care of creating the pair.
|
||||
*/
|
||||
if (lydx_get_child(cif, "container-network"))
|
||||
return false;
|
||||
if (lydx_get_child(peer, "container-network"))
|
||||
return true;
|
||||
self_cni = lydx_get_child(cif, "container-network") != NULL;
|
||||
peer_cni = lydx_get_child(peer, "container-network") != NULL;
|
||||
|
||||
return strcmp(lydx_get_cattr(cif, "name"),
|
||||
lydx_get_cattr(veth, "peer")) < 0;
|
||||
/* When exactly one end is handed to a container (CNI host-device),
|
||||
* the other end stays in the host namespace and creates the pair.
|
||||
*/
|
||||
if (self_cni != peer_cni)
|
||||
return peer_cni;
|
||||
|
||||
/* Neither or both ends are container interfaces: pick a stable
|
||||
* primary by name so exactly one end creates the pair. When both
|
||||
* ends are containers the pair is still created in the host
|
||||
* namespace first, then moved into each container by CNI host-device.
|
||||
*/
|
||||
return strcmp(lydx_get_cattr(cif, "name"), peername) < 0;
|
||||
}
|
||||
|
||||
int ifchange_cand_infer_veth(sr_session_ctx_t *session, const char *path)
|
||||
|
||||
@@ -518,6 +518,14 @@ static int veth_gen_del(struct lyd_node *dif, FILE *sh)
|
||||
if (!veth_is_primary(dif))
|
||||
return 0;
|
||||
|
||||
/* When the primary end is itself a container interface it currently
|
||||
* lives in the container's namespace, so a host-namespace delete here
|
||||
* would fail and abort the teardown. Its removal is handled after the
|
||||
* container is gone, see cni_netdag_gen_iface().
|
||||
*/
|
||||
if (lydx_get_child(dif, "container-network"))
|
||||
return 0;
|
||||
|
||||
return link_gen_del(dif, sh);
|
||||
}
|
||||
|
||||
@@ -571,6 +579,28 @@ static int netdag_gen_iface_del(struct dagger *net, struct lyd_node *dif,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Both ends of a veth pair can be handed to containers, leaving no
|
||||
* host-side interface to create the pair. Have the primary end create it
|
||||
* in the host namespace early (NETDAG_INIT_PHYS, before the container is
|
||||
* (re)started); CNI host-device then moves each end into its container.
|
||||
*/
|
||||
static int veth_gen_host(struct dagger *net, struct lyd_node *dif, struct lyd_node *cif)
|
||||
{
|
||||
const char *ifname = lydx_get_cattr(cif, "name");
|
||||
FILE *ip;
|
||||
int err;
|
||||
|
||||
ip = dagger_fopen_net_init(net, ifname, NETDAG_INIT_PHYS, "init.ip");
|
||||
if (!ip)
|
||||
return -EIO;
|
||||
|
||||
err = veth_gen(dif, cif, ip);
|
||||
fclose(ip);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static sr_error_t netdag_gen_iface_timeout(struct dagger *net, const char *ifname, const char *iftype)
|
||||
{
|
||||
if (!strcmp(iftype, "infix-if-type:ethernet")) {
|
||||
@@ -604,8 +634,13 @@ static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net
|
||||
|
||||
if ((err = cni_netdag_gen_iface(net, ifname, dif, cif))) {
|
||||
/* error or managed by CNI/podman */
|
||||
if (err > 0)
|
||||
if (err > 0) {
|
||||
err = 0; /* done, nothing more to do here */
|
||||
|
||||
if (op == LYDX_OP_CREATE && lydx_get_child(cif, "veth") &&
|
||||
veth_is_primary(cif))
|
||||
err = veth_gen_host(net, dif, cif);
|
||||
}
|
||||
goto err;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,11 +59,7 @@ submodule infix-if-container {
|
||||
|
||||
identity host {
|
||||
base container-network;
|
||||
description "Host device, e.g., one end of a VETH pair or other host interface.
|
||||
|
||||
Note: When using VETH pairs, at least one side must remain in the
|
||||
host namespace. Both ends of a VETH pair cannot be assigned to
|
||||
different containers.";
|
||||
description "Host device, e.g., one end of a VETH pair or other host interface.";
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -13,11 +13,7 @@ submodule infix-if-veth {
|
||||
|
||||
organization "KernelKit";
|
||||
contact "kernelkit@googlegroups.com";
|
||||
description "Linux virtual Ethernet pair extension for ietf-interfaces.
|
||||
|
||||
Note: When using VETH pairs with containers, at least one side
|
||||
of the pair must remain in the host namespace. Both ends of a
|
||||
VETH pair cannot be assigned to different containers.";
|
||||
description "Linux virtual Ethernet pair extension for ietf-interfaces.";
|
||||
|
||||
revision 2023-06-05 {
|
||||
description "Initial revision.";
|
||||
|
||||
@@ -916,7 +916,12 @@ class Sensor:
|
||||
# Standalone sensor without description: use name as-is
|
||||
display_name = self.name
|
||||
|
||||
row = f"{indent_str}{display_name:<{PadSensor.name - len(indent_str)}}"
|
||||
# Truncate over-long names so they never spill into the VALUE column
|
||||
# (e.g. unmapped switch-PHY hwmon names derived from the DT path).
|
||||
field = PadSensor.name - len(indent_str)
|
||||
if len(display_name) >= field:
|
||||
display_name = display_name[:field - 2] + "…"
|
||||
row = f"{indent_str}{display_name:<{field}}"
|
||||
# For colored value, pad manually to account for ANSI codes
|
||||
value_str = self.get_formatted_value()
|
||||
# Count visible characters (strip ANSI codes for length calculation)
|
||||
@@ -2245,6 +2250,13 @@ def show_services(json):
|
||||
service_table.print()
|
||||
|
||||
|
||||
def sensor_sort_key(component):
|
||||
"""Natural sort key for sensor names: digit runs compare numerically so
|
||||
e2 sorts before e10, while keeping ap-cpu/cp0-ic/sfp groups together."""
|
||||
name = component.get("name", "")
|
||||
return [int(t) if t.isdigit() else t for t in re.split(r'(\d+)', name)]
|
||||
|
||||
|
||||
def show_hardware(json):
|
||||
if not json.get("ietf-hardware:hardware"):
|
||||
print("Error, top level \"ietf-hardware:component\" missing")
|
||||
@@ -2418,15 +2430,16 @@ def show_hardware(json):
|
||||
print(f"\n{module_name}:")
|
||||
|
||||
if module_name in children:
|
||||
for child in sorted(children[module_name], key=lambda c: c.get("name", "")):
|
||||
for child in sorted(children[module_name], key=sensor_sort_key):
|
||||
sensor = Sensor(child)
|
||||
sensor.print(indent=1)
|
||||
|
||||
# Display standalone sensors (no parent)
|
||||
# Display standalone sensors (no parent), naturally sorted so port
|
||||
# temperatures read e1, e2, ... e28 rather than e1, e10, e11, ...
|
||||
if standalone:
|
||||
if modules:
|
||||
print() # Add blank line between modules and standalone
|
||||
for component in sorted(standalone, key=lambda c: c.get("name", "")):
|
||||
for component in sorted(standalone, key=sensor_sort_key):
|
||||
sensor = Sensor(component)
|
||||
sensor.print()
|
||||
|
||||
|
||||
@@ -149,6 +149,36 @@ def normalize_sensor_name(name):
|
||||
return name
|
||||
|
||||
|
||||
def _dt_phandle(path):
|
||||
"""Read a device-tree phandle cell as a normalized hex string.
|
||||
|
||||
phandle/phy-handle properties are 4-byte big-endian cells. Read them via
|
||||
od(1) so the binary content survives the text-based HOST transport (works
|
||||
both locally and over the ssh-style remote transport).
|
||||
"""
|
||||
out = HOST.run(("od", "-An", "-tx1", path), default="")
|
||||
return "".join(out.split()) if out else None
|
||||
|
||||
|
||||
def phy_handle_to_ifname():
|
||||
"""Map a PHY's device-tree phandle to the interface it drives.
|
||||
|
||||
DSA user ports carry a "phy-handle" pointing at the PHY that serves them.
|
||||
The reverse map lets us name a switch PHY's hwmon temperature sensor after
|
||||
the front-panel port (e.g. e1) instead of the unreadable name the kernel
|
||||
derives from the full device-tree path (cp0busbusf2000000mdio...).
|
||||
"""
|
||||
mapping = {}
|
||||
for ifname in HOST.run(("ls", "/sys/class/net"), default="").split():
|
||||
handle_path = os.path.join("/sys/class/net", ifname, "of_node", "phy-handle")
|
||||
if not HOST.exists(handle_path):
|
||||
continue
|
||||
handle = _dt_phandle(handle_path)
|
||||
if handle:
|
||||
mapping[handle] = ifname
|
||||
return mapping
|
||||
|
||||
|
||||
def get_wifi_phy_info():
|
||||
"""
|
||||
Discover WiFi PHYs using iw list command.
|
||||
@@ -218,6 +248,7 @@ def hwmon_sensor_components():
|
||||
"""
|
||||
components = []
|
||||
device_sensors = {} # Track {device_base_name: [list of sensor components]}
|
||||
phy_ifname = phy_handle_to_ifname()
|
||||
|
||||
def add_sensor(base_name, sensor_component):
|
||||
"""Helper to track sensors per device"""
|
||||
@@ -244,6 +275,16 @@ def hwmon_sensor_components():
|
||||
|
||||
base_name = normalize_sensor_name(device_name)
|
||||
|
||||
# Switch PHYs get an hwmon name derived from their full
|
||||
# device-tree path (e.g. cp0busbusf2000000mdio12a200switch2mdio01).
|
||||
# If this PHY drives a known port, name the sensor after that
|
||||
# port (e1, e2, ...) instead.
|
||||
phandle_path = os.path.join(hwmon_path, "device", "of_node", "phandle")
|
||||
if HOST.exists(phandle_path):
|
||||
ifname = phy_ifname.get(_dt_phandle(phandle_path))
|
||||
if ifname:
|
||||
base_name = ifname
|
||||
|
||||
# Helper to create sensor component with human-readable description
|
||||
def create_sensor(sensor_name, value, value_type, value_scale, label=None):
|
||||
component = {
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
- name: Container with VETH Pair
|
||||
case: veth/test.py
|
||||
|
||||
- name: VETH Pair Between Two Containers
|
||||
case: internal_link/test.py
|
||||
|
||||
- name: Container Volume Persistence
|
||||
case: volume/test.py
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
test.adoc
|
||||
@@ -0,0 +1,33 @@
|
||||
=== VETH Pair Between Two Containers
|
||||
|
||||
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/containers/internal_link]
|
||||
|
||||
==== Description
|
||||
|
||||
Verify that a VETH pair can connect two containers directly, with *both*
|
||||
ends handed to containers and neither remaining in the host namespace.
|
||||
|
||||
....
|
||||
.------------. .------------.
|
||||
| left | | right |
|
||||
| veth0a ===|========= veth ===========|=== veth0b |
|
||||
'------------' 10.0.0.1 10.0.0.2 '------------'
|
||||
....
|
||||
|
||||
The pair is created in the host namespace then each end is moved into
|
||||
its container when starting up. Connectivity is verified by pinging
|
||||
across the pair, from inside one container's network namespace to the
|
||||
other end's address.
|
||||
|
||||
==== Topology
|
||||
|
||||
image::topology.svg[VETH Pair Between Two Containers topology, align=center, scaledwidth=75%]
|
||||
|
||||
==== Sequence
|
||||
|
||||
. Set up topology and attach to target DUT
|
||||
. Create VETH pair with both ends assigned to containers
|
||||
. Verify both containers have started
|
||||
. Verify {LEFT} reaches {RIGHT} over the internal VETH pair
|
||||
|
||||
|
||||
Executable
+101
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
r"""VETH Pair Between Two Containers
|
||||
|
||||
Verify that a VETH pair can connect two containers directly, with *both*
|
||||
ends handed to containers and neither remaining in the host namespace.
|
||||
|
||||
....
|
||||
.------------. .------------.
|
||||
| left | | right |
|
||||
| veth0a ===|========= veth ===========|=== veth0b |
|
||||
'------------' 10.0.0.1 10.0.0.2 '------------'
|
||||
....
|
||||
|
||||
The pair is created in the host namespace then each end is moved into
|
||||
its container when starting up. Connectivity is verified by pinging
|
||||
across the pair, from inside one container's network namespace to the
|
||||
other end's address.
|
||||
|
||||
"""
|
||||
|
||||
import infamy
|
||||
from infamy.util import until
|
||||
|
||||
# Regression test for #941: previously, when both ends of a pair were
|
||||
# assigned to a container, neither side created the pair.
|
||||
with infamy.Test() as test:
|
||||
LEFT, IFACE_LEFT, IP_LEFT = "left", "veth0a", "10.0.0.1"
|
||||
RIGHT, IFACE_RIGHT, IP_RIGHT = "right", "veth0b", "10.0.0.2"
|
||||
IMAGE = f"oci-archive:{infamy.Container.HTTPD_IMAGE}"
|
||||
|
||||
with test.step("Set up topology and attach to target DUT"):
|
||||
env = infamy.Env()
|
||||
target = env.attach("target", "mgmt")
|
||||
tgtssh = env.attach("target", "mgmt", "ssh")
|
||||
|
||||
if not target.has_model("infix-containers"):
|
||||
test.skip()
|
||||
|
||||
with test.step("Create VETH pair with both ends assigned to containers"):
|
||||
target.put_config_dicts({
|
||||
"ietf-interfaces": {
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"name": IFACE_LEFT,
|
||||
"type": "infix-if-type:veth",
|
||||
"enabled": True,
|
||||
"infix-interfaces:veth": {"peer": IFACE_RIGHT},
|
||||
"ipv4": {
|
||||
"address": [{"ip": IP_LEFT, "prefix-length": 24}]
|
||||
},
|
||||
"container-network": {}
|
||||
},
|
||||
{
|
||||
"name": IFACE_RIGHT,
|
||||
"type": "infix-if-type:veth",
|
||||
"enabled": True,
|
||||
"infix-interfaces:veth": {"peer": IFACE_LEFT},
|
||||
"ipv4": {
|
||||
"address": [{"ip": IP_RIGHT, "prefix-length": 24}]
|
||||
},
|
||||
"container-network": {}
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
"infix-containers": {
|
||||
"containers": {
|
||||
"container": [
|
||||
{
|
||||
"name": LEFT,
|
||||
"image": IMAGE,
|
||||
"command": "/usr/sbin/httpd -f -v -p 91",
|
||||
"network": {"interface": [{"name": IFACE_LEFT}]}
|
||||
},
|
||||
{
|
||||
"name": RIGHT,
|
||||
"image": IMAGE,
|
||||
"command": "/usr/sbin/httpd -f -v -p 91",
|
||||
"network": {"interface": [{"name": IFACE_RIGHT}]}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
c = infamy.Container(target)
|
||||
with test.step("Verify both containers have started"):
|
||||
until(lambda: c.running(LEFT), attempts=60)
|
||||
until(lambda: c.running(RIGHT), attempts=60)
|
||||
|
||||
with test.step(f"Verify {LEFT} reaches {RIGHT} over the internal VETH pair"):
|
||||
pid = tgtssh.runsh(f"sudo podman inspect --format '{{{{.State.Pid}}}}' {LEFT}").stdout.strip()
|
||||
assert pid.isdigit(), f"failed to get pid for container {LEFT}: {pid!r}"
|
||||
|
||||
def reachable():
|
||||
return tgtssh.runsh(f"sudo nsenter -t {pid} -n ping -c 2 -w 5 {IP_RIGHT}").returncode == 0
|
||||
|
||||
until(reachable, attempts=30)
|
||||
|
||||
test.succeed()
|
||||
@@ -0,0 +1 @@
|
||||
../../../infamy/topologies/1x1.dot
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<!-- Title: 1x1 Pages: 1 -->
|
||||
<svg width="424pt" height="45pt"
|
||||
viewBox="0.00 0.00 424.03 45.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 41)">
|
||||
<title>1x1</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-41 420.03,-41 420.03,4 -4,4"/>
|
||||
<!-- host -->
|
||||
<g id="node1" class="node">
|
||||
<title>host</title>
|
||||
<polygon fill="none" stroke="black" points="0,-0.5 0,-36.5 100,-36.5 100,-0.5 0,-0.5"/>
|
||||
<text text-anchor="middle" x="25" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||
<polyline fill="none" stroke="black" points="50,-0.5 50,-36.5 "/>
|
||||
<text text-anchor="middle" x="75" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
</g>
|
||||
<!-- target -->
|
||||
<g id="node2" class="node">
|
||||
<title>target</title>
|
||||
<polygon fill="none" stroke="black" points="300.03,-0.5 300.03,-36.5 416.03,-36.5 416.03,-0.5 300.03,-0.5"/>
|
||||
<text text-anchor="middle" x="325.03" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="350.03,-0.5 350.03,-36.5 "/>
|
||||
<text text-anchor="middle" x="383.03" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
|
||||
</g>
|
||||
<!-- host--target -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt--target:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M100,-18.5C100,-18.5 300.03,-18.5 300.03,-18.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -22,10 +22,12 @@ with infamy.Test() as test:
|
||||
target = env.attach("target", "mgmt")
|
||||
_, data_if = env.ltop.xlate("target", "data")
|
||||
_, mgmt_if = env.ltop.xlate("target", "mgmt")
|
||||
_, unused_if = env.ltop.xlate("target", "unused")
|
||||
_, host_data = env.ltop.xlate("host", "data")
|
||||
TARGET_IP = "192.168.1.1"
|
||||
HOST_IP = "192.168.1.42"
|
||||
# A dummy interface stands in for an unused port: it is not placed
|
||||
# in any zone, so it must fall back to the default zone.
|
||||
UNUSED_IF = "dummy0"
|
||||
|
||||
with test.step("Configure basic end-device firewall"):
|
||||
target.put_config_dicts({
|
||||
@@ -41,6 +43,11 @@ with infamy.Test() as test:
|
||||
"prefix-length": 24
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": UNUSED_IF,
|
||||
"type": "infix-if-type:dummy",
|
||||
"enabled": True
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -119,16 +126,14 @@ with infamy.Test() as test:
|
||||
assert "http" in public_zone["service"]
|
||||
|
||||
with test.step("Verify unused interface assigned to default zone"):
|
||||
data = target.get_data("/infix-firewall:firewall")
|
||||
fw = data["firewall"]
|
||||
def unused_in_default_zone():
|
||||
data = target.get_data("/infix-firewall:firewall")
|
||||
fw = data["firewall"]
|
||||
assert fw["default"] == "public-untrusted-net", "Default zone should be 'public-untrusted-net'"
|
||||
zones = {zone["name"]: zone for zone in fw["zone"]}
|
||||
return UNUSED_IF in zones["public-untrusted-net"].get("interface", [])
|
||||
|
||||
assert fw["default"] == "public-untrusted-net", "Default zone should be 'public-untrusted-net'"
|
||||
|
||||
zones = {zone["name"]: zone for zone in fw["zone"]}
|
||||
public_zone = zones["public-untrusted-net"]
|
||||
|
||||
assert unused_if in public_zone["interface"], \
|
||||
f"Unused interface {unused_if} should be in default zone 'public-untrusted-net', got interfaces: {public_zone['interface']}"
|
||||
until(unused_in_default_zone, attempts=10)
|
||||
|
||||
with infamy.IsolatedMacVlan(host_data) as ns:
|
||||
ns.addip(HOST_IP)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
graph "1x3" {
|
||||
graph "1x2" {
|
||||
layout = "neato";
|
||||
overlap = false;
|
||||
esep = "+30";
|
||||
@@ -8,23 +8,16 @@ graph "1x3" {
|
||||
|
||||
host [
|
||||
label="host | { <mgmt> mgmt | <data> data }",
|
||||
pos="10,10.95!",
|
||||
pos="10,10!",
|
||||
requires="controller"
|
||||
];
|
||||
|
||||
target [
|
||||
label="{ <mgmt> mgmt | <data> data | <unused> unused } | target",
|
||||
pos="30,10!",
|
||||
requires="infix",
|
||||
];
|
||||
|
||||
dummy [
|
||||
label="{ <link> link } | dummy",
|
||||
pos="29.8,00!",
|
||||
label="{ <mgmt> mgmt | <data> data } | target",
|
||||
pos="40,10!",
|
||||
requires="infix",
|
||||
];
|
||||
|
||||
host:mgmt -- target:mgmt [requires="mgmt", color="lightgray"]
|
||||
host:data -- target:data [color=black, fontcolor=black, taillabel="192.168.1.42/24"]
|
||||
target:unused -- dummy:link [color="gray", style="dashed"]
|
||||
host:data -- target:data [color=black]
|
||||
}
|
||||
|
||||
@@ -2,57 +2,41 @@
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<!-- Title: 1x3 Pages: 1 -->
|
||||
<svg width="381pt" height="191pt"
|
||||
viewBox="0.00 0.00 381.03 190.72" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 186.72)">
|
||||
<title>1x3</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-186.72 377.03,-186.72 377.03,4 -4,4"/>
|
||||
<!-- Title: 1x2 Pages: 1 -->
|
||||
<svg width="299pt" height="55pt"
|
||||
viewBox="0.00 0.00 299.02 55.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 51)">
|
||||
<title>1x2</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-51 295.02,-51 295.02,4 -4,4"/>
|
||||
<!-- host -->
|
||||
<g id="node1" class="node">
|
||||
<title>host</title>
|
||||
<polygon fill="none" stroke="black" points="0,-136.22 0,-182.22 100,-182.22 100,-136.22 0,-136.22"/>
|
||||
<text text-anchor="middle" x="25" y="-155.52" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||
<polyline fill="none" stroke="black" points="50,-136.22 50,-182.22 "/>
|
||||
<text text-anchor="middle" x="75" y="-167.02" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="50,-159.22 100,-159.22 "/>
|
||||
<text text-anchor="middle" x="75" y="-144.02" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
|
||||
<polygon fill="none" stroke="black" points="0,-0.5 0,-46.5 100,-46.5 100,-0.5 0,-0.5"/>
|
||||
<text text-anchor="middle" x="25" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||
<polyline fill="none" stroke="black" points="50,-0.5 50,-46.5 "/>
|
||||
<text text-anchor="middle" x="75" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="50,-23.5 100,-23.5 "/>
|
||||
<text text-anchor="middle" x="75" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
|
||||
</g>
|
||||
<!-- target -->
|
||||
<g id="node2" class="node">
|
||||
<title>target</title>
|
||||
<polygon fill="none" stroke="black" points="241.03,-112.51 241.03,-181.51 373.03,-181.51 373.03,-112.51 241.03,-112.51"/>
|
||||
<text text-anchor="middle" x="274.03" y="-166.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="241.03,-158.51 307.03,-158.51 "/>
|
||||
<text text-anchor="middle" x="274.03" y="-143.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
|
||||
<polyline fill="none" stroke="black" points="241.03,-135.51 307.03,-135.51 "/>
|
||||
<text text-anchor="middle" x="274.03" y="-120.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">unused</text>
|
||||
<polyline fill="none" stroke="black" points="307.03,-112.51 307.03,-181.51 "/>
|
||||
<text text-anchor="middle" x="340.03" y="-143.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
|
||||
<polygon fill="none" stroke="black" points="175.02,-0.5 175.02,-46.5 291.02,-46.5 291.02,-0.5 175.02,-0.5"/>
|
||||
<text text-anchor="middle" x="200.02" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="175.02,-23.5 225.02,-23.5 "/>
|
||||
<text text-anchor="middle" x="200.02" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
|
||||
<polyline fill="none" stroke="black" points="225.02,-0.5 225.02,-46.5 "/>
|
||||
<text text-anchor="middle" x="258.02" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
|
||||
</g>
|
||||
<!-- host--target -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt--target:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M100,-171.22C100,-171.22 241.03,-170.01 241.03,-170.01"/>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M100,-35.5C100,-35.5 175.02,-35.5 175.02,-35.5"/>
|
||||
</g>
|
||||
<!-- host--target -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:data--target:data</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M100,-147.22C100,-147.22 241.03,-147.01 241.03,-147.01"/>
|
||||
<text text-anchor="middle" x="159" y="-151.02" font-family="DejaVu Serif, Book" font-size="14.00">192.168.1.42/24</text>
|
||||
</g>
|
||||
<!-- dummy -->
|
||||
<g id="node3" class="node">
|
||||
<title>dummy</title>
|
||||
<polygon fill="none" stroke="black" points="250.46,-0.5 250.46,-36.5 358.46,-36.5 358.46,-0.5 250.46,-0.5"/>
|
||||
<text text-anchor="middle" x="275.46" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
|
||||
<polyline fill="none" stroke="black" points="300.46,-0.5 300.46,-36.5 "/>
|
||||
<text text-anchor="middle" x="329.46" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">dummy</text>
|
||||
</g>
|
||||
<!-- target--dummy -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>target:unused--dummy:link</title>
|
||||
<path fill="none" stroke="gray" stroke-width="2" stroke-dasharray="5,2" d="M274.03,-112.01C274.03,-112.01 275.46,-36.5 275.46,-36.5"/>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M100,-11.5C100,-11.5 175.02,-11.5 175.02,-11.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 2.2 KiB |
Reference in New Issue
Block a user