mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-24 01:43:00 +02:00
Compare commits
48
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
257f1df1d5 | ||
|
|
8664c27096 | ||
|
|
c7c1b5b12d | ||
|
|
2f4cdd0d9c | ||
|
|
f1e7fda956 | ||
|
|
7bb48ece49 | ||
|
|
508e2287b8 | ||
|
|
03d48e9aab | ||
|
|
d7c41489e0 | ||
|
|
897faf08d9 | ||
|
|
3ad20dfd5b | ||
|
|
3f2ae3f3fc | ||
|
|
f94815cf46 | ||
|
|
2190c3bc52 | ||
|
|
e65625e32f | ||
|
|
af65bbe7f9 | ||
|
|
1dbe09b428 | ||
|
|
232c2329af | ||
|
|
772019db22 | ||
|
|
52c6c2274e | ||
|
|
e53f3362be | ||
|
|
c3945a996c | ||
|
|
c2d357a185 | ||
|
|
74b0679cf0 | ||
|
|
dc6dffbff9 | ||
|
|
4c6dfdf600 | ||
|
|
b62fd627f0 | ||
|
|
177b65587b | ||
|
|
b5679aa603 | ||
|
|
fb2bd2c2b7 | ||
|
|
b9907fa7ae | ||
|
|
7d6792a638 | ||
|
|
b6cf866f52 | ||
|
|
2c72aaf6e1 | ||
|
|
b5f2252650 | ||
|
|
ca026a37fc | ||
|
|
abd8621e92 | ||
|
|
0df778a5f6 | ||
|
|
0a7f8ce216 | ||
|
|
7e2d433857 | ||
|
|
413470569e | ||
|
|
b2adf639ff | ||
|
|
3ac1dfbf4c | ||
|
|
975a7b466c | ||
|
|
bdefe9452c | ||
|
|
9211fba6d5 | ||
|
|
1d58d2058d | ||
|
|
23dc237403 |
+36
-20
@@ -11,7 +11,7 @@ Talking about code and problems first is often the best way to get started
|
||||
before submitting a pull request.
|
||||
|
||||
When submitting a bug report, patch, or pull request, please start by
|
||||
stating the version the change is made against, what it does, and why.
|
||||
stating the version the change is made against, what it does, *and why*.
|
||||
|
||||
Please take care to ensure you follow the project coding style and the
|
||||
commit message format. If you follow these recommendations you help
|
||||
@@ -21,13 +21,28 @@ the maintainer(s) and make it easier for them to include your code.
|
||||
Coding Style
|
||||
------------
|
||||
|
||||
> **Tip:** Always submit code that follows the style of surrounding code!
|
||||
Before jumping into code, remember to **document new features** and bug
|
||||
fixes. Both the manual and ChangeLog are in the `doc/` sub-directory
|
||||
and it is expected that you provide a human-readable summary for the
|
||||
release notes (ChangeLog) and at least a configuration example in the
|
||||
manual for new features.
|
||||
|
||||
First of all, lines are allowed to be longer than 72 characters these
|
||||
days. In fact, there exist no enforced maximum, but keeping it around
|
||||
100 chars is OK.
|
||||
> **Tip:** consider ["Readme driven development"][RDD] for new features.
|
||||
> It is amazing how many flaws in your own bright ideas come to bare
|
||||
> when you suddenly have to explain them to someone else!
|
||||
|
||||
The coding style itself is otherwise strictly Linux [KNF][].
|
||||
We expect code contributions for:
|
||||
|
||||
- C code in [Linux Coding Style][Linux]
|
||||
- Python code should follow [PEP-8][]
|
||||
|
||||
> **However,** always submit code that follows the style of surrounding
|
||||
> code! Legacy takes precedence, and remember, we read code a lot more
|
||||
> than write it, so legibility is important.
|
||||
|
||||
As a final note, lines are allowed to be longer than 72 characters these
|
||||
days. There is no enforced maximum, but the team usually keep it around
|
||||
100 characters for both C and Python.
|
||||
|
||||
|
||||
Commit Messages
|
||||
@@ -37,22 +52,21 @@ Commit messages exist to track *why* a change was made. Try to be as
|
||||
clear and concise as possible in your commit messages, and always, be
|
||||
proud of your work and set up a proper GIT identity for your commits:
|
||||
|
||||
git config --global user.name "Jane Doe"
|
||||
git config --global user.email jane.doe@example.com
|
||||
$ git config --global user.name "Jacky Linker"
|
||||
$ git config --global user.email jacky.linker@example.com
|
||||
|
||||
Example commit message from the [Pro Git][gitbook] online book, notice
|
||||
how `git commit -s` is used to automatically add a `Signed-off-by`:
|
||||
|
||||
Brief, but clear and concise summary of changes
|
||||
subsystem: brief, but clear and concise summary of changes
|
||||
|
||||
More detailed explanatory text, if necessary. Wrap it to about 72
|
||||
characters or so. In some contexts, the first line is treated as
|
||||
the subject of an email and the rest of the text as the body. The
|
||||
blank line separating the ummary from the body is critical (unless
|
||||
you omit the body entirely); tools like rebase can get confused if
|
||||
you run the two together.
|
||||
empty line separating summary from body is critical. Tools like
|
||||
rebase can get confused if the empty line is missing.
|
||||
|
||||
Further paragraphs come after blank lines.
|
||||
Further paragraphs should be separated with empty lines.
|
||||
|
||||
- Bullet points are okay, too
|
||||
|
||||
@@ -60,17 +74,19 @@ how `git commit -s` is used to automatically add a `Signed-off-by`:
|
||||
by a single space, with blank lines in between, but conventions
|
||||
vary here
|
||||
|
||||
Signed-off-by: Jane Doe <jane.doe@example.com>
|
||||
Signed-off-by: Jacky Linker <jacky.linker@example.com>
|
||||
|
||||
|
||||
Code of Conduct
|
||||
---------------
|
||||
|
||||
It is expected of everyone to respect the [Code of Conduct][conduct].
|
||||
The *"maintainers have the right and responsibility to remove, edit,
|
||||
or reject comments, commits, code, wiki edits, issues, and other
|
||||
contributions that are not aligned to this Code of Conduct."*
|
||||
The *"maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, discussion forum threads, issues, and
|
||||
other contributions that are not aligned to this Code of Conduct."*
|
||||
|
||||
[KNF]: https://en.wikipedia.org/wiki/Kernel_Normal_Form
|
||||
[gitbook]: https://git-scm.com/book/ch5-2.html
|
||||
[conduct]: CODE-OF-CONDUCT.md
|
||||
[Linux]: https://www.kernel.org/doc/html/v6.9/process/coding-style.html
|
||||
[PEP-8]: https://peps.python.org/pep-0008/
|
||||
[RDD]: https://tom.preston-werner.com/2010/08/23/readme-driven-development
|
||||
[gitbook]: https://git-scm.com/book/ch5-2.html
|
||||
[conduct]: CODE-OF-CONDUCT.md
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
|
||||
name: "\U0001F41E Bug report"
|
||||
description: Create a bug report
|
||||
labels:
|
||||
- "bug"
|
||||
- "triage"
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Thank :heart: you for taking the time to fill out this bug report!**
|
||||
|
||||
We kindly ask that you search to see if an issue [already exists](https://github.com/kernelkit/infix/issues?q=is%3Aissue+sort%3Acreated-desc+) for the bug you encountered.
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current Behavior
|
||||
description: |
|
||||
A clear and concise description of the issue you're experiencing.
|
||||
value: |
|
||||
For code snippets, logs, commands, etc., please use triple backticks:
|
||||
```
|
||||
admin@infix-c0-ff-ee:/> show log
|
||||
May 15 07:21:02 infix-00-00-00 container[3192]: Failed creating container test from curios-httpd-v24.03.0
|
||||
- (press h for help or q to quit)
|
||||
```
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: |
|
||||
A clear and concise description of what you expected to happen.
|
||||
value: |
|
||||
```
|
||||
admin@infix-c0-ff-ee:/> show log
|
||||
May 15 07:21:02 infix-c0-ff-ee container[3192]: Successfully created container test from curios-httpd-v24.03.0
|
||||
- (press h for help or q to quit)
|
||||
```
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: |
|
||||
Steps to reproduce the issue. For example:
|
||||
|
||||
1. Built from source
|
||||
2. Upgrade to latest release, vYY.MM.P
|
||||
3. Factory reset
|
||||
4. Enable feature
|
||||
5. Check the logs/show command/operational status
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: |
|
||||
- Relevant parts of `startup-config`
|
||||
- Output from `show interfaces`, if applicable
|
||||
- Other observations, screenshots, log files ...
|
||||
|
||||
**Tip:** You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,7 @@
|
||||
contact_links:
|
||||
- name: "🔍 Look for my Issue"
|
||||
url: https://github.com/kernelkit/infix/issues?q=is%3Aissue+sort%3Acreated-desc+
|
||||
about: Thank ❤️ you for your time! We kindly ask that you first check if an issue already exists.
|
||||
- name: "💬 Discussions and Q&A"
|
||||
url: https://github.com/kernelkit/infix/discussions
|
||||
about: Discussions are perfect for quick questions, bouncing ideas, and things that may be a bug.
|
||||
@@ -0,0 +1,45 @@
|
||||
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
|
||||
name: "\U0001F6E0️ Feature request"
|
||||
description: Suggest a new feature, idea or improvement of the OS
|
||||
labels:
|
||||
- "feature"
|
||||
- "triage"
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Thank :heart: you for taking the time to fill out this feature request!**
|
||||
|
||||
We kindly ask that you search to see if an issue [already exists](https://github.com/kernelkit/infix/issues?q=is%3Aissue+sort%3Acreated-desc+) for your feature. We are also happy to accept contributions from our users. For details see [CONTRIBUTING](https://github.com/kernelkit/infix/blob/master/.github/CONTRIBUTING.md).
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
A clear and concise description of the problem or missing feature.
|
||||
**Example:** *I'm always frustrated when [...]*, or *I'd like to see support for [...]*
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional Information
|
||||
description: |
|
||||
Any other related information, e.g. existing YANG model(s), or possibly screenshots about the feature request here.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: General Information
|
||||
description: |
|
||||
Appended to all feature requests for future readers.
|
||||
value: |
|
||||
Anyone can help out by [sponsoring][1] development of new features or [contributing][2] pull requests.
|
||||
Please use this issue for discussions related to the feature.
|
||||
|
||||
[1]: https://github.com/kernelkit/infix/blob/main/.github/SUPPORT.md
|
||||
[2]: https://github.com/kernelkit/infix/blob/main/.github/CONTRIBUTING.md
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,5 +1,3 @@
|
||||
<!--- **Summarize** your changes in the title above -->
|
||||
|
||||
## Description
|
||||
|
||||
<!--
|
||||
@@ -15,7 +13,7 @@
|
||||
|
||||
## Checklist
|
||||
|
||||
Tick relevant boxes, this PR is-a or has-a:
|
||||
Tick *relevant* boxes, this PR is-a or has-a:
|
||||
|
||||
- [ ] Bugfix
|
||||
- [ ] Regression tests
|
||||
@@ -31,7 +29,3 @@ Tick relevant boxes, this PR is-a or has-a:
|
||||
- [ ] Documentation content changes
|
||||
- [ ] Other (please describe):
|
||||
|
||||
|
||||
## References
|
||||
|
||||
<!-- Please list references to related issue(s) -->
|
||||
|
||||
@@ -8,21 +8,22 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Infix ${{ matrix.platform }}
|
||||
name: Build Infix ${{ matrix.target }}
|
||||
runs-on: [ self-hosted, latest ]
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [aarch64, x86_64]
|
||||
target: [aarch64, x86_64]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
clean: true
|
||||
submodules: recursive
|
||||
|
||||
- name: Set Build Variables
|
||||
id: vars
|
||||
run: |
|
||||
target=${{ matrix.platform }}
|
||||
target=${{ matrix.target }}
|
||||
echo "dir=infix-$target" >> $GITHUB_OUTPUT
|
||||
echo "tgz=infix-$target.tar.gz" >> $GITHUB_OUTPUT
|
||||
|
||||
@@ -38,14 +39,14 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .ccache/
|
||||
key: ccache-${{ matrix.platform }}-${{ hashFiles('.git/modules/buildroot/HEAD', 'package/*/*.hash') }}
|
||||
key: ccache-${{ matrix.target }}-${{ hashFiles('.git/modules/buildroot/HEAD', 'package/*/*.hash') }}
|
||||
restore-keys: |
|
||||
ccache-${{ matrix.platform }}-
|
||||
ccache-${{ matrix.target }}-
|
||||
ccache-
|
||||
|
||||
- name: Configure & Build
|
||||
run: |
|
||||
target=${{ matrix.platform }}_defconfig
|
||||
target=${{ matrix.target }}_defconfig
|
||||
echo "Building $target ..."
|
||||
make $target
|
||||
make
|
||||
@@ -58,13 +59,14 @@ jobs:
|
||||
tar chfz ${{ steps.vars.outputs.tgz }} ${{ steps.vars.outputs.dir }}
|
||||
|
||||
- name: Test
|
||||
if: matrix.platform == 'x86_64'
|
||||
if: matrix.target == 'x86_64'
|
||||
run: |
|
||||
make test
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: output/${{ steps.vars.outputs.tgz }}
|
||||
name: artifact-${{ matrix.platform }}
|
||||
name: artifact-${{ matrix.target }}
|
||||
|
||||
release:
|
||||
if: ${{github.repository_owner == 'kernelkit' && github.ref_name == 'main'}}
|
||||
|
||||
@@ -15,13 +15,18 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Regression Testing
|
||||
name: Regression Testing ${{ matrix.target }}
|
||||
runs-on: [ self-hosted, regression ]
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
strategy:
|
||||
matrix:
|
||||
target: [aarch64, x86_64]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
clean: true
|
||||
submodules: recursive
|
||||
|
||||
- name: Clean up cruft ...
|
||||
run: |
|
||||
@@ -30,7 +35,7 @@ jobs:
|
||||
- name: Set Build Variables
|
||||
id: vars
|
||||
run: |
|
||||
target=x86_64
|
||||
target=${{ matrix.target }}
|
||||
echo "dir=infix-$target" >> $GITHUB_OUTPUT
|
||||
echo "tgz=infix-$target.tar.gz" >> $GITHUB_OUTPUT
|
||||
|
||||
@@ -38,23 +43,24 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: dl/
|
||||
key: dl-netconf-${{ hashFiles('.git/modules/buildroot/refs/heads/master', 'package/*/*.hash') }}
|
||||
key: dl-netconf-${{ hashFiles('.git/modules/buildroot/HEAD', 'configs/*', 'package/*/*.hash') }}
|
||||
restore-keys: |
|
||||
dl-netconf-
|
||||
dl-
|
||||
|
||||
- name: Restore Cache of .ccache/
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .ccache/
|
||||
key: ccache-x86_64-${{ hashFiles('.git/modules/buildroot/refs/head/master', 'package/*/*.hash') }}
|
||||
key: ccache-x86_64-${{ hashFiles('.git/modules/buildroot/HEAD', 'package/*/*.hash') }}
|
||||
restore-keys: |
|
||||
ccache-x86_64-
|
||||
ccache-${{ matrix.target }}-
|
||||
ccache-
|
||||
|
||||
- name: Configure NETCONF
|
||||
- name: Configure ${{ matrix.target }}
|
||||
run: |
|
||||
make x86_64_defconfig
|
||||
target=${{ matrix.target }}_defconfig
|
||||
echo "Building $target ..."
|
||||
make $target
|
||||
|
||||
- name: Unit Test
|
||||
run: |
|
||||
@@ -70,12 +76,15 @@ jobs:
|
||||
mv images ${{ steps.vars.outputs.dir }}
|
||||
ln -s ${{ steps.vars.outputs.dir }} images
|
||||
tar cfz ${{ steps.vars.outputs.tgz }} ${{ steps.vars.outputs.dir }}
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: output/${{ steps.vars.outputs.tgz }}
|
||||
name: artifact-${{ matrix.target }}
|
||||
|
||||
- name: Regression Test
|
||||
if: matrix.target == 'x86_64'
|
||||
run: |
|
||||
make test
|
||||
|
||||
|
||||
@@ -20,16 +20,17 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository == 'kernelkit/infix' && startsWith(github.ref, 'refs/tags/')
|
||||
name: Build Infix ${{ github.ref_name }} [${{ matrix.platform }}]
|
||||
name: Build Infix ${{ github.ref_name }} [${{ matrix.target }}]
|
||||
runs-on: [ self-hosted, release ]
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [aarch64, x86_64]
|
||||
target: [aarch64, x86_64]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
clean: true
|
||||
submodules: recursive
|
||||
|
||||
- name: Set Release Variables
|
||||
id: vars
|
||||
@@ -41,7 +42,7 @@ jobs:
|
||||
fi
|
||||
echo "ver=${ver}" >> $GITHUB_OUTPUT
|
||||
fver=${ver#v}
|
||||
target=${{ matrix.platform }}-${fver}
|
||||
target=${{ matrix.target }}-${fver}
|
||||
echo "dir=infix-$target" >> $GITHUB_OUTPUT
|
||||
echo "tgz=infix-$target.tar.gz" >> $GITHUB_OUTPUT
|
||||
|
||||
@@ -57,16 +58,16 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .ccache/
|
||||
key: ccache-${{ matrix.platform }}-${{ hashFiles('.git/modules/buildroot/HEAD', 'package/*/*.hash') }}
|
||||
key: ccache-${{ matrix.target }}-${{ hashFiles('.git/modules/buildroot/HEAD', 'package/*/*.hash') }}
|
||||
restore-keys: |
|
||||
ccache-${{ matrix.platform }}-
|
||||
ccache-${{ matrix.target }}-
|
||||
ccache-
|
||||
|
||||
- name: Configure & Build
|
||||
env:
|
||||
INFIX_RELEASE: ${{ steps.vars.outputs.ver }}
|
||||
run: |
|
||||
target=${{ matrix.platform }}_defconfig
|
||||
target=${{ matrix.target }}_defconfig
|
||||
echo "Building $target ..."
|
||||
make $target
|
||||
make
|
||||
@@ -87,7 +88,7 @@ jobs:
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artifact-${{ matrix.platform }}
|
||||
name: artifact-${{ matrix.target }}
|
||||
path: output/*.tar.gz
|
||||
|
||||
release:
|
||||
|
||||
@@ -100,7 +100,7 @@ and testing, but can also be used for evaluation and demo purposes. For
|
||||
more information, see: [Infix in Virtual Environments](doc/virtual.md).
|
||||
|
||||
> See the [GitHub Releases](https://github.com/kernelkit/infix/releases)
|
||||
> page for our pre-built images. The *Latest Build* has the bleeding
|
||||
> page for our pre-built images. The *[Latest Build][]* has bleeding
|
||||
> edge images, if possible we recommend using a versioned release.
|
||||
>
|
||||
> For *customer specific builds* of Infix, see your product repository.
|
||||
@@ -116,6 +116,7 @@ more information, see: [Infix in Virtual Environments](doc/virtual.md).
|
||||
[1]: https://buildroot.org/
|
||||
[2]: https://www.sysrepo.org/
|
||||
[3]: doc/cli/introduction.md
|
||||
[Latest Build]: https://github.com/kernelkit/infix/releases/tag/latest
|
||||
[License]: https://en.wikipedia.org/wiki/GPL_license
|
||||
[License Badge]: https://img.shields.io/badge/License-GPL%20v2-blue.svg
|
||||
[GitHub]: https://github.com/kernelkit/infix/actions/workflows/build.yml/
|
||||
|
||||
@@ -312,10 +312,10 @@ generate_dot()
|
||||
{
|
||||
[ "$CONFIG_QEMU_NET_TAP" = "y" ] || return
|
||||
|
||||
hostports="<qtap0> qtap0"
|
||||
targetports="<e0> e0"
|
||||
edges="host:qtap0 -- target:e0 [kind=mgmt];"
|
||||
for tap in $(seq 1 $(($CONFIG_QEMU_NET_TAP_N - 1))); do
|
||||
hostports="<qtap1> qtap1"
|
||||
targetports="<e1> e1"
|
||||
edges="host:qtap1 -- target:e1 [kind=mgmt];"
|
||||
for tap in $(seq 2 $(($CONFIG_QEMU_NET_TAP_N - 1))); do
|
||||
hostports="$hostports | <qtap$tap> qtap$tap "
|
||||
targetports="$targetports | <e$tap> e$tap "
|
||||
edges="$edges host:qtap$tap -- target:e$tap;"
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
0 unspec
|
||||
1 kernel_lo
|
||||
2 kernel_ra
|
||||
3 kernel_ll
|
||||
4 static
|
||||
5 dhcp
|
||||
6 random
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
UsePAM yes
|
||||
@@ -1,34 +0,0 @@
|
||||
test-dir := $(BR2_EXTERNAL_INFIX_PATH)/test
|
||||
INFIX_TESTS ?= $(test-dir)/case/all.yaml
|
||||
|
||||
test-env = $(test-dir)/env \
|
||||
-f $(BINARIES_DIR)/infix-x86_64.img \
|
||||
-f $(BINARIES_DIR)/infix-x86_64-disk.img \
|
||||
-f $(BINARIES_DIR)/OVMF.fd \
|
||||
-p $(BINARIES_DIR)/infix-x86_64.pkg \
|
||||
$(1) $(2)
|
||||
|
||||
test-env-qeneth = $(call test-env,-q $(test-dir)/virt/quad,$(1))
|
||||
test-env-run = $(call test-env,-C -t $(BINARIES_DIR)/qemu.dot,$(1))
|
||||
|
||||
test test-qeneth:
|
||||
$(call test-env-qeneth,\
|
||||
$(BR2_EXTERNAL_INFIX_PATH)/test/9pm/9pm.py \
|
||||
$(INFIX_TESTS))
|
||||
test-sh test-qeneth-sh:
|
||||
$(call test-env-qeneth,-i /bin/sh)
|
||||
|
||||
test-run: | ~/.infix-test-venv
|
||||
$(call test-env-run,\
|
||||
$(BR2_EXTERNAL_INFIX_PATH)/test/9pm/9pm.py \
|
||||
$(INFIX_TESTS))
|
||||
test-run-sh: | ~/.infix-test-venv
|
||||
$(call test-env-run,/bin/sh)
|
||||
|
||||
test-run-play: | ~/.infix-test-venv
|
||||
$(call test-env-run,$(test-dir)/case/meta/play.py)
|
||||
|
||||
~/.infix-test-venv:
|
||||
$(test-dir)/docker/init-venv.sh $(test-dir)/docker/pip-requirements.txt
|
||||
|
||||
.PHONY: test test-sh test-qeneth test-qeneth-sh test-run test-run-sh test-run-play
|
||||
|
||||
@@ -62,6 +62,8 @@ BR2_PACKAGE_LIBOPENSSL_BIN=y
|
||||
BR2_PACKAGE_LIBCURL_CURL=y
|
||||
BR2_PACKAGE_NETOPEER2_CLI=y
|
||||
BR2_PACKAGE_NSS_MDNS=y
|
||||
BR2_PACKAGE_LINUX_PAM=y
|
||||
BR2_PACKAGE_LIBPAM_RADIUS_AUTH=y
|
||||
BR2_PACKAGE_ONIGURUMA=y
|
||||
BR2_PACKAGE_AVAHI=y
|
||||
BR2_PACKAGE_AVAHI_DAEMON=y
|
||||
|
||||
@@ -73,6 +73,8 @@ BR2_PACKAGE_LIBOPENSSL_BIN=y
|
||||
BR2_PACKAGE_LIBCURL_CURL=y
|
||||
BR2_PACKAGE_NETOPEER2_CLI=y
|
||||
BR2_PACKAGE_NSS_MDNS=y
|
||||
BR2_PACKAGE_LINUX_PAM=y
|
||||
BR2_PACKAGE_LIBPAM_RADIUS_AUTH=y
|
||||
BR2_PACKAGE_ONIGURUMA=y
|
||||
BR2_PACKAGE_AVAHI=y
|
||||
BR2_PACKAGE_AVAHI_DAEMON=y
|
||||
|
||||
@@ -57,6 +57,8 @@ BR2_PACKAGE_LIBOPENSSL_BIN=y
|
||||
BR2_PACKAGE_LIBCURL_CURL=y
|
||||
BR2_PACKAGE_NETOPEER2_CLI=y
|
||||
BR2_PACKAGE_NSS_MDNS=y
|
||||
BR2_PACKAGE_LINUX_PAM=y
|
||||
BR2_PACKAGE_LIBPAM_RADIUS_AUTH=y
|
||||
BR2_PACKAGE_ONIGURUMA=y
|
||||
BR2_PACKAGE_AVAHI=y
|
||||
BR2_PACKAGE_AVAHI_DAEMON=y
|
||||
|
||||
@@ -4,6 +4,45 @@ Change Log
|
||||
All notable changes to the project are documented in this file.
|
||||
|
||||
|
||||
[v24.05.0][UNRELEASED]
|
||||
----------------------
|
||||
|
||||
### Changes
|
||||
- Default web landing page refactored into a Buildroot package to make
|
||||
it possible to overload from customer repos.
|
||||
- Enable DCB support in aarch64 kernel (for EtherType prio override)
|
||||
- Topology mapper improvements, including option for deterministic
|
||||
reproduction of logical to physical mappings
|
||||
- New version of `gencert` tool, for self signed HTTPS certificates.
|
||||
This allows dropping dependency on building a host rust toolchain
|
||||
- Issue #374: add timestamps to dagger .log files
|
||||
- Add small delay in U-Boot to allow stopping boot on reference boards
|
||||
- Document how to provision the bootloader and Infix on a blank board
|
||||
- Use initial hostname from `/etc/os-release` as configuration fallback
|
||||
- Update documentation for use of VETH pairs in containers
|
||||
- Issue #454: create bridges in `factory-config` with IGMP/MLD snooping
|
||||
enabled by default
|
||||
- Add support for optionally running user scripts from
|
||||
`/cfg/user-scripts.d`
|
||||
|
||||
### Fixes
|
||||
- Add missing LICENSE hash for factory reset tool
|
||||
- Fix #424: regression, root user can log in without password
|
||||
- Fix build regressions in `cn9130_crb_boot_defconfig` caused by upgrade
|
||||
to Buildroot v2024.02 and recent multi-key support in RAUC and U-Boot
|
||||
- Fix provisioning script after changes to make GRUB loading more robust
|
||||
- Fix missing `/etc/resolv.conf`, as noticed by `avahi-daemon`, when a
|
||||
user calls `no system` from the CLI
|
||||
- Fix #428: loss of admin account after upgrade to v24.04
|
||||
- Fix #429: failing to load `startup-config` does not trigger the fail
|
||||
secure mode, causing the system to end up in an undefined state
|
||||
- Fix #453: fix inconsistent behavior of custom MAC address (interface
|
||||
`phys-address` for VETH pairs. Allows fixed MAC in containers
|
||||
- Fix locking issue with standard counter groups on `mv88e6xxx`
|
||||
- Fix MDB/ATU synchronization issue from IGMPv3/MLDv2 reports on
|
||||
`mv88e6xxx` systems
|
||||
|
||||
|
||||
[v24.04.2][] - 2024-05-15
|
||||
-------------------------
|
||||
|
||||
@@ -881,6 +920,7 @@ Supported YANG models in addition to those used by sysrepo and netopeer:
|
||||
|
||||
[buildroot]: https://buildroot.org/
|
||||
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v24.04.0...HEAD
|
||||
[v24.05.0]: https://github.com/kernelkit/infix/compare/v24.04.0...v24.05.0
|
||||
[v24.04.2]: https://github.com/kernelkit/infix/compare/v24.04.1...v24.04.2
|
||||
[v24.04.1]: https://github.com/kernelkit/infix/compare/v24.04.0...v24.04.1
|
||||
[v24.04.0]: https://github.com/kernelkit/infix/compare/v24.02.0...v24.04.0
|
||||
|
||||
+60
-19
@@ -390,12 +390,30 @@ network:
|
||||
|
||||
### Container Host Interface
|
||||
|
||||
Another common use-case is to move a network interface into the network
|
||||
namespace of a container. Which the container bridge network type does
|
||||
behind the scenes with one end of the automatically created VETH pair.
|
||||
This works with regular Ethernet interfaces as well, but here we will
|
||||
use a VETH pair as an example along with a regular bridge (where other
|
||||
Ethernet interfaces may live as well).
|
||||
Another common use-case is to move network interfaces into the network
|
||||
namespace of a container[^3]. This of course works with plain Ethernet
|
||||
interfaces as well, but here we will use one end of a VETH pair as an
|
||||
example.
|
||||
|
||||
[^3]: Something which the container bridge network type does behind the
|
||||
scenes with one end of an automatically created VETH pair.
|
||||
|
||||
#### Routed Setup
|
||||
|
||||
In this routed setup we reserve 192.168.0.0/24 for the network between
|
||||
the host and the `ntpd` container.
|
||||
|
||||
_____________
|
||||
| |
|
||||
veth0 .1 | .2 eth0 |
|
||||
\ 192.168.0.0/24 | / |
|
||||
'--------------------' [ntpd] |
|
||||
|_____________|
|
||||
|
||||
Configuration is a straight-forward VETH pair setup where we name the
|
||||
container-end of pair `ntpd`. This is just a convenience for us when
|
||||
reading the configuration later. The *real action* happens on the last
|
||||
line where we declare the `ntpd` end as a container network interface:
|
||||
|
||||
admin@example:/config/> edit interface veth0
|
||||
admin@example:/config/interface/veth0/> set veth peer ntpd
|
||||
@@ -403,23 +421,49 @@ Ethernet interfaces may live as well).
|
||||
admin@example:/config/interface/veth0/> end
|
||||
admin@example:/config/> edit interface ntpd
|
||||
admin@example:/config/interface/ntpd/> set ipv4 address 192.168.0.2 prefix-length 24
|
||||
admin@example:/config/interface/ntpd/> set phys-address 00:c0:ff:ee:00:01
|
||||
admin@example:/config/interface/ntpd/> set container-network
|
||||
|
||||
This is a routed setup, where we reserve 192.168.0.0/24 for the network
|
||||
between the host and the `ntpd` container. A perhaps more common case
|
||||
is to put `veth0` as a port in a bridge with other physical ports. The
|
||||
point of the routed case is that port forwarding from the container in
|
||||
this case is limited to a single interface, not *all interfaces* as is
|
||||
the default in the masquerading container bridge setup.
|
||||
> Notice how you can also set a custom MAC address at the same time.
|
||||
|
||||
When a container has multiple host interfaces it can often be useful to
|
||||
have a default route installed. This can be added from the host with a
|
||||
`0.0.0.0/0` route on one of the interfaces. The following is an example
|
||||
when adding a second VETH pair to the container:
|
||||
Adding the interface to the container is the same as before, but since
|
||||
everything for host interfaces is set up in the interfaces context, we
|
||||
can take a bit of a shortcut.
|
||||
|
||||
admin@example:/config/container/ntpd/> set network ntpd
|
||||
admin@example:/config/container/ntpd/> leave
|
||||
|
||||
The point of the routed case is that port forwarding from the container
|
||||
in this case is limited to a single interface, not *all interfaces* as
|
||||
is the default in the masquerading container bridge setup.
|
||||
|
||||
|
||||
#### Bridged Setup
|
||||
|
||||
A perhaps more common case is to bridge the other end of the VETH pair
|
||||
with other physical ports. In this section we show how to add a new
|
||||
pair to give our container two interfaces:
|
||||
|
||||
_______________
|
||||
| | .1 br0
|
||||
veth0 .1 | .2 eth0 | / \
|
||||
\ 192.168.0.0/24 | / eth1 .2 | veth1b e1
|
||||
'------------------' \ | 192.168.1.0/24 /
|
||||
| [ntpd] '------------------'
|
||||
|_______________|
|
||||
|
||||
We start by adding the second VETH pair:
|
||||
|
||||
admin@example:/config/> edit interface veth1a
|
||||
admin@example:/config/interface/veth1a/> set veth peer veth1b
|
||||
admin@example:/config/interface/veth1a/> set ipv4 address 192.168.1.2 prefix-length 24
|
||||
|
||||
> The LAN bridge (br0) in this example has IP address 192.168.1.1.
|
||||
|
||||
When a container has multiple host interfaces it can often be useful to
|
||||
have a default route installed. This can be added from the host with a
|
||||
`0.0.0.0/0` route on one of the interfaces:
|
||||
|
||||
admin@example:/config/interface/veth1a/> set container-network route 0.0.0.0/0 gateway 192.168.1.1
|
||||
admin@example:/config/interface/veth1a/> show
|
||||
type veth;
|
||||
@@ -439,9 +483,6 @@ Please note, container network routes require the base interface also
|
||||
have a static IP address set. Setting only the route, but no address,
|
||||
means the route is skipped.
|
||||
|
||||
> The LAN bridge (br0) in this example has IP address 192.168.1.1.
|
||||
|
||||
|
||||
### Host Networking
|
||||
|
||||
The third use-case is host networking, this is where a container share
|
||||
|
||||
+41
-5
@@ -97,8 +97,10 @@ To see available defconfigs for supported targets, use:
|
||||
Development
|
||||
-----------
|
||||
|
||||
When changing a package, locally kept sources, or when using [`local.mk`](override-package.md),
|
||||
you only want to rebuild the parts you have modified:
|
||||
Developing with Infix is the same as [developing with Buildroot][4].
|
||||
When working with a package, be it locally kept sources, or when using
|
||||
[`local.mk`](override-package.md), you only want to rebuild the parts
|
||||
you have modified:
|
||||
|
||||
make foo-rebuild
|
||||
|
||||
@@ -106,7 +108,7 @@ or
|
||||
|
||||
make foo-reconfigure
|
||||
|
||||
or, when nothing seems to bite:
|
||||
or, as a last resort when nothing seems to bite:
|
||||
|
||||
make foo-dirclean foo-rebuild
|
||||
|
||||
@@ -119,6 +121,38 @@ This rebuilds (and installs) `foo` and `bar`, the `all` target calls
|
||||
on Buildroot to finalize the target filesystem and generate the images.
|
||||
The final `run` argument is explained below.
|
||||
|
||||
### `confd`
|
||||
|
||||
The Infix `src/confd/` is the engine of the system. Currently it is a
|
||||
plugin for `systemd-plugind` and contains XPath subscriptions to all the
|
||||
supported YANG models.
|
||||
|
||||
There are essentially two ways of adding support for a new YANG model:
|
||||
|
||||
- The [sysrepo way][3], or
|
||||
- The Infix way, using libsrx (the `lydx_*()` functions)
|
||||
|
||||
The former is well documented in sysrepo, and the latter is best taught
|
||||
by example, e.g., `src/confd/src/infix-dhcp.c`. Essentially libsrx is a
|
||||
way of traversing the libyang tree instead of fetching changes by XPath.
|
||||
|
||||
When working with `confd` you likely want to enable full debug mode,
|
||||
this is how you do it:
|
||||
|
||||
1. Open the file `package/confd/confd.conf`
|
||||
2. Uncomment the first line `set DEBUG=1`
|
||||
3. Change the following line to add `-v3` at the end
|
||||
|
||||
[S12345] sysrepo-plugind -f -p /run/confd.pid -n -- Configuration daemon
|
||||
|
||||
to:
|
||||
|
||||
[S12345] sysrepo-plugind -f -p /run/confd.pid -n -v3 -- Configuration daemon
|
||||
|
||||
Now you can rebuild `confd`, just as described above, and restart Infix:
|
||||
|
||||
make confd-rebuild all run
|
||||
|
||||
|
||||
Testing
|
||||
-------
|
||||
@@ -142,8 +176,8 @@ and then use GitHub to create a *Pull Reqeuest*.
|
||||
For this to work as painlessly as possible:
|
||||
|
||||
1. Fork Infix to your own user or organization[^1]
|
||||
2. Fork all the Infix submodules, e.g., `buildroot` to your own user
|
||||
or organization as well
|
||||
2. Fork all the Infix submodules, e.g., `kernelkit/buildroot` to your
|
||||
own user or organization as well
|
||||
3. Clone your fork of Infix to your laptop/workstation
|
||||
|
||||
If you use a GitHub organization you get the added benefit of having
|
||||
@@ -170,3 +204,5 @@ $ git submodule update --init
|
||||
[0]: https://github.com/kernelkit/infix/releases
|
||||
[1]: https://buildroot.org/downloads/manual/manual.html
|
||||
[2]: https://github.com/wkz/qeneth
|
||||
[3]: https://netopeer.liberouter.org/doc/sysrepo/master/html/dev_guide.html
|
||||
[4]: https://buildroot.org/downloads/manual/manual.html#_developer_guide
|
||||
|
||||
+53
-28
@@ -1,50 +1,75 @@
|
||||
Package override
|
||||
Package Override
|
||||
================
|
||||
|
||||
This guide demonstrates how the `local.mk` file is utilized to override
|
||||
a Linux Buildroot package. The example of `tcpdump` serves to illustrate
|
||||
This guide demonstrates how the `local.mk` file is utilized to override
|
||||
a Linux Buildroot package. As an example we use `tcpdump` to illustrate
|
||||
this process.
|
||||
|
||||
In an instance such as `Infix` using tcpdump, the `local.mk` file is modified
|
||||
as shown below:
|
||||
> For a comprehensive guide to utilizing Buildroot during development,
|
||||
> including the `<pkg>_OVERRIDE_SRCDIR` mechanism, shown below, please
|
||||
> see [Using Buildroot during development][1] in the official docs.
|
||||
|
||||
|
||||
Setup
|
||||
-----
|
||||
|
||||
Since the `output/` directory is often wiped for rebuilds, make sure you
|
||||
keep the file `local.mk` in the top directory and only symlink it to your
|
||||
build directory:
|
||||
|
||||
~$ cd infix/
|
||||
~/infix(main)$ touch local.mk
|
||||
~/infix/output(main)$ ln -s ../local.mk .
|
||||
~/infix/output(main)$ cd ..
|
||||
|
||||
|
||||
Override
|
||||
--------
|
||||
|
||||
Now edit the file:
|
||||
|
||||
~/infix(main)$ editor local.mk
|
||||
|
||||
Add an override for `tcpdump`. The file is a Makefile snippet so you
|
||||
can add a lot of things. Comment out lines with the UNIX comment `#`
|
||||
character if needed:
|
||||
|
||||
```
|
||||
TCPDUMP_OVERRIDE_SRCDIR = /path/to/tcpdump/repo
|
||||
```
|
||||
|
||||
and stored in the `output/` folder, alongside the `.config` file:
|
||||
```
|
||||
user@PC:~/infix$ll output/ | grep -e 'local.mk' -e '.config'
|
||||
-rw-r--r-- 1 group user 119936 Nov 10 18:04 .config
|
||||
-rw-r--r-- 1 group user 43 Nov 10 18:25 local.mk
|
||||
```
|
||||
Building
|
||||
--------
|
||||
|
||||
The execution of `make tcpdump-rebuild all` triggers a process where
|
||||
Buildroot synchronizes the tcpdump source code from the specified override directory
|
||||
to `output/build/tcpdump-custom`, followed by the rebuilding of the entire project.
|
||||
The execution of `make tcpdump-rebuild all` triggers a process where
|
||||
Buildroot synchronizes the tcpdump source code from the specified
|
||||
override directory to `output/build/tcpdump-custom`, followed by the
|
||||
rebuilding of the entire project.
|
||||
|
||||
```
|
||||
user@PC:~/infix$ make tcpdump-rebuild all
|
||||
~/infix$(main)$ make tcpdump-rebuild all
|
||||
```
|
||||
|
||||
Buildroot follows a process of downloading and processing tarballs:
|
||||
extraction, configuration, compilation, and installation. The source
|
||||
for each package is extracted to a temporary build directory:
|
||||
|
||||
output/build/<package>-<version> # e.g., tcpdump-4.99.4
|
||||
|
||||
Let's have a look at what we got:
|
||||
|
||||
```
|
||||
user@PC:~/infix$ ll /output/build/ | grep tcpdump
|
||||
~/infix$(main)$ ll /output/build/ | grep tcpdump
|
||||
drwxr-xr-x 7 group user 20480 Nov 10 18:26 tcpdump-4.99.4/
|
||||
drwxr-xr-x 7 group user 12288 Nov 10 18:28 tcpdump-custom/
|
||||
```
|
||||
|
||||
Buildroot follows a process of downloading and processing tarballs
|
||||
(extraction, configuration, compilation, and installation).
|
||||
The source code is stored in a temporary directory:
|
||||
`output/build/<package>-<version>` (i.e. `tcpdump-4.99.4/`),
|
||||
which is removed and recreated with each `make` command. That is why
|
||||
the direct modifications in the `output/build` directory are generally
|
||||
**not recommended**.
|
||||
As long as your local override is in place, Buildroot will use your
|
||||
custom version.
|
||||
|
||||
To manage the development changes more effectively, where the package source code
|
||||
remains untouched, Buildroot incorporates the `<pkg>_OVERRIDE_SRCDIR` feature.
|
||||
> **Remember:** the build directory is ephemeral, so be careful to
|
||||
> change any of the files therein. It can be useful though during
|
||||
> debugging, but just make sure to learn the difference between the
|
||||
> various Buildroot commands to build, clean, reconfigure, etc.
|
||||
|
||||
For a comprehensive understanding of utilizing Buildroot during development,
|
||||
including detailed elaboration on the `<pkg>_OVERRIDE_SRCDIR` feature,
|
||||
refer to section 8.13.6 in [Using Buildroot during development](https://nightly.buildroot.org/).
|
||||
[1]: https://buildroot.org/downloads/manual/manual.html#_using_buildroot_during_development
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
Test System Architecture
|
||||
========================
|
||||
|
||||
Tenets
|
||||
------
|
||||
|
||||
- **Keep overhead to a minimum**. Tests should be fast to both write
|
||||
and run. Ideally, the developer should _want_ to add tests early in
|
||||
the development cycle because they instinctively feel that that is
|
||||
the quickest route to arrive at a correct and robust implementation.
|
||||
|
||||
- **Both physical and virtual hardware matters**. Infix is primarily
|
||||
deployed on physical hardware, so being able to run the test suite
|
||||
on real devices is crucial to guarantee a high quality product. At
|
||||
the same time, there is much value in running the same suite on
|
||||
virtual hardware, as it makes it easy to catch regressions early.
|
||||
It is also much more practical and economical to build large virtual
|
||||
networks than physical ones.
|
||||
|
||||
- **Avoid CLI scipting & scraping**. Reliably interacting with a DUT
|
||||
over a serial line in a robust way is _very_ hard to get right.
|
||||
Given that we have a proper API (RESTCONF), we should leverage that
|
||||
when testing. Front-ends can be tested by other means.
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||

|
||||
|
||||
The test system is made up of several independent components, which
|
||||
are typically used in concert to run a full test suite.
|
||||
|
||||
### Test Cases
|
||||
|
||||
A test case is an executable, receiving the physical topology as a
|
||||
positional argument, which produces [TAP][] compliant output on its
|
||||
`stdout`. I.e., it is executed in the following manner:
|
||||
|
||||
test-case [OPTS] <physical-topology>
|
||||
|
||||
Test cases are typically written in Python, using the
|
||||
[Infamy](#infamy) library. Ultimately though, it can be implemented
|
||||
in any language, as long as it matches the calling convention above.
|
||||
|
||||
### Infamy
|
||||
|
||||
Rather than having each test case come up with its own implementation
|
||||
of how to map topologies, how to push NETCONF data to a device, etc.,
|
||||
we provide a library of functions to take care of all that, dubbed
|
||||
"Infamy". When adding a new test case, ask yourself if any parts of
|
||||
it might belong in Infamy as a generalized component that can be
|
||||
reused by other tests.
|
||||
|
||||
Some of the core functions provided by Infamy are:
|
||||
|
||||
- Mapping a logical topology to a physical one
|
||||
- Finding and attaching to a device over an Ethernet interface, using
|
||||
NETCONF
|
||||
- Pushing/pulling NETCONF data to/from a device
|
||||
- Generating TAP compliant output
|
||||
|
||||
### 9PM
|
||||
|
||||
To run multiple tests, we employ [9PM][]. It let's us define test
|
||||
suites as simple YAML files. Suites can also be hierarchically
|
||||
structured, with a suite being made up of other suites, etc.
|
||||
|
||||
It also validates the TAP output, making sure to catch early exits
|
||||
from a case, and produces a nice summary report.
|
||||
|
||||
### `/test/env`
|
||||
|
||||
A good way to ensure that nobody ever runs the test suite is to make
|
||||
it _really_ hard to do so. `/test/env`'s job is instead to make it
|
||||
very _easy_ to create a reproducible environment in which tests can be
|
||||
executed.
|
||||
|
||||
Several technologies are leveraged to accomplish this:
|
||||
|
||||
- **Containers**: The entire execution is optionally done inside a
|
||||
standardized `docker` container environment. This ensures that the
|
||||
software needed to run the test suite is always available, no matter
|
||||
which distribution the user is running on their machine.
|
||||
|
||||
- **Python Virtual Environments**: To make sure that the expected
|
||||
versions of all Python packages are available, the execution is
|
||||
wrapped inside a `venv`. This is true for containerized executions,
|
||||
where the container comes with a pre-installed environment, but it
|
||||
can also be sourced from the host system when running outside of the
|
||||
container.
|
||||
|
||||
- **Virtual Test Topology**: Using [Qeneth][], the environment can
|
||||
optionally be started with a virtual topology of DUTs to run the
|
||||
tests on.
|
||||
|
||||
> `docker` is the only supported container environment when running
|
||||
> tests in the host's network namespace. When running on a virtual
|
||||
> Qeneth topology, `podman` may also be used by installing the
|
||||
> `podman-docker` package from your host system's distro.
|
||||
|
||||
|
||||
Physical and Logical Topologies
|
||||
-------------------------------
|
||||
|
||||
Imagine that we want to create a test with three DUTs; one acting as a
|
||||
DHCP server, and the other two as DHCP clients - with all three having
|
||||
a management connection to the host PC running the test. In other
|
||||
words, the test requires a _logical_ topology like the one below.
|
||||
|
||||
<img align="right" src="img/testing-log.dot.svg" alt="Example Logical Topology">
|
||||
|
||||
```dot
|
||||
graph "dhcp-client-server" {
|
||||
host [
|
||||
label="host | { <c1> c1 | <srv> srv | <c2> c2 }",
|
||||
kind="controller",
|
||||
];
|
||||
|
||||
server [
|
||||
label="{ <mgmt> mgmt } | server | { <c1> c1 | <c2> c2 }",
|
||||
kind="infix",
|
||||
];
|
||||
client1 [
|
||||
label="{ <mgmt> mgmt } | client1 | { <srv> srv }",
|
||||
kind="infix",
|
||||
];
|
||||
client2 [
|
||||
label="{ <mgmt> mgmt } | client2 | { <srv> srv }",
|
||||
kind="infix",
|
||||
];
|
||||
|
||||
host:srv -- server:mgmt
|
||||
host:c1 -- client1:mgmt
|
||||
host:c2 -- client2:mgmt
|
||||
|
||||
server:c1 -- client1:srv;
|
||||
server:c2 -- client2:srv;
|
||||
}
|
||||
```
|
||||
|
||||
When running in a virtualized environment, one could simply create a
|
||||
setup that matches the test's logical topology. But in scenarios when
|
||||
devices are physical systems, connected by real copper cables, this is
|
||||
not possible (unless you have some wicked L1 relay matrix thingy).
|
||||
|
||||
Instead, the test implementation does not concern itself with the
|
||||
exact nodes used to run the test, only that the _logical_ topology can
|
||||
be _mapped_ to some subset of the _physical_ topology. In
|
||||
mathematical terms, the physical topology must contain a subgraph that
|
||||
is _isomorphic_ to the logical topology.
|
||||
|
||||
Standing on the shoulders of giants (i.e. people with mathematics
|
||||
degrees), we can deploy well-known algorithms to find such subgraphs.
|
||||
Continuing our example, let's say we want to run our DHCP test on the
|
||||
_physical_ topology below.
|
||||
|
||||
<img align="right" src="img/testing-phy.dot.svg" alt="Example Physical Topology">
|
||||
|
||||
```dot
|
||||
graph "quad-ring" {
|
||||
host [
|
||||
label="host | { <d1a> d1a | <d1b> d1b | <d1c> d1c | <d2a> d2a | <d2b> d2b | <d2c> d2c | <d3a> d3a | <d3b> d3b | <d3c> d3c | <d4a> d4a | <d4b> d4b | <d4c> d4c }",
|
||||
kind="controller",
|
||||
];
|
||||
|
||||
dut1 [
|
||||
label="{ <e1> e1 | <e2> e2 | <e3> e3 } | dut1 | { <e4> e4 | <e5> e5 }",
|
||||
kind="infix",
|
||||
];
|
||||
dut2 [
|
||||
label="{ <e1> e1 | <e2> e2 | <e3> e3 } | dut2 | { <e4> e4 | <e5> e5 }",
|
||||
kind="infix",
|
||||
];
|
||||
dut3 [
|
||||
label="{ <e1> e1 | <e2> e2 | <e3> e3 } | dut3 | { <e4> e4 | <e5> e5 }",
|
||||
kind="infix",
|
||||
];
|
||||
dut4 [
|
||||
label="{ <e1> e1 | <e2> e2 | <e3> e3 } | dut4 | { <e4> e4 | <e5> e5 }",
|
||||
kind="infix",
|
||||
];
|
||||
|
||||
host:d1a -- dut1:e1
|
||||
host:d1b -- dut1:e2
|
||||
host:d1c -- dut1:e3
|
||||
|
||||
host:d2a -- dut2:e1
|
||||
host:d2b -- dut2:e2
|
||||
host:d2c -- dut2:e3
|
||||
|
||||
host:d3a -- dut3:e1
|
||||
host:d3b -- dut3:e2
|
||||
host:d3c -- dut3:e3
|
||||
|
||||
host:d4a -- dut4:e1
|
||||
host:d4b -- dut4:e2
|
||||
host:d4c -- dut4:e3
|
||||
|
||||
dut1:e5 -- dut2:e4
|
||||
dut2:e5 -- dut3:e4
|
||||
dut3:e5 -- dut4:e4
|
||||
dut4:e5 -- dut1:e4
|
||||
}
|
||||
```
|
||||
|
||||
Our test (in fact, all tests) receives the physical topology as an
|
||||
input parameter, and then maps the desired logical topology onto it,
|
||||
producing a mapping from logical nodes and ports to their physical
|
||||
counterparts.
|
||||
|
||||
```dot
|
||||
{
|
||||
"client1": "dut1",
|
||||
"client1:mgmt": "dut1:e1",
|
||||
"client1:srv": "dut1:e4",
|
||||
"client2": "dut3",
|
||||
"client2:mgmt": "dut3:e2",
|
||||
"client2:srv": "dut3:e5",
|
||||
"host": "host",
|
||||
"host:c1": "host:d1a",
|
||||
"host:c2": "host:d3b",
|
||||
"host:srv": "host:d4c",
|
||||
"server": "dut4",
|
||||
"server:c1": "dut4:e5",
|
||||
"server:c2": "dut4:e4",
|
||||
"server:mgmt": "dut4:e3"
|
||||
}
|
||||
```
|
||||
|
||||
With this information, the test knows that, in this particular
|
||||
environment, the server should be managed via the port called `d4c` on
|
||||
the node called `host`; that the port connected to the server on
|
||||
`client1` is `e4` on `dut1`, etc. Thereby separating the
|
||||
implementation of the test from any specific physical setup.
|
||||
|
||||
Testcases are not required to use a logical topology; they may choose
|
||||
to accept whatever physical topology its given, and dynamically
|
||||
determine the DUTs to use for testing. As an example, an STP test
|
||||
could accept an arbitrary physical topology, run the STP algorithm on
|
||||
it offline, enable STP on all DUTs, and then verify that the resulting
|
||||
spanning tree matches the expected one.
|
||||
|
||||
[9PM]: https://github.com/rical/9pm
|
||||
[Qeneth]: https://github.com/wkz/qeneth
|
||||
[TAP]: https://testanything.org/
|
||||
+98
-291
@@ -7,287 +7,57 @@ that one or more DUTs are configured over NETCONF; the resulting
|
||||
network is then black-box tested by injecting and inspecting network
|
||||
traffic at various points.
|
||||
|
||||
This document is intended to be a practical guide on how to run,
|
||||
develop and debug tests. There is a separate document describing the
|
||||
[Test System Architecture](test-arch.md).
|
||||
|
||||
TL;DR
|
||||
-----
|
||||
|
||||
Build Infix and run the test suite on a set of virtual Infix nodes.
|
||||
Modes of Testing
|
||||
----------------
|
||||
|
||||
### Virtual Devices
|
||||
|
||||
By default, tests are run on a topology made up of virtual Infix nodes
|
||||
using [Qeneth][]. To run the full regression test suite: build Infix
|
||||
for `x86_64`:
|
||||
|
||||
$ make x86_64_defconfig
|
||||
$ make
|
||||
$ make test
|
||||
|
||||
To run a subset of tests, e.g., only the DHCP client tests:
|
||||
### Physical Devices
|
||||
|
||||
$ make test INFIX_TESTS=case/infix_dhcp/all.yaml
|
||||
To run the tests on a preexisting topology from the host's network
|
||||
namespace, specify the `host` `TEST_MODE`:
|
||||
|
||||
> **Note:** see the below section [Quick Start Guide][] for how to
|
||||
> connect to the test system, debug and develop tests, and more.
|
||||
$ make TEST_MODE=host test
|
||||
|
||||
This typically used when testing on physical hardware. By default the
|
||||
topology will be sourced from `/etc/infamy.dot`, but this can be
|
||||
overwritten by setting the `TOPOLOGY` variable:
|
||||
|
||||
$ make TEST_MODE=host TOPOLOGY=~/my-topology.dot test
|
||||
|
||||
|
||||
Tenets
|
||||
------
|
||||
### `make run` Devices
|
||||
|
||||
- **Keep overhead to a minimum**. Tests should be fast to both write
|
||||
and run. Ideally, the developer should _want_ to add tests early in
|
||||
the development cycle because they instinctively feel that that is
|
||||
the quickest route to arrive at a correct and robust implementation.
|
||||
Some tests only require a single DUT. These can therefore be run
|
||||
against an Infix image started from `make run`. This requires that the
|
||||
instance is configured to use TAP networking.
|
||||
|
||||
- **Both physical and virtual hardware matters**. Infix is primarily
|
||||
deployed on physical hardware, so being able to run the test suite
|
||||
on real devices is crucial to guarantee a high quality product. At
|
||||
the same time, there is much value in running the same suite on
|
||||
virtual hardware, as it makes it easy to catch regressions early.
|
||||
It is also much more practical and economical to build large virtual
|
||||
networks than physical ones.
|
||||
When the instance is running, you can open a separate terminal and run
|
||||
the subset of the test suite that can be mapped to it:
|
||||
|
||||
- **Avoid CLI scipting & scraping**. Reliably interacting with a DUT
|
||||
over a serial line in a robust way is _very_ hard to get right.
|
||||
Given that we have a proper API (RESTCONF), we should leverage that
|
||||
when testing. Front-ends can be tested by other means.
|
||||
|
||||
|
||||
Architectural Overview
|
||||
----------------------
|
||||
|
||||

|
||||
|
||||
The test system is made up of several independent components, which
|
||||
are typically used in concert to run a full test suite.
|
||||
|
||||
### Test Cases
|
||||
|
||||
A test case is an executable, receiving the physical topology as a
|
||||
positional argument, which produces [TAP][] compliant output on its
|
||||
`stdout`. I.e., it is executed in the following manner:
|
||||
|
||||
test-case [OPTS] <physical-topology>
|
||||
|
||||
Test cases are typically written in Python, using the
|
||||
[Infamy](#infamy) library. Ultimately though, it can be implemented
|
||||
in any language, as long as it matches the calling convention above.
|
||||
|
||||
### Infamy
|
||||
|
||||
Rather than having each test case come up with its own implementation
|
||||
of how to map topologies, how to push NETCONF data to a device, etc.,
|
||||
we provide a library of functions to take care of all that, dubbed
|
||||
"Infamy". When adding a new test case, ask yourself if any parts of
|
||||
it might belong in Infamy as a generalized component that can be
|
||||
reused by other tests.
|
||||
|
||||
Some of the core functions provided by Infamy are:
|
||||
|
||||
- Mapping a logical topology to a physical one
|
||||
- Finding and attaching to a device over an Ethernet interface, using
|
||||
NETCONF
|
||||
- Pushing/pulling NETCONF data to/from a device
|
||||
- Generating TAP compliant output
|
||||
|
||||
### 9PM
|
||||
|
||||
To run multiple tests, we employ [9PM][]. It let's us define test
|
||||
suites as simple YAML files. Suites can also be hierarchically
|
||||
structured, with a suite being made up of other suites, etc.
|
||||
|
||||
It also validates the TAP output, making sure to catch early exits
|
||||
from a case, and produces a nice summary report.
|
||||
|
||||
### `/test/env`
|
||||
|
||||
A good way to ensure that nobody ever runs the test suite is to make
|
||||
it _really_ hard to do so. `/test/env`'s job is instead to make it
|
||||
very _easy_ to create a reproducible environment in which tests can be
|
||||
executed.
|
||||
|
||||
Several technologies are leveraged to accomplish this:
|
||||
|
||||
- **Containers**: The entire execution is optionally done inside a
|
||||
standardized container environment, using either `podman` or
|
||||
`docker`. This ensures that the software needed to run the test
|
||||
suite is always available, no matter which distribution the user is
|
||||
running on their machine.
|
||||
|
||||
- **Python Virtual Environments**: To make sure that the expected
|
||||
versions of all Python packages are available, the execution is
|
||||
wrapped inside a `venv`. This is true for containerized executions,
|
||||
where the container comes with a pre-installed environment, but it
|
||||
can also be sourced from the host system when running outside of the
|
||||
container.
|
||||
|
||||
- **Virtual Test Topology**: Using [Qeneth][], the environment can
|
||||
optionally be started with a virtual topology of DUTs to run the
|
||||
tests on.
|
||||
$ make TEST_MODE=run test
|
||||
|
||||
|
||||
Interactive Usage
|
||||
-----------------
|
||||
|
||||
Some tests only require a single DUT. These can therefore be run
|
||||
against an Infix image started from `make run`. When the instance is
|
||||
running, you can open a separate terminal and run `make test-run`, to
|
||||
run the subset of the test suite that can be mapped to it.
|
||||
|
||||
Both `make test` and `make test-run` targets have a respective target
|
||||
with a `-sh` suffix. These can be used to start an interactive session
|
||||
in the reproducible environment, which is usually much easier to work
|
||||
with during a debugging session.
|
||||
|
||||
Inside the reproducible environment, a wrapper for Qeneth is created for
|
||||
the running network's directory. E.g., calling `qeneth status` inside a
|
||||
`make test-sh` environment show the expected status information.
|
||||
|
||||
> **Note:** for more information and help writing a test, see the below
|
||||
> [Quick Start Guide][].
|
||||
|
||||
|
||||
Physical and Logical Topologies
|
||||
-------------------------------
|
||||
|
||||
Imagine that we want to create a test with three DUTs; one acting as a
|
||||
DHCP server, and the other two as DHCP clients - with all three having
|
||||
a management connection to the host PC running the test. In other
|
||||
words, the test requires a _logical_ topology like the one below.
|
||||
|
||||
<img align="right" src="img/testing-log.dot.svg" alt="Example Logical Topology">
|
||||
|
||||
```dot
|
||||
graph "dhcp-client-server" {
|
||||
host [
|
||||
label="host | { <c1> c1 | <srv> srv | <c2> c2 }",
|
||||
kind="controller",
|
||||
];
|
||||
|
||||
server [
|
||||
label="{ <mgmt> mgmt } | server | { <c1> c1 | <c2> c2 }",
|
||||
kind="infix",
|
||||
];
|
||||
client1 [
|
||||
label="{ <mgmt> mgmt } | client1 | { <srv> srv }",
|
||||
kind="infix",
|
||||
];
|
||||
client2 [
|
||||
label="{ <mgmt> mgmt } | client2 | { <srv> srv }",
|
||||
kind="infix",
|
||||
];
|
||||
|
||||
host:srv -- server:mgmt
|
||||
host:c1 -- client1:mgmt
|
||||
host:c2 -- client2:mgmt
|
||||
|
||||
server:c1 -- client1:srv;
|
||||
server:c2 -- client2:srv;
|
||||
}
|
||||
```
|
||||
|
||||
When running in a virtualized environment, one could simply create a
|
||||
setup that matches the test's logical topology. But in scenarios when
|
||||
devices are physical systems, connected by real copper cables, this is
|
||||
not possible (unless you have some wicked L1 relay matrix thingy).
|
||||
|
||||
Instead, the test implementation does not concern itself with the
|
||||
exact nodes used to run the test, only that the _logical_ topology can
|
||||
be _mapped_ to some subset of the _physical_ topology. In
|
||||
mathematical terms, the physical topology must contain a subgraph that
|
||||
is _isomorphic_ to the logical topology.
|
||||
|
||||
Standing on the shoulders of giants (i.e. people with mathematics
|
||||
degrees), we can deploy well-known algorithms to find such subgraphs.
|
||||
Continuing our example, let's say we want to run our DHCP test on the
|
||||
_physical_ topology below.
|
||||
|
||||
<img align="right" src="img/testing-phy.dot.svg" alt="Example Physical Topology">
|
||||
|
||||
```dot
|
||||
graph "quad-ring" {
|
||||
host [
|
||||
label="host | { <d1a> d1a | <d1b> d1b | <d1c> d1c | <d2a> d2a | <d2b> d2b | <d2c> d2c | <d3a> d3a | <d3b> d3b | <d3c> d3c | <d4a> d4a | <d4b> d4b | <d4c> d4c }",
|
||||
kind="controller",
|
||||
];
|
||||
|
||||
dut1 [
|
||||
label="{ <e1> e1 | <e2> e2 | <e3> e3 } | dut1 | { <e4> e4 | <e5> e5 }",
|
||||
kind="infix",
|
||||
];
|
||||
dut2 [
|
||||
label="{ <e1> e1 | <e2> e2 | <e3> e3 } | dut2 | { <e4> e4 | <e5> e5 }",
|
||||
kind="infix",
|
||||
];
|
||||
dut3 [
|
||||
label="{ <e1> e1 | <e2> e2 | <e3> e3 } | dut3 | { <e4> e4 | <e5> e5 }",
|
||||
kind="infix",
|
||||
];
|
||||
dut4 [
|
||||
label="{ <e1> e1 | <e2> e2 | <e3> e3 } | dut4 | { <e4> e4 | <e5> e5 }",
|
||||
kind="infix",
|
||||
];
|
||||
|
||||
host:d1a -- dut1:e1
|
||||
host:d1b -- dut1:e2
|
||||
host:d1c -- dut1:e3
|
||||
|
||||
host:d2a -- dut2:e1
|
||||
host:d2b -- dut2:e2
|
||||
host:d2c -- dut2:e3
|
||||
|
||||
host:d3a -- dut3:e1
|
||||
host:d3b -- dut3:e2
|
||||
host:d3c -- dut3:e3
|
||||
|
||||
host:d4a -- dut4:e1
|
||||
host:d4b -- dut4:e2
|
||||
host:d4c -- dut4:e3
|
||||
|
||||
dut1:e5 -- dut2:e4
|
||||
dut2:e5 -- dut3:e4
|
||||
dut3:e5 -- dut4:e4
|
||||
dut4:e5 -- dut1:e4
|
||||
}
|
||||
```
|
||||
|
||||
Our test (in fact, all tests) receives the physical topology as an
|
||||
input parameter, and then maps the desired logical topology onto it,
|
||||
producing a mapping from logical nodes and ports to their physical
|
||||
counterparts.
|
||||
|
||||
```dot
|
||||
{
|
||||
"client1": "dut1",
|
||||
"client1:mgmt": "dut1:e1",
|
||||
"client1:srv": "dut1:e4",
|
||||
"client2": "dut3",
|
||||
"client2:mgmt": "dut3:e2",
|
||||
"client2:srv": "dut3:e5",
|
||||
"host": "host",
|
||||
"host:c1": "host:d1a",
|
||||
"host:c2": "host:d3b",
|
||||
"host:srv": "host:d4c",
|
||||
"server": "dut4",
|
||||
"server:c1": "dut4:e5",
|
||||
"server:c2": "dut4:e4",
|
||||
"server:mgmt": "dut4:e3"
|
||||
}
|
||||
```
|
||||
|
||||
With this information, the test knows that, in this particular
|
||||
environment, the server should be managed via the port called `d4c` on
|
||||
the node called `host`; that the port connected to the server on
|
||||
`client1` is `e4` on `dut1`, etc. Thereby separating the
|
||||
implementation of the test from any specific physical setup.
|
||||
|
||||
Testcases are not required to use a logical topology; they may choose
|
||||
to accept whatever physical topology its given, and dynamically
|
||||
determine the DUTs to use for testing. As an example, an STP test
|
||||
could accept an arbitrary physical topology, run the STP algorithm on
|
||||
it offline, enable STP on all DUTs, and then verify that the resulting
|
||||
spanning tree matches the expected one.
|
||||
|
||||
|
||||
Quick Start Guide
|
||||
-----------------
|
||||
|
||||
When developing a test, instead of blindly coding in Python and running
|
||||
`make test` over and over, start a test shell:
|
||||
When developing and debugging tests, the overhead of repeatedly
|
||||
setting up and tearing down the test environment can quickly start to
|
||||
weigh you down. In these situation, you can start an interactive test
|
||||
environment:
|
||||
|
||||
$ make test-sh
|
||||
Info: Generating topology
|
||||
@@ -299,13 +69,28 @@ When developing a test, instead of blindly coding in Python and running
|
||||
Info: Launching dut4
|
||||
11:42:52 infamy0:test #
|
||||
|
||||
The example above uses the default mode (`qeneth`), but the `host` and
|
||||
`run` modes also support the `test-sh` target.
|
||||
|
||||
It takes a little while to start up, but then we have a shell prompt
|
||||
inside the container running Infamy. It's a very limited environment,
|
||||
but it has enough to easily run single tests, connect to the virtual
|
||||
devices, and *step* your code. Let's run a test:
|
||||
devices, and *step* your code.
|
||||
|
||||
|
||||
### Running Subsets of Tests
|
||||
|
||||
Each test case is a separate executable, which can be run without
|
||||
arguments:
|
||||
|
||||
11:42:53 infamy0:test # ./case/infix_dhcp/dhcp_basic.py
|
||||
|
||||
To run a suite of tests, e.g., only the DHCP client tests, pass the
|
||||
suite as an argument to [9PM][]:
|
||||
|
||||
11:42:53 infamy0:test # ./9pm/9pm.py case/infix_dhcp/all.yaml
|
||||
|
||||
|
||||
### Connecting to Infamy
|
||||
|
||||
The test system runs in a Docker container, so to get a shell prompt in
|
||||
@@ -313,6 +98,7 @@ The test system runs in a Docker container, so to get a shell prompt in
|
||||
with a helper script for this:
|
||||
|
||||
$ ./test/shell
|
||||
11:42:53 infamy0:test #
|
||||
|
||||
By default it connect to the latest started Infamy instance. If you for
|
||||
some reason run multiple instances of Infamy the `shell` script takes an
|
||||
@@ -320,6 +106,8 @@ optional argument "system", which is the hostname of the container you
|
||||
want to connect to:
|
||||
|
||||
$ ./test/shell infamy2
|
||||
11:42:53 infamy2:test #
|
||||
|
||||
|
||||
### Connecting to a DUT
|
||||
|
||||
@@ -369,10 +157,47 @@ You can also connect to the console of a DUT from within a `shell`:
|
||||
> the BusyBox version so you press 'e' + enter instead of 'q' to quit.
|
||||
|
||||
|
||||
### Debug a Test
|
||||
### Attaching to MACVLANs
|
||||
|
||||
First, add a Python `breakpoint()` to your test and run it from your
|
||||
`make test-sh` Infamy instance:
|
||||
To fully isolate the host's interfaces from one another, many tests
|
||||
will stack a MACVLAN on an interface, which is then placed in a
|
||||
separate network namespace.
|
||||
|
||||
It is often useful to attach to those namespaces, so that you can
|
||||
interactively inject traffic into the test setup.
|
||||
|
||||
First, connect to the Infamy instance in question:
|
||||
|
||||
$ ./test/shell
|
||||
11:43:19 infamy0:test #
|
||||
|
||||
Then, attach to the MACVLAN namespace by running the `nsenter` helper
|
||||
script from the test directory, supplying the base interface name as
|
||||
the first argument:
|
||||
|
||||
11:43:19 infamy0:test # ./nsenter d1b
|
||||
11:43:20 infamy0(d1b):test #
|
||||
|
||||
By default, an interactive shell is started, but you can also supply
|
||||
another command:
|
||||
|
||||
11:43:19 infamy0:test # ./nsenter d1b ip -br addr
|
||||
lo UNKNOWN 127.0.0.1/8 ::1/128
|
||||
iface@if7 UP 10.0.0.1/24 fe80::38d0:88ff:fe77:b7cd/64
|
||||
|
||||
You can now freely debug the network activity of your test and the
|
||||
responses from the DUT.
|
||||
|
||||
|
||||
### Using the Python Debugger
|
||||
|
||||
The built in `breakpoint()` function in Python is very useful when you
|
||||
want to run a test case to a certain point at which you might want to
|
||||
interactively inspect either the test's or the device's state.
|
||||
|
||||
Simply insert a call to `breakpoint()` at the point of interest in
|
||||
your test and run it as normal. Once Python executes the call, it will
|
||||
drop you into the Python debugger:
|
||||
|
||||
11:42:58 infamy0:test # ./case/infix_dhcp/dhcp_basic.py
|
||||
# Starting (2024-02-10 11:42:59)
|
||||
@@ -382,32 +207,15 @@ First, add a Python `breakpoint()` to your test and run it from your
|
||||
> /home/jocke/src/infix/test/case/infix_dhcp/dhcp_basic.py(44)<module>()
|
||||
(Pdb)
|
||||
|
||||
You are now in the Python debugger, Pdb.
|
||||
At this point you have full access to the test's state, but it is also
|
||||
an opportunity to inspect the state of the DUTs (e.g. via their
|
||||
console or over SSH).
|
||||
|
||||
### Debug a Test in a MacVLAN
|
||||
It is also possible to run a test under Pdb from the get-go, if you
|
||||
want to setup breakpoints without modifying the source, or simply step
|
||||
through the code:
|
||||
|
||||
Most tests use some sort of network connection to the DUTs. From a test
|
||||
shell you cannot see or debug that connection by default. So you need
|
||||
to employ a little trick.
|
||||
|
||||
Open another terminal, and start a test shell in your Infamy instance:
|
||||
|
||||
$ ./test/shell
|
||||
11:43:19 infamy0:test #
|
||||
|
||||
MacVLAN interfaces created by Infamy run in another network namespace,
|
||||
to enter it we can look for a sleeper process:
|
||||
|
||||
11:43:20 infamy0:test # nsenter -n U -t $(pidof sleep infinity)
|
||||
|
||||
You are now one step further down:
|
||||
|
||||
infamy0:/home/jocke/src/infix/test# ip -br a
|
||||
lo UNKNOWN 127.0.0.1/8 ::1/128
|
||||
iface@if7 UP 10.0.0.1/24 fe80::38d0:88ff:fe77:b7cd/64
|
||||
|
||||
You can now freely debug the network activity of your test and the
|
||||
responses from the DUT.
|
||||
11:42:58 infamy0:test # python -m pdb case/infix_dhcp/dhcp_basic.py
|
||||
|
||||
|
||||
### Deterministic Topology Mappings
|
||||
@@ -451,4 +259,3 @@ the whole suite) with identical topology mappings:
|
||||
|
||||
[9PM]: https://github.com/rical/9pm
|
||||
[Qeneth]: https://github.com/wkz/qeneth
|
||||
[TAP]: https://testanything.org/
|
||||
|
||||
@@ -10,7 +10,7 @@ GENCERT_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/gencert
|
||||
GENCERT_LICENSE = ISC
|
||||
GENCERT_LICENSE_FILES = LICENSE
|
||||
GENCERT_REDISTRIBUTE = NO
|
||||
GENCERT_DEPENDENCIES = openssl
|
||||
GENCERT_DEPENDENCIES = host-pkgconf libopenssl
|
||||
GENCERT_AUTORECONF = YES
|
||||
|
||||
define GENCERT_CONF_ENV
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
task [2] run-parts /cfg/user-scripts.d -- Running user startup scripts
|
||||
@@ -1,3 +1,20 @@
|
||||
From 50fafdc63dc3d6152e2494cc693871e9bd04bfe5 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Tue, 5 Mar 2024 09:41:46 +0100
|
||||
Subject: [PATCH 1/2] iplink_bridge: add mcast_flood_always bridge option
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
- Break out boolopt handling to simplify parsing and setting
|
||||
- Add set/get support for mcast_flood_always
|
||||
- Add get support for mst_enabled
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
include/uapi/linux/if_bridge.h | 1 +
|
||||
ip/iplink_bridge.c | 75 +++++++++++++++++++++++++---------
|
||||
man/man8/ip-link.8.in | 12 ++++++
|
||||
3 files changed, 68 insertions(+), 20 deletions(-)
|
||||
|
||||
diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h
|
||||
index adb75f7d..611ab413 100644
|
||||
--- a/include/uapi/linux/if_bridge.h
|
||||
@@ -153,3 +170,6 @@ index 6764a9ad..8f77c5ad 100644
|
||||
.BI mcast_snooping " MULTICAST_SNOOPING "
|
||||
- turn multicast snooping on
|
||||
.RI ( MULTICAST_SNOOPING " > 0) "
|
||||
--
|
||||
2.34.1
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
From 226a8b532fc074165ba76d71c4488629daed2625 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Mon, 20 May 2024 21:35:11 +0200
|
||||
Subject: [PATCH 2/2] ipaddress: accept symbolic names also for show
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
This is a follow-up to 709063e, which in turn was a follow-up to
|
||||
bdb8d85, to add support for 'ip addr show proto static', where
|
||||
'static' is defined in /etc/iproute2/rt_addrprotos.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
ip/ipaddress.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ip/ipaddress.c b/ip/ipaddress.c
|
||||
index e536912f..19900157 100644
|
||||
--- a/ip/ipaddress.c
|
||||
+++ b/ip/ipaddress.c
|
||||
@@ -2217,7 +2217,7 @@ static int ipaddr_list_flush_or_save(int argc, char **argv, int action)
|
||||
__u8 proto;
|
||||
|
||||
NEXT_ARG();
|
||||
- if (get_u8(&proto, *argv, 0))
|
||||
+ if (rtnl_addrprot_a2n(&proto, *argv))
|
||||
invarg("\"proto\" value is invalid\n", *argv);
|
||||
filter.have_proto = true;
|
||||
filter.proto = proto;
|
||||
--
|
||||
2.34.1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 79abf7872e35080ca5b519bb4ad2acc1fbd96e8c Mon Sep 17 00:00:00 2001
|
||||
From: Christian Marangi <ansuelsmth@gmail.com>
|
||||
Date: Thu, 25 Jan 2024 21:36:59 +0100
|
||||
Subject: [PATCH 01/28] net: phy: add support for PHY LEDs polarity modes
|
||||
Subject: [PATCH 01/30] net: phy: add support for PHY LEDs polarity modes
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Add support for PHY LEDs polarity modes. Some PHY require LED to be set
|
||||
@@ -22,7 +22,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
|
||||
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
|
||||
Link: https://lore.kernel.org/r/20240125203702.4552-4-ansuelsmth@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
drivers/net/phy/phy_device.c | 16 ++++++++++++++++
|
||||
include/linux/phy.h | 22 ++++++++++++++++++++++
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 424ddb5d5d8ce06c578cc08cbedf519c42dd8b69 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Mon, 4 Dec 2023 11:08:11 +0100
|
||||
Subject: [PATCH 02/28] net: mvmdio: Support setting the MDC frequency on XSMI
|
||||
Subject: [PATCH 02/30] net: mvmdio: Support setting the MDC frequency on XSMI
|
||||
controllers
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
@@ -14,7 +14,6 @@ Reviewed-by: Andrew Lunn <andrew@lunn.ch>
|
||||
Tested-by: Andrew Lunn <andrew@lunn.ch>
|
||||
Link: https://lore.kernel.org/r/20231204100811.2708884-4-tobias@waldekranz.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
drivers/net/ethernet/marvell/mvmdio.c | 44 +++++++++++++++++++++++++++
|
||||
1 file changed, 44 insertions(+)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 337cf21833cef6ab456a8562338de88ab1a84ead Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Thu, 14 Dec 2023 14:50:23 +0100
|
||||
Subject: [PATCH 03/28] net: dsa: mv88e6xxx: Create API to read a single stat
|
||||
Subject: [PATCH 03/30] net: dsa: mv88e6xxx: Create API to read a single stat
|
||||
counter
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
@@ -16,7 +16,6 @@ Reviewed-by: Vladimir Oltean <vladimir.oltean@nxp.com>
|
||||
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Signed-off-by: David S. Miller <davem@davemloft.net>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
drivers/net/dsa/mv88e6xxx/chip.c | 162 ++++++++++++++++++-------------
|
||||
drivers/net/dsa/mv88e6xxx/chip.h | 27 +++---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From dbdbfd57998b4f8224146ac94cd5d0a577326087 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Thu, 14 Dec 2023 14:50:25 +0100
|
||||
Subject: [PATCH 04/28] net: dsa: mv88e6xxx: Give each hw stat an ID
|
||||
Subject: [PATCH 04/30] net: dsa: mv88e6xxx: Give each hw stat an ID
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
With the upcoming standard counter group support, we are no longer
|
||||
@@ -16,7 +16,6 @@ Reviewed-by: Vladimir Oltean <vladimir.oltean@nxp.com>
|
||||
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Signed-off-by: David S. Miller <davem@davemloft.net>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
drivers/net/dsa/mv88e6xxx/chip.c | 138 +++++++++++++++++--------------
|
||||
1 file changed, 75 insertions(+), 63 deletions(-)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 26815572016d9851ec6362f23746035fb933acf3 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Thu, 14 Dec 2023 14:50:26 +0100
|
||||
Subject: [PATCH 05/28] net: dsa: mv88e6xxx: Add "eth-mac" counter group
|
||||
Subject: [PATCH 05/30] net: dsa: mv88e6xxx: Add "eth-mac" counter group
|
||||
support
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
@@ -12,7 +12,6 @@ Reviewed-by: Vladimir Oltean <vladimir.oltean@nxp.com>
|
||||
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Signed-off-by: David S. Miller <davem@davemloft.net>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
drivers/net/dsa/mv88e6xxx/chip.c | 39 ++++++++++++++++++++++++++++++++
|
||||
1 file changed, 39 insertions(+)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From cc5badac76e76692eb516a5c086fcf54a5a93080 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Thu, 14 Dec 2023 14:50:27 +0100
|
||||
Subject: [PATCH 06/28] net: dsa: mv88e6xxx: Limit histogram counters to
|
||||
Subject: [PATCH 06/30] net: dsa: mv88e6xxx: Limit histogram counters to
|
||||
ingress traffic
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
@@ -27,7 +27,6 @@ Reviewed-by: Andrew Lunn <andrew@lunn.ch>
|
||||
Reviewed-by: Vladimir Oltean <vladimir.oltean@nxp.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Signed-off-by: David S. Miller <davem@davemloft.net>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
drivers/net/dsa/mv88e6xxx/chip.c | 6 +++---
|
||||
drivers/net/dsa/mv88e6xxx/global1.c | 7 +++----
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 35ecc84237bfccbe5b93c0aef6c07cc506d43070 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Thu, 14 Dec 2023 14:50:28 +0100
|
||||
Subject: [PATCH 07/28] net: dsa: mv88e6xxx: Add "rmon" counter group support
|
||||
Subject: [PATCH 07/30] net: dsa: mv88e6xxx: Add "rmon" counter group support
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Report the applicable subset of an mv88e6xxx port's counters using
|
||||
@@ -11,7 +11,6 @@ Reviewed-by: Vladimir Oltean <vladimir.oltean@nxp.com>
|
||||
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Signed-off-by: David S. Miller <davem@davemloft.net>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
drivers/net/dsa/mv88e6xxx/chip.c | 42 ++++++++++++++++++++++++++++++++
|
||||
1 file changed, 42 insertions(+)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
From 7750d0a5bfae46ab783e8e7087b70175860f808c Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Thu, 16 Nov 2023 19:44:32 +0100
|
||||
Subject: [PATCH 09/28] net: dsa: mv88e6xxx: Add LED infrastructure
|
||||
Subject: [PATCH 09/30] net: dsa: mv88e6xxx: Add LED infrastructure
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Parse DT for LEDs and register them for devices that support it,
|
||||
though no actual implementations exist yet.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
drivers/net/dsa/mv88e6xxx/Makefile | 1 +
|
||||
drivers/net/dsa/mv88e6xxx/chip.c | 5 +-
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 63fcd626fdb108d2ac07cb0aa16b91fc43894bff Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Thu, 16 Nov 2023 21:59:35 +0100
|
||||
Subject: [PATCH 10/28] net: dsa: mv88e6xxx: Add LED support for 6393X
|
||||
Subject: [PATCH 10/30] net: dsa: mv88e6xxx: Add LED support for 6393X
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Trigger support:
|
||||
@@ -9,7 +9,7 @@ Trigger support:
|
||||
- "timer"
|
||||
- "netdev"
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
drivers/net/dsa/mv88e6xxx/chip.c | 1 +
|
||||
drivers/net/dsa/mv88e6xxx/leds.c | 226 +++++++++++++++++++++++++++++++
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 0bc6f19d18d7c0e374c824bcc86433b04210a378 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 12 Mar 2024 10:27:24 +0100
|
||||
Subject: [PATCH 11/28] net: dsa: mv88e6xxx: Fix timeout on waiting for PPU on
|
||||
Subject: [PATCH 11/30] net: dsa: mv88e6xxx: Fix timeout on waiting for PPU on
|
||||
6393X
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
@@ -9,7 +9,7 @@ In a multi-chip setup, delays of up to 750ms are observed before the
|
||||
device (6393X) signals completion of PPU initialization (Global 1,
|
||||
register 0, bit 15). Therefore, increase the timeout threshold to 1s.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
drivers/net/dsa/mv88e6xxx/chip.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From fe60e3ad1e3b43a924c311658b15a1106e813661 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 16 Jan 2024 16:00:55 +0100
|
||||
Subject: [PATCH 12/28] net: dsa: Support MDB memberships whose L2 addresses
|
||||
Subject: [PATCH 12/30] net: dsa: Support MDB memberships whose L2 addresses
|
||||
overlap
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
@@ -30,7 +30,7 @@ needed to do this is already in place, since it is also needed on CPU
|
||||
and DSA ports. Thus, "implement" this by simply removing the guards
|
||||
which previously skipped reference countung on user ports.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
net/dsa/switch.c | 16 ----------------
|
||||
1 file changed, 16 deletions(-)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 5f5bf4a50e98c5aff05fe85994a981253ae85987 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 19 Sep 2023 18:38:10 +0200
|
||||
Subject: [PATCH 13/28] net: phy: marvell10g: Support firmware loading on
|
||||
Subject: [PATCH 13/30] net: phy: marvell10g: Support firmware loading on
|
||||
88X3310
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
@@ -12,7 +12,7 @@ We have no choice but to bail out of the probe if firmware is not
|
||||
available, as the device does not have any built-in image on which to
|
||||
fall back.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
drivers/net/phy/marvell10g.c | 161 +++++++++++++++++++++++++++++++++++
|
||||
1 file changed, 161 insertions(+)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 27d5241d3ddf70d65af5f4dd029f99f0fea866a5 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 21 Nov 2023 20:15:24 +0100
|
||||
Subject: [PATCH 14/28] net: phy: marvell10g: Fix power-up when strapped to
|
||||
Subject: [PATCH 14/30] net: phy: marvell10g: Fix power-up when strapped to
|
||||
start powered down
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
@@ -9,7 +9,7 @@ On devices which are hardware strapped to start powered down (PDSTATE
|
||||
== 1), make sure that we clear the power-down bit on all units
|
||||
affected by this setting.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
drivers/net/phy/marvell10g.c | 18 +++++++++++++++---
|
||||
1 file changed, 15 insertions(+), 3 deletions(-)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From e389ee690ce56ef20863aeabdf389bed842d7f42 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 15 Nov 2023 20:58:42 +0100
|
||||
Subject: [PATCH 15/28] net: phy: marvell10g: Add LED support for 88X3310
|
||||
Subject: [PATCH 15/30] net: phy: marvell10g: Add LED support for 88X3310
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Pickup the LEDs from the state in which the hardware reset or
|
||||
@@ -18,7 +18,7 @@ Trigger support:
|
||||
- "netdev": Offload link or duplex information to the solid behavior;
|
||||
tx and/or rx activity to blink behavior.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
drivers/net/phy/marvell10g.c | 422 +++++++++++++++++++++++++++++++++++
|
||||
1 file changed, 422 insertions(+)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 3113b64290b5b7b20c294535c12ea4c732a02f51 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 12 Dec 2023 09:51:05 +0100
|
||||
Subject: [PATCH 16/28] net: phy: marvell10g: Support LEDs tied to a single
|
||||
Subject: [PATCH 16/30] net: phy: marvell10g: Support LEDs tied to a single
|
||||
media side
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
@@ -15,7 +15,7 @@ the offloading of the "netdev" trigger, such that LEDs attached to the
|
||||
RJ45 jack only lights up when a copper link is established, and vice
|
||||
versa for the SFP cage.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
drivers/net/phy/marvell10g.c | 23 ++++++++++++++++++++++-
|
||||
1 file changed, 22 insertions(+), 1 deletion(-)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 2570c2350d3d614a31e14dd87d0e99e8fcb5096b Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Fri, 24 Nov 2023 23:29:55 +0100
|
||||
Subject: [PATCH 17/28] nvmem: layouts: onie-tlv: Let device probe even when
|
||||
Subject: [PATCH 17/30] nvmem: layouts: onie-tlv: Let device probe even when
|
||||
TLV is invalid
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
@@ -14,7 +14,7 @@ be successfully probed.
|
||||
Therefore, settle for reporting data corruption issues in the log, and
|
||||
simply refrain from registering any cells in those cases.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
drivers/nvmem/layouts/onie-tlv.c | 6 +++---
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 681ce95c15bff94ac83eb26db3c718594f8dfa16 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Mon, 4 Mar 2024 16:47:28 +0100
|
||||
Subject: [PATCH 18/28] net: bridge: avoid classifying unknown multicast as
|
||||
Subject: [PATCH 18/30] net: bridge: avoid classifying unknown multicast as
|
||||
mrouters_only
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
@@ -16,6 +16,7 @@ Because a multicast router should always receive both known and unknown
|
||||
multicast.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
include/uapi/linux/if_bridge.h | 1 +
|
||||
net/bridge/br.c | 5 +++++
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 2482848726a5b8185a04d90891693a1713f894b5 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Tue, 5 Mar 2024 06:44:41 +0100
|
||||
Subject: [PATCH 19/28] net: bridge: Ignore router ports when forwarding L2
|
||||
Subject: [PATCH 19/30] net: bridge: Ignore router ports when forwarding L2
|
||||
multicast
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
From 25efcdb13ae720ee662f4b6091b270e48ac4d375 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Thu, 21 Mar 2024 19:12:15 +0100
|
||||
Subject: [PATCH 20/28] net: dsa: Support EtherType based priority overrides
|
||||
Subject: [PATCH 20/30] net: dsa: Support EtherType based priority overrides
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
include/net/dsa.h | 4 ++++
|
||||
net/dsa/slave.c | 56 +++++++++++++++++++++++++++++++++++++++++++++--
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 213e9a1d9145723897fbba40fc4afc5b5f70c1de Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Fri, 22 Mar 2024 16:15:43 +0100
|
||||
Subject: [PATCH 21/28] net: dsa: mv88e6xxx: Support EtherType based priority
|
||||
Subject: [PATCH 21/30] net: dsa: mv88e6xxx: Support EtherType based priority
|
||||
overrides
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
drivers/net/dsa/mv88e6xxx/chip.c | 64 +++++++++++++++++++++++++++++
|
||||
drivers/net/dsa/mv88e6xxx/chip.h | 21 ++++++++++
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 1c1c81864c9803831c2d5b7df0d7da28e566f3b2 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 27 Mar 2024 10:10:19 +0100
|
||||
Subject: [PATCH 22/28] net: phy: Do not resume PHY when attaching
|
||||
Subject: [PATCH 22/30] net: phy: Do not resume PHY when attaching
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
The PHY should not start negotiating with its link-partner until
|
||||
@@ -16,7 +16,7 @@ probing (e.g. DSA) would end up with a physical link being
|
||||
established, even though the corresponding interface was still
|
||||
administratively down.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
drivers/net/phy/phy_device.c | 1 -
|
||||
1 file changed, 1 deletion(-)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From b53e5007cd5be42eb5d9a510d63f89c7c6edd9b6 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 27 Mar 2024 15:52:43 +0100
|
||||
Subject: [PATCH 23/28] net: dsa: mv88e6xxx: Improve indirect register access
|
||||
Subject: [PATCH 23/30] net: dsa: mv88e6xxx: Improve indirect register access
|
||||
perf on 6393
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
@@ -14,7 +14,7 @@ Therefore, add a new set of SMI operations which remaps accesses to
|
||||
such registers to the corresponding directly addressable register. All
|
||||
other accesses use the regular indirect interface.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
drivers/net/dsa/mv88e6xxx/chip.c | 7 +++
|
||||
drivers/net/dsa/mv88e6xxx/global1.h | 3 ++
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From f53652205b1c044fa00f896d6422c82ac808f16a Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Thu, 4 Apr 2024 16:36:30 +0200
|
||||
Subject: [PATCH 24/28] net: bridge: drop delay for applying strict multicast
|
||||
Subject: [PATCH 24/30] net: bridge: drop delay for applying strict multicast
|
||||
filtering
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
@@ -19,6 +19,7 @@ A proper fix for upstreaming could be to add a knob to disable the delay.
|
||||
[2]: https://lore.kernel.org/netdev/20240127175033.9640-1-linus.luessing@c0d3.blue/
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
net/bridge/br_multicast.c | 42 +++++++--------------------------------
|
||||
net/bridge/br_private.h | 4 +---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 56208d4cba23496c09f69961a32b1b9662f06aef Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Mon, 22 Apr 2024 23:18:01 +0200
|
||||
Subject: [PATCH 25/28] net: dsa: mv88e6xxx: Honor ports being managed via
|
||||
Subject: [PATCH 25/30] net: dsa: mv88e6xxx: Honor ports being managed via
|
||||
in-band-status
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
@@ -14,7 +14,7 @@ This state is the default set up by mv88e6xxx_port_setup_mac(), so all
|
||||
we have to do is to make the phylink MAC callbacks no-ops in cases
|
||||
when in-band-status is being used.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
drivers/net/dsa/mv88e6xxx/chip.c | 6 ++++++
|
||||
1 file changed, 6 insertions(+)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 04023a449a6894c0ec46e246c52e94566b3be4b3 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 24 Apr 2024 21:35:26 +0200
|
||||
Subject: [PATCH 26/28] net: dsa: mv88e6xxx: Fix port policy config on 6393X
|
||||
Subject: [PATCH 26/30] net: dsa: mv88e6xxx: Fix port policy config on 6393X
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
mv88e6393x_port_policy_{read,write} expect the `pointer` argument to
|
||||
@@ -16,7 +16,7 @@ never enabled on DSA ports, which broke standalone port isolation in
|
||||
multichip switch trees made up of 6393X decices.
|
||||
|
||||
Fixes: 6584b26020fc ("net: dsa: mv88e6xxx: implement .port_set_policy for Amethyst")
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
drivers/net/dsa/mv88e6xxx/port.c | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 7529102af1693138349032b7d1339d91d2b6a578 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 24 Apr 2024 22:41:04 +0200
|
||||
Subject: [PATCH 27/28] net: dsa: mv88e6xxx: Limit rsvd2cpu policy to user
|
||||
Subject: [PATCH 27/30] net: dsa: mv88e6xxx: Limit rsvd2cpu policy to user
|
||||
ports on 6393X
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
@@ -30,7 +30,7 @@ switch would try to trap it back to the CPU. Given that the CPU is
|
||||
trusted, instead assume that it indeed meant for the packet to be
|
||||
forwarded like any other.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
drivers/net/dsa/mv88e6xxx/port.c | 31 +++++++++++++++++++++++++------
|
||||
1 file changed, 25 insertions(+), 6 deletions(-)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From c446111df33d9d6064395e37235bbbeee422358e Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Mon, 29 Apr 2024 15:14:51 +0200
|
||||
Subject: [PATCH 28/28] usb: core: adjust log level for unauthorized devices
|
||||
Subject: [PATCH 28/30] usb: core: adjust log level for unauthorized devices
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
The fact that a USB device currently is not authorized is not an error,
|
||||
@@ -9,6 +9,7 @@ so let's adjust the log level so these messages slip below radar for the
|
||||
commonly used 'quiet' log level.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
drivers/usb/core/driver.c | 4 ++--
|
||||
drivers/usb/core/generic.c | 2 +-
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
From eeeab4de57f77712ca5f920e03a68aa5e426784c Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 15 May 2024 13:50:58 +0200
|
||||
Subject: [PATCH 29/30] net: dsa: mv88e6xxx: Grab register lock during counter
|
||||
snapshotting
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
This was missing for the standard counter groups. Since no caller
|
||||
already holds the lock, opt for pushing the locking down into
|
||||
mv88e6xxx_stats_snapshot() rather than having it duplicated at each
|
||||
call site.
|
||||
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
drivers/net/dsa/mv88e6xxx/chip.c | 10 ++++++----
|
||||
1 file changed, 6 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
|
||||
index 29691280cff4..4916d8344a5a 100644
|
||||
--- a/drivers/net/dsa/mv88e6xxx/chip.c
|
||||
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
|
||||
@@ -950,10 +950,15 @@ static void mv88e6xxx_mac_link_up(struct dsa_switch *ds, int port,
|
||||
|
||||
static int mv88e6xxx_stats_snapshot(struct mv88e6xxx_chip *chip, int port)
|
||||
{
|
||||
+ int err;
|
||||
+
|
||||
if (!chip->info->ops->stats_snapshot)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
- return chip->info->ops->stats_snapshot(chip, port);
|
||||
+ mv88e6xxx_reg_lock(chip);
|
||||
+ err = chip->info->ops->stats_snapshot(chip, port);
|
||||
+ mv88e6xxx_reg_unlock(chip);
|
||||
+ return err;
|
||||
}
|
||||
|
||||
#define MV88E6XXX_HW_STAT_MAPPER(_fn) \
|
||||
@@ -1325,10 +1330,7 @@ static void mv88e6xxx_get_ethtool_stats(struct dsa_switch *ds, int port,
|
||||
struct mv88e6xxx_chip *chip = ds->priv;
|
||||
int ret;
|
||||
|
||||
- mv88e6xxx_reg_lock(chip);
|
||||
-
|
||||
ret = mv88e6xxx_stats_snapshot(chip, port);
|
||||
- mv88e6xxx_reg_unlock(chip);
|
||||
|
||||
if (ret < 0)
|
||||
return;
|
||||
--
|
||||
2.34.1
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
From 52eabcd2cfb30013d43ef72c2ec09c76043a0f4a Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Thu, 16 May 2024 14:51:54 +0200
|
||||
Subject: [PATCH 30/30] net: bridge: Differentiate MDB additions from
|
||||
modifications
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Before this change, the reception of an IGMPv3 report (and analogously
|
||||
for MLDv2) that adds a new group, would trigger two MDB RTM_NEWMDB
|
||||
notifications from br_ip4_multicast_igmp3_report():
|
||||
|
||||
1. In br_ip4_multicast_add_group() when creating the entry
|
||||
2. Directly at the end of br_ip4_multicast_igmp3_report(), as the new
|
||||
group is also deemed to be "changed"
|
||||
|
||||
The corresponding switchdev notifications generated by these present a
|
||||
problem for drivers wanting to reference count group memberships,
|
||||
since logically there is only a single reference to the group. Indeed,
|
||||
when the membership eventually times out, only a single RTM_DELMDB is
|
||||
generated.
|
||||
|
||||
Therefore, discriminate new groups from changes to existing groups by
|
||||
introducing a RTM_SETMDB events to be used in the latter scenario.
|
||||
|
||||
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
---
|
||||
include/uapi/linux/rtnetlink.h | 2 ++
|
||||
net/bridge/br_mdb.c | 4 ++--
|
||||
net/bridge/br_multicast.c | 8 ++++----
|
||||
3 files changed, 8 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h
|
||||
index 51c13cf9c5ae..fbc8fd1d5f2c 100644
|
||||
--- a/include/uapi/linux/rtnetlink.h
|
||||
+++ b/include/uapi/linux/rtnetlink.h
|
||||
@@ -134,6 +134,8 @@ enum {
|
||||
#define RTM_DELMDB RTM_DELMDB
|
||||
RTM_GETMDB = 86,
|
||||
#define RTM_GETMDB RTM_GETMDB
|
||||
+ RTM_SETMDB = 87,
|
||||
+#define RTM_SETMDB RTM_SETMDB
|
||||
|
||||
RTM_NEWNSID = 88,
|
||||
#define RTM_NEWNSID RTM_NEWNSID
|
||||
diff --git a/net/bridge/br_mdb.c b/net/bridge/br_mdb.c
|
||||
index 7305f5f8215c..a2882b126ecb 100644
|
||||
--- a/net/bridge/br_mdb.c
|
||||
+++ b/net/bridge/br_mdb.c
|
||||
@@ -724,7 +724,7 @@ static int br_mdb_replace_group_sg(const struct br_mdb_config *cfg,
|
||||
else
|
||||
del_timer(&pg->timer);
|
||||
|
||||
- br_mdb_notify(cfg->br->dev, mp, pg, RTM_NEWMDB);
|
||||
+ br_mdb_notify(cfg->br->dev, mp, pg, RTM_SETMDB);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -945,7 +945,7 @@ static int br_mdb_replace_group_star_g(const struct br_mdb_config *cfg,
|
||||
else
|
||||
del_timer(&pg->timer);
|
||||
|
||||
- br_mdb_notify(cfg->br->dev, mp, pg, RTM_NEWMDB);
|
||||
+ br_mdb_notify(cfg->br->dev, mp, pg, RTM_SETMDB);
|
||||
|
||||
if (br_multicast_should_handle_mode(brmctx, cfg->group.proto))
|
||||
br_multicast_star_g_handle_mode(pg, cfg->filter_mode);
|
||||
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
|
||||
index 8fcd8faf99f3..7ba1eee1a024 100644
|
||||
--- a/net/bridge/br_multicast.c
|
||||
+++ b/net/bridge/br_multicast.c
|
||||
@@ -618,7 +618,7 @@ static void br_multicast_fwd_src_handle(struct net_bridge_group_src *src)
|
||||
sg_mp = br_mdb_ip_get(src->br, &sg_key.addr);
|
||||
if (!sg_mp)
|
||||
return;
|
||||
- br_mdb_notify(src->br->dev, sg_mp, sg, RTM_NEWMDB);
|
||||
+ br_mdb_notify(src->br->dev, sg_mp, sg, RTM_SETMDB);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -885,7 +885,7 @@ static void br_multicast_port_group_expired(struct timer_list *t)
|
||||
|
||||
if (WARN_ON(!mp))
|
||||
goto out;
|
||||
- br_mdb_notify(br->dev, mp, pg, RTM_NEWMDB);
|
||||
+ br_mdb_notify(br->dev, mp, pg, RTM_SETMDB);
|
||||
}
|
||||
out:
|
||||
spin_unlock(&br->multicast_lock);
|
||||
@@ -2888,7 +2888,7 @@ static int br_ip4_multicast_igmp3_report(struct net_bridge_mcast *brmctx,
|
||||
break;
|
||||
}
|
||||
if (changed)
|
||||
- br_mdb_notify(brmctx->br->dev, mdst, pg, RTM_NEWMDB);
|
||||
+ br_mdb_notify(brmctx->br->dev, mdst, pg, RTM_SETMDB);
|
||||
unlock_continue:
|
||||
spin_unlock(&brmctx->br->multicast_lock);
|
||||
}
|
||||
@@ -3030,7 +3030,7 @@ static int br_ip6_multicast_mld2_report(struct net_bridge_mcast *brmctx,
|
||||
break;
|
||||
}
|
||||
if (changed)
|
||||
- br_mdb_notify(brmctx->br->dev, mdst, pg, RTM_NEWMDB);
|
||||
+ br_mdb_notify(brmctx->br->dev, mdst, pg, RTM_SETMDB);
|
||||
unlock_continue:
|
||||
spin_unlock(&brmctx->br->multicast_lock);
|
||||
}
|
||||
--
|
||||
2.34.1
|
||||
|
||||
@@ -226,7 +226,7 @@ sysrepoctl -s $SEARCH \
|
||||
-i infix-dhcp-client@2024-04-12.yang -g wheel -p 0660 \
|
||||
-i infix-shell-type@2023-08-21.yang -g wheel -p 0660 \
|
||||
-i infix-system@2024-04-12.yang -g wheel -p 0660 \
|
||||
-i infix-services@2024-04-08.yang -g wheel -p 0660 \
|
||||
-i infix-services@2024-05-21.yang -g wheel -p 0660 \
|
||||
-i ieee802-ethernet-interface@2019-06-21.yang -g wheel -p 0660 \
|
||||
-i infix-ethernet-interface@2024-02-27.yang -g wheel -p 0660 \
|
||||
-I "${INIT_DATA}"
|
||||
|
||||
@@ -185,6 +185,13 @@ if [ -n "$bridge" ]; then
|
||||
},
|
||||
"ietf-ip:ipv6": {
|
||||
"enabled": true
|
||||
},
|
||||
"infix-interfaces:bridge": {
|
||||
"multicast": {
|
||||
"snooping": true,
|
||||
"querier": "auto",
|
||||
"query-interval": 125
|
||||
}
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
@@ -1376,11 +1376,22 @@ static int netdag_gen_veth(struct dagger *net, struct lyd_node *dif,
|
||||
if (dagger_should_skip(net, ifname)) {
|
||||
err = dagger_add_dep(net, ifname, peer);
|
||||
if (err)
|
||||
return ERR_IFACE(cif, err, "Unable to add dep \"%s\" to %s",
|
||||
peer, ifname);
|
||||
return ERR_IFACE(cif, err, "Unable to add dep \"%s\" to %s", peer, ifname);
|
||||
} else {
|
||||
char ifname_args[64] = "", peer_args[64] = "";
|
||||
|
||||
dagger_skip_iface(net, peer);
|
||||
fprintf(ip, "link add dev %s down type veth peer %s\n", ifname, peer);
|
||||
|
||||
node = lydx_get_child(dif, "phys-address");
|
||||
if (node)
|
||||
snprintf(ifname_args, sizeof(ifname_args), "address %s", lyd_get_value(node));
|
||||
|
||||
node = lydx_find_by_name(lyd_parent(cif), "interface", peer);
|
||||
if (node && (node = lydx_get_child(node, "phys-address")))
|
||||
snprintf(peer_args, sizeof(peer_args), "address %s", lyd_get_value(node));
|
||||
|
||||
fprintf(ip, "link add dev %s %s type veth peer %s %s\n",
|
||||
ifname, ifname_args, peer, peer_args);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
SVC(web) \
|
||||
SVC(ttyd) \
|
||||
SVC(netbrowse) \
|
||||
SVC(userscripts) \
|
||||
SVC(all) /* must be last entry */
|
||||
|
||||
typedef enum {
|
||||
@@ -52,7 +53,7 @@ struct mdns_svc {
|
||||
};
|
||||
|
||||
static const struct srx_module_requirement reqs[] = {
|
||||
{ .dir = YANG_PATH_, .name = "infix-services", .rev = "2024-04-08" },
|
||||
{ .dir = YANG_PATH_, .name = "infix-services", .rev = "2024-05-21" },
|
||||
{ .dir = YANG_PATH_, .name = "ieee802-dot1ab-lldp", .rev = "2022-03-15" },
|
||||
{ .dir = YANG_PATH_, .name = "infix-lldp", .rev = "2023-08-23" },
|
||||
{ NULL }
|
||||
@@ -291,6 +292,21 @@ static int web_change(sr_session_ctx_t *session, uint32_t sub_id, const char *mo
|
||||
return put(cfg, srv);
|
||||
}
|
||||
|
||||
static int userscripts_change(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
|
||||
const char *xpath, sr_event_t event, unsigned request_id, void *_confd)
|
||||
{
|
||||
struct lyd_node *srv = NULL;
|
||||
sr_data_t *cfg;
|
||||
|
||||
cfg = get(session, event, xpath, &srv, "user-scripts", NULL);
|
||||
if (!cfg)
|
||||
return SR_ERR_OK;
|
||||
|
||||
svc_enadis(lydx_is_enabled(srv, "enabled"), none, "user-scripts");
|
||||
|
||||
return put(cfg, srv);
|
||||
}
|
||||
|
||||
int infix_services_init(struct confd *confd)
|
||||
{
|
||||
int rc;
|
||||
@@ -312,6 +328,8 @@ int infix_services_init(struct confd *confd)
|
||||
0, netbrowse_change, confd, &confd->sub);
|
||||
REGISTER_CHANGE(confd->session, "ieee802-dot1ab-lldp", "/ieee802-dot1ab-lldp:lldp",
|
||||
0, lldp_change, confd, &confd->sub);
|
||||
REGISTER_CHANGE(confd->session, "infix-services", "/infix-services:user-scripts",
|
||||
0, userscripts_change, confd, &confd->sub);
|
||||
|
||||
return SR_ERR_OK;
|
||||
fail:
|
||||
|
||||
+17
@@ -7,6 +7,10 @@ module infix-services {
|
||||
contact "kernelkit@googlegroups.com";
|
||||
description "Infix services, generic.";
|
||||
|
||||
revision 2024-05-21 {
|
||||
description "Add support for user-scripts.";
|
||||
reference "internal";
|
||||
}
|
||||
revision 2024-04-08 {
|
||||
description "Initial support for web services.";
|
||||
reference "internal";
|
||||
@@ -33,6 +37,19 @@ module infix-services {
|
||||
}
|
||||
}
|
||||
|
||||
container user-scripts {
|
||||
description "Run scripts in /cfg/user-scripts.d on system startup
|
||||
|
||||
CAUTION! Anyone who can write to this directory will be able to
|
||||
install a permanent backdoor on the system. This could include
|
||||
a remote attacker with brief window of access.";
|
||||
|
||||
leaf enabled {
|
||||
description "Enable or disable user script execution";
|
||||
type boolean;
|
||||
}
|
||||
}
|
||||
|
||||
container web {
|
||||
description "Web services";
|
||||
|
||||
@@ -2,28 +2,33 @@
|
||||
# shellcheck disable=SC2034,SC2154
|
||||
|
||||
# Current container image
|
||||
INFIX_TEST=ghcr.io/kernelkit/infix-test:1.3
|
||||
INFIX_TEST=ghcr.io/kernelkit/infix-test:1.4
|
||||
|
||||
ixdir=$(readlink -f "$testdir/..")
|
||||
logdir=$(readlink -f "$testdir/.log")
|
||||
envdir="$HOME/.infix-test-venv"
|
||||
qeneth="$testdir/qeneth/qeneth"
|
||||
runners="podman docker"
|
||||
|
||||
build_ps1()
|
||||
{
|
||||
echo "\e[1m\$(date \"+%H:%M:%S\") \h$1\e[0m:\W # "
|
||||
}
|
||||
|
||||
#
|
||||
# Figure out available container runner
|
||||
#
|
||||
runner()
|
||||
{
|
||||
if which podman >/dev/null; then
|
||||
runner=podman
|
||||
elif which docker >/dev/null; then
|
||||
runner=docker
|
||||
else
|
||||
echo "Error: Neither podman or docker is installed, we recommend podman."
|
||||
exit 1
|
||||
fi
|
||||
for r in $runners; do
|
||||
which $r >/dev/null && {
|
||||
echo $r
|
||||
return
|
||||
}
|
||||
done
|
||||
|
||||
echo "$runner"
|
||||
echo "ERROR: No container manager found (tried \"$runners\")" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
#
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
# ,------------------------------------------------,
|
||||
# | [TARGET] |
|
||||
# | br0 |
|
||||
# | / \ |
|
||||
# | target:mgmt target:data0 targetgt:data1 |
|
||||
# ,------------------------------------------------,
|
||||
# | [TARGET] |
|
||||
# | |
|
||||
# | |
|
||||
# | target:mgmt target:data0 targetgt:data1 |
|
||||
# | [192.168.0.1] [10.0.0.1] |
|
||||
# '------------------------------------------------'
|
||||
# | | |
|
||||
# | | |
|
||||
# | | |
|
||||
# | | |
|
||||
# ,----------------------------------------------,
|
||||
# | host:mgmt host:data0 host:data1 |
|
||||
# | [192.168.0.10] [10.0.0.10] |
|
||||
@@ -52,45 +52,34 @@ with infamy.Test() as test:
|
||||
with test.step("Initialize"):
|
||||
env = infamy.Env(infamy.std_topology("1x3"))
|
||||
target = env.attach("target", "mgmt")
|
||||
|
||||
with test.step("Configure target physical ports [enable routing]"):
|
||||
_, tport0 = env.ltop.xlate("target", "data0")
|
||||
_, tport1 = env.ltop.xlate("target", "data1")
|
||||
|
||||
config_target(target, tport0, tport1, True)
|
||||
|
||||
with test.step("Test: Ip routing enabled"):
|
||||
_, hport0 = env.ltop.xlate("host", "data0")
|
||||
_, hport1 = env.ltop.xlate("host", "data1")
|
||||
|
||||
with infamy.IsolatedMacVlan(hport0) as ns0, \
|
||||
infamy.IsolatedMacVlan(hport1) as ns1 :
|
||||
|
||||
with infamy.IsolatedMacVlan(hport0) as ns0, \
|
||||
infamy.IsolatedMacVlan(hport1) as ns1 :
|
||||
|
||||
with test.step("Setup host"):
|
||||
ns0.addip("192.168.0.10")
|
||||
ns0.addroute("default", "192.168.0.1")
|
||||
|
||||
|
||||
ns1.addip("10.0.0.10")
|
||||
ns1.addroute("default", "10.0.0.1")
|
||||
|
||||
|
||||
with test.step("Enable forwarding"):
|
||||
config_target(target, tport0, tport1, True)
|
||||
|
||||
with test.step("Traffic is forwarded"):
|
||||
ns0.must_reach("10.0.0.10")
|
||||
ns1.must_reach("192.168.0.10")
|
||||
|
||||
with test.step("Configure target physical ports [disable routing]"):
|
||||
with test.step("Disable forwarding"):
|
||||
config_target(target, tport0, tport1, False)
|
||||
|
||||
config_target(target, tport0, tport1, False)
|
||||
|
||||
with test.step("Test: Ip routing disabled"):
|
||||
|
||||
with infamy.IsolatedMacVlan(hport0) as ns0, \
|
||||
infamy.IsolatedMacVlan(hport1) as ns1 :
|
||||
|
||||
ns0.addip("192.168.0.10")
|
||||
ns0.addroute("default", "192.168.0.1")
|
||||
|
||||
ns1.addip("10.0.0.10")
|
||||
ns1.addroute("default", "10.0.0.1")
|
||||
|
||||
ns0.must_not_reach("10.0.0.10")
|
||||
ns1.must_not_reach("192.168.0.10")
|
||||
with test.step("Traffic is not forwarded"):
|
||||
infamy.parallel(lambda: ns0.must_not_reach("10.0.0.10"),
|
||||
lambda: ns1.must_not_reach("192.168.0.10"))
|
||||
|
||||
test.succeed()
|
||||
|
||||
@@ -188,7 +188,7 @@ def config_remove_routes(target):
|
||||
new = copy.deepcopy(running)
|
||||
new["routing"]["control-plane-protocols"].clear()
|
||||
target.put_diff_dicts("ietf-routing",running,new)
|
||||
|
||||
|
||||
with infamy.Test() as test:
|
||||
with test.step("Initialize"):
|
||||
env = infamy.Env(infamy.std_topology("2x2"))
|
||||
@@ -214,31 +214,27 @@ with infamy.Test() as test:
|
||||
until(lambda: route.ipv6_route_exist(target1, "2001:db8:3c4d:200::1/128"))
|
||||
until(lambda: route.ipv6_route_exist(target2, "::/0"))
|
||||
|
||||
|
||||
with test.step("Ping from host to 192.168.200.1 and 2001:db8:3c4d:200::1 (dut2) through dut1"):
|
||||
_, hport0 = env.ltop.xlate("host", "data1")
|
||||
with infamy.IsolatedMacVlan(hport0) as ns0:
|
||||
_, hport0 = env.ltop.xlate("host", "data1")
|
||||
with infamy.IsolatedMacVlan(hport0) as ns0:
|
||||
with test.step("Configure host addresses and routes"):
|
||||
ns0.addip("2001:db8:3c4d:10::2", prefix_length=64, proto="ipv6")
|
||||
ns0.addroute("2001:db8:3c4d:200::1/128", "2001:db8:3c4d:10::1", proto="ipv6")
|
||||
ns0.addip("192.168.10.2")
|
||||
ns0.addroute("192.168.200.1/32", "192.168.10.1")
|
||||
|
||||
with test.step("Verify that dut2 is reachable from host over IPv4"):
|
||||
ns0.must_reach("192.168.200.1")
|
||||
|
||||
with test.step("Remove static routes on dut1"):
|
||||
config_remove_routes(target1);
|
||||
until(lambda: route.ipv4_route_exist(target1, "192.168.200.1/32") == False)
|
||||
until(lambda: route.ipv6_route_exist(target1, "2001:db8:3c4d:200::1/128") == False)
|
||||
with test.step("Verify that dut2 is reachable from host over IPv6"):
|
||||
ns0.must_reach("2001:db8:3c4d:200::1")
|
||||
|
||||
with test.step("Ping from host to 192.168.200.1 and 2001:db8:3c4d:200::1 (dut2) through dut1 (should not be possible)"):
|
||||
_, hport0 = env.ltop.xlate("host", "data1")
|
||||
with infamy.IsolatedMacVlan(hport0) as ns0:
|
||||
ns0.addip("2001:db8:3c4d:10::2", prefix_length=64, proto="ipv6")
|
||||
ns0.addroute("2001:db8:3c4d:200::1/128", "2001:db8:3c4d:10::1", proto="ipv6")
|
||||
ns0.addip("192.168.10.2")
|
||||
ns0.addroute("192.168.200.1/32", "192.168.10.1")
|
||||
|
||||
ns0.must_not_reach("192.168.200.1")
|
||||
ns0.must_not_reach("2001:db8:3c4d:200::1")
|
||||
with test.step("Remove static routes on dut1"):
|
||||
config_remove_routes(target1);
|
||||
until(lambda: route.ipv4_route_exist(target1, "192.168.200.1/32") == False)
|
||||
until(lambda: route.ipv6_route_exist(target1, "2001:db8:3c4d:200::1/128") == False)
|
||||
|
||||
with test.step("Verify that dut2 is no longer reachable"):
|
||||
infamy.parallel(lambda: ns0.must_not_reach("192.168.200.1"),
|
||||
lambda: ns0.must_not_reach("2001:db8:3c4d:200::1"))
|
||||
|
||||
test.succeed()
|
||||
|
||||
@@ -42,7 +42,7 @@ with infamy.Test() as test:
|
||||
}
|
||||
})
|
||||
|
||||
with test.step(f"Verify {NAME} continer has started"):
|
||||
with test.step(f"Verify {NAME} container has started"):
|
||||
c = infamy.Container(target)
|
||||
until(lambda: c.running(NAME), attempts=10)
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ with infamy.Test() as test:
|
||||
}
|
||||
})
|
||||
|
||||
with test.step(f"Verify {NAME} continer has started"):
|
||||
with test.step(f"Verify {NAME} container has started"):
|
||||
c = infamy.Container(target)
|
||||
until(lambda: c.running(NAME), attempts=10)
|
||||
|
||||
@@ -89,15 +89,8 @@ with infamy.Test() as test:
|
||||
url = infamy.Furl(URL)
|
||||
|
||||
with infamy.IsolatedMacVlan(hport) as ns:
|
||||
connectivity = ns.runsh(f"""
|
||||
ip link set iface up
|
||||
ip addr add {OURIP}/24 dev iface
|
||||
|
||||
ping -c1 -w5 {DUTIP} || exit 1
|
||||
""")
|
||||
if connectivity.returncode:
|
||||
print(connectivity.stdout)
|
||||
test.fail()
|
||||
ns.addip(OURIP)
|
||||
ns.must_reach(DUTIP)
|
||||
|
||||
until(lambda: url.nscheck(ns, "Kilroy was here"), attempts=10)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ with infamy.Test() as test:
|
||||
}
|
||||
})
|
||||
|
||||
with test.step(f"Verify {NAME} continer has started"):
|
||||
with test.step(f"Verify {NAME} container has started"):
|
||||
c = infamy.Container(target)
|
||||
until(lambda: c.running(NAME), attempts=10)
|
||||
|
||||
@@ -68,15 +68,8 @@ with infamy.Test() as test:
|
||||
url = infamy.Furl(URL)
|
||||
|
||||
with infamy.IsolatedMacVlan(hport) as ns:
|
||||
connectivity = ns.runsh(f"""
|
||||
ip link set iface up
|
||||
ip addr add {OURIP}/24 dev iface
|
||||
|
||||
ping -c1 -w5 {DUTIP} || exit 1
|
||||
""")
|
||||
if connectivity.returncode:
|
||||
print(connectivity.stdout)
|
||||
test.fail()
|
||||
ns.addip(OURIP)
|
||||
ns.must_reach(DUTIP)
|
||||
|
||||
until(lambda: url.nscheck(ns, "It works"), attempts=10)
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ with infamy.Test() as test:
|
||||
}
|
||||
})
|
||||
|
||||
with test.step(f"Verify {NAME} continer has started"):
|
||||
with test.step(f"Verify {NAME} container has started"):
|
||||
c = infamy.Container(target)
|
||||
until(lambda: c.running(NAME), attempts=10)
|
||||
|
||||
@@ -92,15 +92,8 @@ with infamy.Test() as test:
|
||||
url = infamy.Furl(URL)
|
||||
|
||||
with infamy.IsolatedMacVlan(hport) as ns:
|
||||
connectivity = ns.runsh(f"""
|
||||
ip link set iface up
|
||||
ip addr add {OURIP}/24 dev iface
|
||||
|
||||
ping -c1 -w5 {DUTIP} || exit 1
|
||||
""")
|
||||
if connectivity.returncode:
|
||||
print(connectivity.stdout)
|
||||
test.fail()
|
||||
ns.addip(OURIP)
|
||||
ns.must_reach(DUTIP)
|
||||
|
||||
until(lambda: url.nscheck(ns, "It works"), attempts=10)
|
||||
|
||||
|
||||
@@ -46,17 +46,7 @@ with infamy.Test() as test:
|
||||
_, hport = env.ltop.xlate("host", "data")
|
||||
|
||||
with infamy.IsolatedMacVlan(hport) as ns:
|
||||
pingtest = ns.runsh("""
|
||||
set -ex
|
||||
|
||||
ip link set iface up
|
||||
ip addr add 10.0.0.1/24 dev iface
|
||||
|
||||
ping -c1 -w5 10.0.0.2 || exit 1
|
||||
""")
|
||||
|
||||
if pingtest.returncode:
|
||||
print(pingtest.stdout)
|
||||
test.fail()
|
||||
ns.addip("10.0.0.1")
|
||||
ns.must_reach("10.0.0.2")
|
||||
|
||||
test.succeed()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
# ,-------------------------------------, ,-------------------------------------,
|
||||
# | dut1:data2 | | dut2:data2 |
|
||||
# | br0 ----------|-------|--------- br0 |
|
||||
# | / \ | | / \ |
|
||||
# |dut1:mgmt dut1:data0 dut1:data1 | | dut2:data0 dut2:data1 dut2:mgmt |
|
||||
# ,-------------------------------------, ,-------------------------------------,
|
||||
# | dut1:data2 | | dut2:data2 |
|
||||
# | br0 ----------|-------|--------- br0 |
|
||||
# | / \ | | / \ |
|
||||
# |dut1:mgmt dut1:data0 dut1:data1 | | dut2:data0 dut2:data1 dut2:mgmt |
|
||||
# '-------------------------------------' '-------------------------------------'
|
||||
# | | | | | |
|
||||
# | | | | | |
|
||||
# | | | | | |
|
||||
# ,-----------------------------------------------------------------------------------------,
|
||||
# | host:mgmt0 host:data0 host:data1 host:data2 host:data3 host:mgmt1 |
|
||||
@@ -125,7 +125,7 @@ with infamy.Test() as test:
|
||||
|
||||
with test.step("Ping host:data20 [10.0.0.3] and host:data21 [10.0.0.4]"\
|
||||
"from host:data11 [10.0.0.2]"):
|
||||
|
||||
|
||||
_, hport11 = env.ltop.xlate("host", "data11")
|
||||
_, hport20 = env.ltop.xlate("host", "data20")
|
||||
_, hport21 = env.ltop.xlate("host", "data21")
|
||||
@@ -140,5 +140,5 @@ with infamy.Test() as test:
|
||||
|
||||
ns11.must_reach("10.0.0.3")
|
||||
ns11.must_reach("10.0.0.4")
|
||||
|
||||
|
||||
test.succeed()
|
||||
|
||||
@@ -65,17 +65,7 @@ with infamy.Test() as test:
|
||||
_, hport = env.ltop.xlate("host", "data")
|
||||
|
||||
with infamy.IsolatedMacVlan(hport) as ns:
|
||||
pingtest = ns.runsh("""
|
||||
set -ex
|
||||
|
||||
ip link set iface up
|
||||
ip addr add 10.0.0.1/24 dev iface
|
||||
|
||||
ping -c1 -w10 10.0.0.2 || exit 1
|
||||
""")
|
||||
|
||||
if pingtest.returncode:
|
||||
print(pingtest.stdout)
|
||||
test.fail()
|
||||
ns.addip("10.0.0.1")
|
||||
ns.must_reach("10.0.0.2")
|
||||
|
||||
test.succeed()
|
||||
|
||||
@@ -117,18 +117,8 @@ with infamy.Test() as test:
|
||||
_, hport = env.ltop.xlate("host", "data1")
|
||||
|
||||
with infamy.IsolatedMacVlan(hport) as ns:
|
||||
pingtest = ns.runsh("""
|
||||
set -ex
|
||||
|
||||
ip link set iface up
|
||||
ip addr add 10.0.0.1/24 dev iface
|
||||
|
||||
ping -c1 -w5 10.0.0.2 || exit 1
|
||||
ping -c1 -w5 10.0.0.3 || exit 1
|
||||
""")
|
||||
|
||||
if pingtest.returncode:
|
||||
print(pingtest.stdout)
|
||||
test.fail()
|
||||
ns.addip("10.0.0.1")
|
||||
ns.must_reach("10.0.0.2")
|
||||
ns.must_reach("10.0.0.3")
|
||||
|
||||
test.succeed()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
# ,-------------------------------------, ,-------------------------------------,
|
||||
# | dut1:data2 | | dut2:data2 |
|
||||
# | br0 ----------|-------|--------- br0 |
|
||||
# | / \ | | / \ |
|
||||
# |dut1:mgmt dut1:data0 dut1:data1 | | dut2:data0 dut2:data1 dut2:mgmt |
|
||||
# ,-------------------------------------, ,-------------------------------------,
|
||||
# | dut1:data2 | | dut2:data2 |
|
||||
# | br0 ----------|-------|--------- br0 |
|
||||
# | / \ | | / \ |
|
||||
# |dut1:mgmt dut1:data0 dut1:data1 | | dut2:data0 dut2:data1 dut2:mgmt |
|
||||
# '-------------------------------------' '-------------------------------------'
|
||||
# | | | | | |
|
||||
# | | | | | |
|
||||
# | | | | | |
|
||||
# ,-----------------------------------------------------------------------------------------,
|
||||
# | host:mgmt0 host:data0 host:data1 host:data2 host:data3 host:mgmt1 |
|
||||
@@ -13,7 +13,7 @@
|
||||
# | (ns10) (ns11) (ns20) (ns21) |
|
||||
# | |
|
||||
# | [ HOST ] |
|
||||
# '-----------------------------------------------------------------------------------------'
|
||||
# '-----------------------------------------------------------------------------------------'
|
||||
|
||||
import infamy
|
||||
|
||||
@@ -135,7 +135,7 @@ with infamy.Test() as test:
|
||||
|
||||
with test.step("Ping host:data20 [10.0.0.3] from host:data10 [10.0.0.1] through <bridge-vlan-10>" \
|
||||
" and host:data21 [10.0.0.4] from host:data11 [10.0.0.2] through <bridge-vlan-20>"):
|
||||
|
||||
|
||||
_, hport10 = env.ltop.xlate("host", "data10")
|
||||
_, hport11 = env.ltop.xlate("host", "data11")
|
||||
_, hport20 = env.ltop.xlate("host", "data20")
|
||||
@@ -154,9 +154,9 @@ with infamy.Test() as test:
|
||||
ns10.must_reach("10.0.0.3")
|
||||
ns11.must_reach("10.0.0.4")
|
||||
|
||||
ns10.must_not_reach("10.0.0.4")
|
||||
ns11.must_not_reach("10.0.0.3")
|
||||
ns10.must_not_reach("10.0.0.2")
|
||||
ns11.must_not_reach("10.0.0.1")
|
||||
|
||||
infamy.parallel(lambda: ns10.must_not_reach("10.0.0.4"),
|
||||
lambda: ns11.must_not_reach("10.0.0.3"),
|
||||
lambda: ns10.must_not_reach("10.0.0.2"),
|
||||
lambda: ns11.must_not_reach("10.0.0.1"))
|
||||
|
||||
test.succeed()
|
||||
|
||||
@@ -73,17 +73,7 @@ with infamy.Test() as test:
|
||||
_, hport = env.ltop.xlate("host", "data")
|
||||
|
||||
with infamy.IsolatedMacVlan(hport) as ns:
|
||||
pingtest = ns.runsh("""
|
||||
set -ex
|
||||
|
||||
ip link set iface up
|
||||
ip addr add 10.0.0.1/24 dev iface
|
||||
|
||||
ping -c1 -w10 10.0.0.2 || exit 1
|
||||
""")
|
||||
|
||||
if pingtest.returncode:
|
||||
print(pingtest.stdout)
|
||||
test.fail()
|
||||
ns.addip("10.0.0.1")
|
||||
ns.must_reach("10.0.0.2")
|
||||
|
||||
test.succeed()
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
# 10.0.0.1
|
||||
# +--------------+
|
||||
# | DUT |
|
||||
# | |
|
||||
# +----------+---\
|
||||
# | | \
|
||||
# | | \
|
||||
# | | \
|
||||
# | | \
|
||||
# 10.0.0.2 | 10.0.0.3| \ 10.0.0.4
|
||||
# +----------+ +---+------+ \ +-------------+
|
||||
# | msend | | mreceive | + no join |
|
||||
# +-------+ +----------+ +-------------+
|
||||
#
|
||||
# Add join the group 224.1.1.1 from data1 on the host. Send to that
|
||||
# group from `msend`, verify that `mrecv` receives it and that `!memb`
|
||||
# does not.
|
||||
#
|
||||
# .1
|
||||
# .---------------------------.
|
||||
# | DUT |
|
||||
# '-data0-----data1-----data2-'
|
||||
# | | |
|
||||
# | | | 10.0.0.0/24
|
||||
# | | |
|
||||
# .-data0-. .-data1-. .-data2-.
|
||||
# | msend | | mrecv | | !memb |
|
||||
# '-------' '-------' '-------'
|
||||
# .2 .3 .4
|
||||
|
||||
import infamy
|
||||
import time
|
||||
@@ -75,13 +78,15 @@ with infamy.Test() as test:
|
||||
|
||||
}
|
||||
})
|
||||
with test.step("Check multicast receieved on correct port"):
|
||||
_, hsend = env.ltop.xlate("host", "data0")
|
||||
_, hreceive = env.ltop.xlate("host", "data1")
|
||||
_, hnojoin = env.ltop.xlate("host", "data2")
|
||||
with infamy.IsolatedMacVlan(hsend) as send_ns, \
|
||||
infamy.IsolatedMacVlan(hreceive) as receive_ns, \
|
||||
infamy.IsolatedMacVlan(hnojoin) as nojoin_ns:
|
||||
|
||||
_, hsend = env.ltop.xlate("host", "data0")
|
||||
_, hreceive = env.ltop.xlate("host", "data1")
|
||||
_, hnojoin = env.ltop.xlate("host", "data2")
|
||||
with infamy.IsolatedMacVlan(hsend) as send_ns, \
|
||||
infamy.IsolatedMacVlan(hreceive) as receive_ns, \
|
||||
infamy.IsolatedMacVlan(hnojoin) as nojoin_ns:
|
||||
|
||||
with test.step("Setup sender and receivers"):
|
||||
send_ns.addip("10.0.0.2")
|
||||
receive_ns.addip("10.0.0.3")
|
||||
nojoin_ns.addip("10.0.0.4")
|
||||
@@ -89,25 +94,18 @@ with infamy.Test() as test:
|
||||
receive_ns.must_reach("10.0.0.1")
|
||||
nojoin_ns.must_reach("10.0.0.1")
|
||||
|
||||
sender = mcast.MCastSender(send_ns, "224.1.1.1")
|
||||
receiver = mcast.MCastReceiver(receive_ns, "224.1.1.1")
|
||||
snif_nojoin = infamy.Sniffer(nojoin_ns, "host 224.1.1.1")
|
||||
snif_receiver = infamy.Sniffer(receive_ns, "host 224.1.1.1")
|
||||
with sender:
|
||||
with snif_receiver:
|
||||
time.sleep(5)
|
||||
with snif_nojoin:
|
||||
time.sleep(5)
|
||||
assert(snif_receiver.packets() != "")
|
||||
assert(snif_nojoin.packets() != "")
|
||||
print("As expected, unregistered multicast is received on both ports")
|
||||
print("Starting sender")
|
||||
with mcast.MCastSender(send_ns, "224.1.1.1"):
|
||||
with test.step("Verify that the group is flooded on all ports"):
|
||||
infamy.parallel(lambda: receive_ns.must_receive("ip dst 224.1.1.1"),
|
||||
lambda: nojoin_ns.must_receive("ip dst 224.1.1.1"))
|
||||
|
||||
with receiver:
|
||||
with snif_receiver,snif_nojoin:
|
||||
time.sleep(5)
|
||||
assert(snif_nojoin.packets() == "")
|
||||
print("As expected, registered multicast is NOT forwarded to non-member port")
|
||||
assert(snif_receiver.packets() != "")
|
||||
print("As expected, registered multicast is forwarded to the member port")
|
||||
print("Subscribe to the group")
|
||||
with mcast.MCastReceiver(receive_ns, "224.1.1.1"):
|
||||
with test.step("Verify that the group is still forwarded to the member"):
|
||||
receive_ns.must_receive("ip dst 224.1.1.1")
|
||||
|
||||
test.succeed()
|
||||
with test.step("Verify that the group is no longer forwarded to the non-member"):
|
||||
nojoin_ns.must_not_receive("ip dst 224.1.1.1")
|
||||
|
||||
test.succeed()
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
# 10.0.0.1
|
||||
# +--------------+
|
||||
# | DUT |
|
||||
# | |
|
||||
# +----------+---\
|
||||
# | | \
|
||||
# | | \
|
||||
# | | \
|
||||
# | | \
|
||||
# 10.0.0.2 | 10.0.0.3| \ 10.0.0.4
|
||||
# +----------+ +---+------+ \ +-------------+
|
||||
# | msend | | Static | + no join |
|
||||
# +----------+ | MC-filter| +-------------+
|
||||
# +----------+
|
||||
#
|
||||
# Add static filter on DUT for 224.1.1.1 containing only data1. Send
|
||||
# to that group from `msend`, verify that `mrecv` receives it and that
|
||||
# `!memb` does not.
|
||||
#
|
||||
# .1
|
||||
# .---------------------------.
|
||||
# | DUT |
|
||||
# '-data0-----data1-----data2-'
|
||||
# | | |
|
||||
# | | | 10.0.0.0/24
|
||||
# | | |
|
||||
# .-data0-. .-data1-. .-data2-.
|
||||
# | msend | | mrecv | | !memb |
|
||||
# '-------' '-------' '-------'
|
||||
# .2 .3 .4
|
||||
|
||||
import infamy
|
||||
import time
|
||||
@@ -20,32 +22,31 @@ import infamy.multicast as mcast
|
||||
import infamy.iface as iface
|
||||
from infamy.util import until
|
||||
|
||||
def set_static_multicast_filter(target, address):
|
||||
target.put_config_dict("ietf-interfaces",
|
||||
{
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"name": "br0",
|
||||
"infix-interfaces:bridge": {
|
||||
"multicast-filters": {
|
||||
"multicast-filter": [
|
||||
{
|
||||
"group": "224.1.1.1",
|
||||
"ports": [
|
||||
{
|
||||
"port": mreceive
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
def set_static_multicast_filter(target, address, port):
|
||||
target.put_config_dict("ietf-interfaces", {
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"name": "br0",
|
||||
"infix-interfaces:bridge": {
|
||||
"multicast-filters": {
|
||||
"multicast-filter": [
|
||||
{
|
||||
"group": address,
|
||||
"ports": [
|
||||
{
|
||||
"port": port,
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
with infamy.Test() as test:
|
||||
with test.step("Initialize"):
|
||||
env = infamy.Env(infamy.std_topology("1x4"))
|
||||
@@ -54,7 +55,7 @@ with infamy.Test() as test:
|
||||
_, mreceive = env.ltop.xlate("target", "data1")
|
||||
_, nojoin = env.ltop.xlate("target", "data2")
|
||||
|
||||
with test.step("Configure device"):
|
||||
with test.step("Configure device without static filter"):
|
||||
target.put_config_dict("ietf-interfaces",
|
||||
{
|
||||
"interfaces": {
|
||||
@@ -103,13 +104,14 @@ with infamy.Test() as test:
|
||||
}
|
||||
})
|
||||
|
||||
with test.step("Check multicast receieved on correct port"):
|
||||
_, hsend = env.ltop.xlate("host", "data0")
|
||||
_, hreceive = env.ltop.xlate("host", "data1")
|
||||
_, hnojoin = env.ltop.xlate("host", "data2")
|
||||
with infamy.IsolatedMacVlan(hsend) as send_ns, \
|
||||
infamy.IsolatedMacVlan(hreceive) as receive_ns, \
|
||||
infamy.IsolatedMacVlan(hnojoin) as nojoin_ns:
|
||||
_, hsend = env.ltop.xlate("host", "data0")
|
||||
_, hreceive = env.ltop.xlate("host", "data1")
|
||||
_, hnojoin = env.ltop.xlate("host", "data2")
|
||||
with infamy.IsolatedMacVlan(hsend) as send_ns, \
|
||||
infamy.IsolatedMacVlan(hreceive) as receive_ns, \
|
||||
infamy.IsolatedMacVlan(hnojoin) as nojoin_ns:
|
||||
|
||||
with test.step("Setup sender and receivers"):
|
||||
send_ns.addip("10.0.0.2")
|
||||
receive_ns.addip("10.0.0.3")
|
||||
nojoin_ns.addip("10.0.0.4")
|
||||
@@ -117,26 +119,20 @@ with infamy.Test() as test:
|
||||
receive_ns.must_reach("10.0.0.1")
|
||||
nojoin_ns.must_reach("10.0.0.1")
|
||||
|
||||
sender = mcast.MCastSender(send_ns, "224.1.1.1")
|
||||
snif_nojoin = infamy.Sniffer(nojoin_ns, "host 224.1.1.1")
|
||||
snif_receiver = infamy.Sniffer(receive_ns, "host 224.1.1.1")
|
||||
with sender:
|
||||
with snif_receiver:
|
||||
time.sleep(5)
|
||||
with snif_nojoin:
|
||||
time.sleep(5)
|
||||
assert(snif_receiver.packets() != "")
|
||||
assert(snif_nojoin.packets() != "")
|
||||
print("As expected, unregistered multicast is received on both ports")
|
||||
print("Starting sender")
|
||||
with mcast.MCastSender(send_ns, "224.1.1.1"):
|
||||
with test.step("Verify that the group is flooded on all ports"):
|
||||
infamy.parallel(lambda: receive_ns.must_receive("ip dst 224.1.1.1"),
|
||||
lambda: nojoin_ns.must_receive("ip dst 224.1.1.1"))
|
||||
|
||||
set_static_multicast_filter(target, "224.1.1.1")
|
||||
with test.step("Install group filter"):
|
||||
set_static_multicast_filter(target, "224.1.1.1", mreceive)
|
||||
until(lambda: iface.exist_bridge_multicast_filter(target, "224.1.1.1", mreceive, "br0"))
|
||||
with snif_receiver,snif_nojoin:
|
||||
time.sleep(5)
|
||||
|
||||
assert(snif_nojoin.packets() == "")
|
||||
print("As expected, registered multicast is NOT forwarded to non-member port")
|
||||
assert(snif_receiver.packets() != "")
|
||||
print("As expected, registered multicast is forwarded to the member port")
|
||||
with test.step("Verify that the group is still forwarded to the member"):
|
||||
receive_ns.must_receive("ip dst 224.1.1.1")
|
||||
|
||||
with test.step("Verify that the group is no longer forwarded to the non-member"):
|
||||
nojoin_ns.must_not_receive("ip dst 224.1.1.1")
|
||||
|
||||
test.succeed()
|
||||
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
|
||||
import random
|
||||
import string
|
||||
|
||||
import infamy
|
||||
|
||||
def attach(env):
|
||||
return (env.attach("target", "mgmt"), env.attach("target", "mgmt", "ssh"))
|
||||
|
||||
with infamy.Test() as test:
|
||||
with test.step("Initialize"):
|
||||
env = infamy.Env(infamy.std_topology("1x1"))
|
||||
target, tgtssh = attach(env)
|
||||
|
||||
cookie = "".join((random.choices(string.ascii_lowercase, k=16)))
|
||||
script = f"/cfg/user-scripts.d/infamy-user-script-test.sh"
|
||||
output = f"/tmp/{cookie}.cookie"
|
||||
|
||||
with test.step(f"Install {script}"):
|
||||
tgtssh.runsh(f"""
|
||||
set -e
|
||||
|
||||
mkdir -p $(dirname {script})
|
||||
printf '#!/bin/sh\necho -n {cookie} >{output}\n' >{script}
|
||||
chmod +x {script}
|
||||
""")
|
||||
|
||||
with test.step("Enable user scripts and reboot"):
|
||||
target.put_config_dict("infix-services", {
|
||||
"user-scripts": {
|
||||
"enabled": True
|
||||
}
|
||||
})
|
||||
target.copy("running", "startup")
|
||||
target.reboot()
|
||||
infamy.util.wait_boot(target) or test.fail()
|
||||
target, tgtssh = attach(env)
|
||||
|
||||
with test.step(f"Verify that {script} has run"):
|
||||
cat = tgtssh.runsh(f"cat {output}").stdout
|
||||
assert cat == cookie, f"Read back {repr(cat)}, expected {repr(cookie)}"
|
||||
|
||||
with test.step("Disable user scripts and reboot"):
|
||||
target.put_config_dict("infix-services", {
|
||||
"user-scripts": {
|
||||
"enabled": False
|
||||
}
|
||||
})
|
||||
target.copy("running", "startup")
|
||||
target.reboot()
|
||||
infamy.util.wait_boot(target) or test.fail()
|
||||
target, tgtssh = attach(env)
|
||||
|
||||
with test.step(f"Verify that {script} has not run"):
|
||||
exists = tgtssh.run(["test", "-f", f"{output}"]).returncode == 0
|
||||
assert not exists, f"Unexpectedly found {output}"
|
||||
|
||||
with test.step(f"Remove {script}"):
|
||||
tgtssh.runsh(f"rm {script}")
|
||||
|
||||
test.succeed()
|
||||
@@ -12,12 +12,14 @@ RUN apk add --no-cache \
|
||||
libc-dev \
|
||||
libyang-dev \
|
||||
linux-headers \
|
||||
openssh-client \
|
||||
python3-dev \
|
||||
qemu-img \
|
||||
qemu-system-x86_64 \
|
||||
ruby-mustache \
|
||||
socat \
|
||||
squashfs-tools \
|
||||
sshpass \
|
||||
tcpdump \
|
||||
tshark \
|
||||
make
|
||||
|
||||
@@ -17,12 +17,17 @@ usage: test/env [<OPTS>] -f <IMAGE> -q <QENETH-DIR> <COMMAND> [<ARGS>...]
|
||||
|
||||
-c
|
||||
Clean up cruft, lingering images, old containers, unused volumes.
|
||||
Used by GitHub action to prevent issues with runnign tests.
|
||||
Used by GitHub action to prevent issues with running tests.
|
||||
|
||||
-C
|
||||
Don't containerize the command, run it directly in the current
|
||||
namespaces
|
||||
|
||||
-D
|
||||
Prefer Docker over podman. This is implied when -t is specified,
|
||||
as podman does not allow the necessary network permissions to be
|
||||
granted to the container
|
||||
|
||||
-f <IMAGE>
|
||||
Specify images required for test, squashfs image is required
|
||||
if testing with kernel. bios and disk image is required if
|
||||
@@ -47,7 +52,8 @@ usage: test/env [<OPTS>] -f <IMAGE> -q <QENETH-DIR> <COMMAND> [<ARGS>...]
|
||||
|
||||
-t <TOPOLOGY>
|
||||
Rather than starting a qeneth network, attach to this existing
|
||||
topology. Mostly useful together with -C
|
||||
topology. If the command is containerized, it will be launched
|
||||
in the host's network namespace
|
||||
|
||||
EOF
|
||||
}
|
||||
@@ -111,7 +117,7 @@ containerize=yes
|
||||
[ -c /dev/kvm ] && kvm="--device=/dev/kvm"
|
||||
files=
|
||||
|
||||
while getopts "cCf:hiKp:q:t:" opt; do
|
||||
while getopts "cCDf:hiKp:q:t:" opt; do
|
||||
case ${opt} in
|
||||
c)
|
||||
$(runner) image prune -af
|
||||
@@ -122,6 +128,9 @@ while getopts "cCf:hiKp:q:t:" opt; do
|
||||
C)
|
||||
containerize=
|
||||
;;
|
||||
D)
|
||||
runners="docker podman"
|
||||
;;
|
||||
f)
|
||||
files="$files $OPTARG"
|
||||
;;
|
||||
@@ -139,9 +148,12 @@ while getopts "cCf:hiKp:q:t:" opt; do
|
||||
;;
|
||||
q)
|
||||
qenethdir="$OPTARG"
|
||||
network="--sysctl net.ipv6.conf.all.disable_ipv6=0"
|
||||
;;
|
||||
t)
|
||||
topology="$OPTARG"
|
||||
network="--network host --volume $topology:$topology:ro"
|
||||
runners="docker podman"
|
||||
;;
|
||||
*)
|
||||
>&2 echo "Unknown option -$opt"
|
||||
@@ -160,14 +172,14 @@ if [ "$containerize" ]; then
|
||||
--env PYTHONHASHSEED=${PYTHONHASHSEED:-$(shuf -i 0-$(((1 << 32) - 1)) -n 1)} \
|
||||
--env VIRTUAL_ENV_DISABLE_PROMPT=yes \
|
||||
--env PROMPT_DIRTRIM="$ixdir" \
|
||||
--env PS1='\e[1m$(date "+%H:%M:%S") \h\e[0m:\W # ' \
|
||||
--env PS1="$(build_ps1)" \
|
||||
--expose 9001-9010 --publish-all \
|
||||
--hostname "$(name)" \
|
||||
--name "$(name)" \
|
||||
$interactive \
|
||||
--rm \
|
||||
--security-opt seccomp=unconfined \
|
||||
--sysctl net.ipv6.conf.all.disable_ipv6=0 \
|
||||
$network \
|
||||
--tty \
|
||||
--volume "$ixdir":"$ixdir" \
|
||||
--workdir "$ixdir/test" \
|
||||
|
||||
@@ -6,7 +6,7 @@ from .furl import Furl
|
||||
from .netns import IsolatedMacVlan
|
||||
from .sniffer import Sniffer
|
||||
from .tap import Test
|
||||
from .util import until
|
||||
from .util import parallel, until
|
||||
|
||||
def std_topology(name):
|
||||
return os.path.realpath(
|
||||
|
||||
+36
-8
@@ -5,7 +5,13 @@ import pydot
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
from . import neigh, netconf, tap, topology
|
||||
from . import neigh, netconf, ssh, tap, topology
|
||||
|
||||
class NullEnv:
|
||||
def attr(self, _, default=None):
|
||||
return default
|
||||
|
||||
ENV = NullEnv()
|
||||
|
||||
class ArgumentParser(argparse.ArgumentParser):
|
||||
def __init__(self, ltop):
|
||||
@@ -35,7 +41,24 @@ class Env(object):
|
||||
if not self.ltop.map_to(self.ptop):
|
||||
raise tap.TestSkip()
|
||||
|
||||
def attach(self, node, port, factory_default=True):
|
||||
print(repr(self.ltop))
|
||||
|
||||
global ENV
|
||||
ENV = self
|
||||
|
||||
def attr(self, name, default=None):
|
||||
val = self.ptop.get_attr("ix_" + name)
|
||||
if val == None:
|
||||
return default
|
||||
|
||||
try:
|
||||
return int(val, 0)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return val
|
||||
|
||||
def attach(self, node, port, protocol="netconf", factory_default=True):
|
||||
if self.ltop:
|
||||
mapping = self.ltop.mapping[node]
|
||||
node, port = self.ltop.xlate(node, port)
|
||||
@@ -54,9 +77,14 @@ class Env(object):
|
||||
if not mgmtip:
|
||||
raise Exception(f"Failed, cannot find mgmt IP for {node}")
|
||||
|
||||
return netconf.Device(
|
||||
location=netconf.Location(cport, mgmtip, password),
|
||||
mapping=mapping,
|
||||
yangdir=self.args.yangdir,
|
||||
factory_default=factory_default
|
||||
)
|
||||
if protocol == "netconf":
|
||||
return netconf.Device(
|
||||
location=netconf.Location(cport, mgmtip, password),
|
||||
mapping=mapping,
|
||||
yangdir=self.args.yangdir,
|
||||
factory_default=factory_default
|
||||
)
|
||||
elif protocol == "ssh":
|
||||
return ssh.Device(ssh.Location(mgmtip, password))
|
||||
|
||||
raise Exception(f"Unsupported management procotol \"{protocol}\"")
|
||||
|
||||
+2
-2
@@ -9,10 +9,10 @@ class Furl:
|
||||
"""Create new URL checker"""
|
||||
self.url = urllib.parse.quote(url, safe='/:')
|
||||
|
||||
def check(self, needle):
|
||||
def check(self, needle, timeout=10):
|
||||
"""Connect to web server URL, fetch body and check for needle"""
|
||||
try:
|
||||
with urllib.request.urlopen(self.url) as response:
|
||||
with urllib.request.urlopen(self.url, timeout=timeout) as response:
|
||||
text = response.read().decode('utf-8')
|
||||
#print(text)
|
||||
return needle in text
|
||||
|
||||
+65
-12
@@ -1,7 +1,11 @@
|
||||
import ctypes
|
||||
import multiprocessing
|
||||
import subprocess
|
||||
import os
|
||||
import random
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
from . import env
|
||||
|
||||
__libc = ctypes.CDLL(None)
|
||||
CLONE_NEWUSER = 0x10000000
|
||||
@@ -17,6 +21,7 @@ class IsolatedMacVlan:
|
||||
def __init__(self, parent, ifname="iface", lo=True):
|
||||
self.sleeper = None
|
||||
self.parent, self.ifname, self.lo = parent, ifname, lo
|
||||
self.ping_timeout = env.ENV.attr("ping_timeout", 5)
|
||||
|
||||
def __enter__(self):
|
||||
self.sleeper = subprocess.Popen(["unshare", "-r", "-n", "sh", "-c",
|
||||
@@ -28,6 +33,7 @@ class IsolatedMacVlan:
|
||||
subprocess.run(["ip", "link", "add",
|
||||
"dev", self.ifname,
|
||||
"link", self.parent,
|
||||
"address", self._stable_mac(),
|
||||
"netns", str(self.sleeper.pid),
|
||||
"type", "macvlan"], check=True)
|
||||
self.runsh(f"""
|
||||
@@ -51,8 +57,31 @@ class IsolatedMacVlan:
|
||||
def __exit__(self, val, typ, tb):
|
||||
self.sleeper.kill()
|
||||
self.sleeper.wait()
|
||||
time.sleep(0.5)
|
||||
|
||||
def __ns_call(self, fn, tx):
|
||||
def _stable_mac(self):
|
||||
"""Generate address for MACVLAN
|
||||
|
||||
By default, the kernel will assign a random address. This
|
||||
causes issues when the parent interface is an Intel X710 NIC,
|
||||
which will add those addresses to its FDB, but never remove
|
||||
them when the interface is deleted.
|
||||
|
||||
Work around the issue by generating an address that is pseudo
|
||||
random, to avoid address conflicts; yet stable across
|
||||
instantiations for any given parent interface name, to avoid
|
||||
the resource exhaustion issue.
|
||||
|
||||
"""
|
||||
random.seed(self.parent)
|
||||
a = list(random.randbytes(6))
|
||||
a[0] |= 0x02
|
||||
a[0] &= ~0x01
|
||||
return \
|
||||
f"{a[0]:02x}:{a[1]:02x}:{a[2]:02x}:" + \
|
||||
f"{a[3]:02x}:{a[4]:02x}:{a[5]:02x}"
|
||||
|
||||
def _ns_call(self, fn, tx):
|
||||
pid = self.sleeper.pid
|
||||
|
||||
uns = os.open(f"/proc/{pid}/ns/user", os.O_RDONLY)
|
||||
@@ -69,7 +98,7 @@ class IsolatedMacVlan:
|
||||
def call(self, fn):
|
||||
rx, tx = multiprocessing.Pipe(duplex=False)
|
||||
|
||||
proc = multiprocessing.Process(target=self.__ns_call, args=(fn, tx))
|
||||
proc = multiprocessing.Process(target=self._ns_call, args=(fn, tx))
|
||||
proc.start()
|
||||
ret = rx.recv()
|
||||
rx.close()
|
||||
@@ -119,7 +148,7 @@ class IsolatedMacVlan:
|
||||
p=proto[3]
|
||||
|
||||
self.runsh(f"""
|
||||
set -ex
|
||||
set -ex
|
||||
ip link set iface up
|
||||
ip -{p} addr add {addr}/{prefix_length} dev iface
|
||||
""", check=True)
|
||||
@@ -136,15 +165,39 @@ class IsolatedMacVlan:
|
||||
result.append(l)
|
||||
return result
|
||||
|
||||
def ping(self, daddr, count=1, timeout=5, interval=2, check=False):
|
||||
return self.runsh(f"""set -ex; ping -c {count} -w {timeout} -i {interval} {daddr}""", check=check)
|
||||
def ping(self, daddr, timeout=None):
|
||||
timeout = timeout if timeout else self.ping_timeout
|
||||
|
||||
return self.run(["timeout", str(timeout), "/bin/sh"], text=True, check=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
input=f"while :; do ping -c1 -w1 {daddr} && break; done")
|
||||
|
||||
def must_reach(self, *args, **kwargs):
|
||||
res = self.ping(*args, **kwargs)
|
||||
if res.returncode != 0:
|
||||
raise Exception(res.stdout)
|
||||
self.ping(*args, **kwargs)
|
||||
|
||||
def must_not_reach(self, *args, **kwargs):
|
||||
res = self.ping(*args, **kwargs)
|
||||
if res.returncode == 0:
|
||||
raise Exception(res.stdout)
|
||||
try:
|
||||
res = self.ping(*args, **kwargs)
|
||||
except subprocess.CalledProcessError as e:
|
||||
return
|
||||
|
||||
raise Exception(res)
|
||||
|
||||
def must_receive(self, expr, timeout=None, ifname=None, must=True):
|
||||
ifname = ifname if ifname else self.ifname
|
||||
timeout = timeout if timeout else self.ping_timeout
|
||||
|
||||
tshark = self.run(["tshark", "-nl", f"-i{ifname}",
|
||||
f"-aduration:{timeout}", "-c1", expr],
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True, check=True)
|
||||
|
||||
needle = "1 packet captured" if must else "0 packets captured"
|
||||
|
||||
if needle not in tshark.stdout:
|
||||
raise Exception(tshark)
|
||||
|
||||
def must_not_receive(self, *args, **kwargs):
|
||||
self.must_receive(*args, **kwargs, must=False)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import subprocess
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class Location:
|
||||
host: str
|
||||
password: str
|
||||
username: str = "admin"
|
||||
port: int = 22
|
||||
|
||||
class Device(object):
|
||||
def __init__(self, location: Location):
|
||||
self.location = location
|
||||
|
||||
def _mangle_subprocess_args(self, args, kwargs):
|
||||
if not args:
|
||||
return
|
||||
|
||||
args = list(args)
|
||||
if type(args[0]) == str:
|
||||
if kwargs.get("shell"):
|
||||
args[0] = ["/bin/sh", "-c", args[0]]
|
||||
kwargs["shell"] = False
|
||||
else:
|
||||
args[0] = [args[0]]
|
||||
|
||||
args[0] = [ "ssh",
|
||||
"-oStrictHostKeyChecking no",
|
||||
"-oUserKnownHostsFile /dev/null",
|
||||
"-oLogLevel QUIET",
|
||||
f"-l{self.location.username}",
|
||||
self.location.host ] + args[0]
|
||||
|
||||
if self.location.password:
|
||||
args[0] = [ "sshpass" , f"-p{self.location.password}" ] + args[0]
|
||||
|
||||
return args, kwargs
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
args, kwargs = self._mangle_subprocess_args(args, kwargs)
|
||||
return subprocess.run(*args, **kwargs)
|
||||
|
||||
def runsh(self, script, *args, **kwargs):
|
||||
return self.run("/bin/sh", text=True, input=script,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, *args, **kwargs)
|
||||
@@ -1,5 +1,6 @@
|
||||
import contextlib
|
||||
import datetime
|
||||
import subprocess
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
@@ -15,21 +16,29 @@ class Test:
|
||||
def __enter__(self):
|
||||
now = datetime.datetime.now().strftime("%F %T")
|
||||
self.out.write(f"# Starting ({now})\n")
|
||||
self.out.flush()
|
||||
return self
|
||||
|
||||
def __exit__(self, _, e, __):
|
||||
now = datetime.datetime.now().strftime("%F %T")
|
||||
self.out.write(f"# Exiting ({now})\n")
|
||||
self.out.flush()
|
||||
|
||||
if not e:
|
||||
self._not_ok("Missing explicit test result\n")
|
||||
else:
|
||||
if type(e) in (TestPass, TestSkip):
|
||||
self.out.write(f"{self.steps}..{self.steps}\n")
|
||||
self.out.flush()
|
||||
raise SystemExit(0)
|
||||
|
||||
traceback.print_exception(e, file=self.commenter)
|
||||
|
||||
if type(e) is subprocess.CalledProcessError:
|
||||
print("Failing subprocess stdout:\n", e.stdout)
|
||||
elif len(e.args) and type(e.args[0]) is subprocess.CompletedProcess:
|
||||
print("Failing subprocess stdout:\n", e.args[0].stdout)
|
||||
|
||||
raise SystemExit(1)
|
||||
|
||||
@contextlib.contextmanager
|
||||
@@ -53,6 +62,7 @@ class Test:
|
||||
def _report(self, tag, msg):
|
||||
self.steps += 1
|
||||
self.out.write(f"{tag} {self.steps}{msg}\n")
|
||||
self.out.flush()
|
||||
|
||||
def _ok(self, msg="", directive=None):
|
||||
if msg:
|
||||
@@ -106,6 +116,7 @@ class CommentWriter:
|
||||
data = data + "\n"
|
||||
|
||||
self.f.write(data)
|
||||
self.flush()
|
||||
|
||||
def flush(self):
|
||||
return self.f.flush()
|
||||
|
||||
+28
-8
@@ -1,6 +1,15 @@
|
||||
import networkx as nx
|
||||
from networkx.algorithms import isomorphism
|
||||
|
||||
def _qstrip(text):
|
||||
if text == None:
|
||||
return None
|
||||
|
||||
if text.startswith("\"") and text.endswith("\""):
|
||||
return text[1:-1]
|
||||
|
||||
return text
|
||||
|
||||
def map_edges(les, pes):
|
||||
acc = []
|
||||
les = sorted(list(les.values()), key=lambda x: x.get("kind", ""), reverse=True)
|
||||
@@ -22,14 +31,6 @@ def match_edge(pes, les):
|
||||
|
||||
class Topology:
|
||||
def __init__(self, dotg):
|
||||
def _qstrip(text):
|
||||
if text == None:
|
||||
return None
|
||||
|
||||
if text.startswith("\"") and text.endswith("\""):
|
||||
return text[1:-1]
|
||||
return text
|
||||
|
||||
self.dotg = dotg
|
||||
self.g = nx.MultiGraph()
|
||||
|
||||
@@ -51,6 +52,22 @@ class Topology:
|
||||
attrs[dn] = dp
|
||||
self.g.add_edge(sn, dn, **attrs)
|
||||
|
||||
def __repr__(self):
|
||||
if not self.mapping:
|
||||
return ""
|
||||
|
||||
out = ""
|
||||
|
||||
for n in self.mapping:
|
||||
out += f"{n + ':':<8} {self.mapping[n][None]}\n"
|
||||
for e in self.mapping[n]:
|
||||
if not e:
|
||||
continue
|
||||
|
||||
out += f" {e + ':':<8} {self.mapping[n][e]}\n"
|
||||
|
||||
return out
|
||||
|
||||
def map_to(self, phy):
|
||||
mapper = isomorphism.MultiGraphMatcher(phy.g, self.g,
|
||||
edge_match=match_edge,
|
||||
@@ -126,6 +143,9 @@ class Topology:
|
||||
def get_infixen(self):
|
||||
return self.get_nodes(lambda _, attrs: attrs.get("kind") == "infix")
|
||||
|
||||
def get_attr(self, name, default=None):
|
||||
return _qstrip(self.dotg.get_attributes().get(name, default))
|
||||
|
||||
# Support calling this script like so...
|
||||
#
|
||||
# python3 topology.py <physical> <logical>
|
||||
|
||||
@@ -1,7 +1,35 @@
|
||||
import time
|
||||
import threading
|
||||
import infamy.neigh
|
||||
import infamy.netconf as netconf
|
||||
|
||||
class ParallelFn(threading.Thread):
|
||||
def __init__(self, group=None, target=None, name=None,
|
||||
args=(), kwargs={}, Verbose=None):
|
||||
threading.Thread.__init__(self, group, target, name, args, kwargs)
|
||||
self._exc, self._return = None, None
|
||||
|
||||
def run(self):
|
||||
if self._target is not None:
|
||||
try:
|
||||
self._return = self._target(*self._args,
|
||||
**self._kwargs)
|
||||
except Exception as e:
|
||||
self._exc = e
|
||||
|
||||
def join(self, *args):
|
||||
threading.Thread.join(self, *args)
|
||||
|
||||
if self._exc:
|
||||
raise self._exc
|
||||
|
||||
return self._return
|
||||
|
||||
def parallel(*fns):
|
||||
ths = [ParallelFn(target=fn) for fn in fns]
|
||||
[th.start() for th in ths]
|
||||
return [th.join() for th in ths]
|
||||
|
||||
def until(fn, attempts=10, interval=1):
|
||||
for attempt in range(attempts):
|
||||
if fn():
|
||||
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
testdir=$(dirname "$(readlink -f "$0")")
|
||||
. "$testdir/.env"
|
||||
|
||||
lower=$1
|
||||
shift
|
||||
[ $# -gt 0 ] || set sh
|
||||
|
||||
for pid in $(pgrep -fx "sleep infinity"); do
|
||||
ifidxs=$(nsenter -t $pid -U -n ip -j link show | \
|
||||
jq '.[] | select(has("link_netnsid")) | .link_index')
|
||||
for ifidx in $ifidxs; do
|
||||
ifname=$(ip -j link show | jq -r ".[] | select(.ifindex == $ifidx) | .ifname")
|
||||
if [ "$ifname" = "$lower" ]; then
|
||||
exec nsenter -t $pid -U -n env PS1="$(build_ps1 "(netns:$ifname)")" "$@"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
echo Found no namespace with interface stacked on $lower >&2
|
||||
exit 1
|
||||
+21
-3
@@ -1,8 +1,26 @@
|
||||
test-dir := $(BR2_EXTERNAL_INFIX_PATH)/test
|
||||
ninepm := $(BR2_EXTERNAL_INFIX_PATH)/test/9pm/9pm.py
|
||||
|
||||
UNIT_TESTS ?= $(test-dir)/case/all-repo.yaml $(test-dir)/case/all-unit.yaml
|
||||
UNIT_TESTS ?= $(test-dir)/case/all-repo.yaml $(test-dir)/case/all-unit.yaml
|
||||
INFIX_TESTS ?= $(test-dir)/case/all.yaml
|
||||
|
||||
TEST_MODE ?= qeneth
|
||||
mode-qeneth := -q $(test-dir)/virt/quad
|
||||
mode-host := -t $(or $(TOPOLOGY),/etc/infamy.dot)
|
||||
mode-run := -t $(BINARIES_DIR)/qemu.dot
|
||||
mode := $(mode-$(TEST_MODE))
|
||||
|
||||
binaries-$(ARCH) := $(addprefix infix-$(ARCH),.img -disk.img .pkg)
|
||||
binaries-x86_64 += OVMF.fd
|
||||
binaries := $(foreach bin,$(binaries-$(ARCH)),-f $(BINARIES_DIR)/$(bin))
|
||||
|
||||
test:
|
||||
$(test-dir)/env $(mode) $(binaries) $(ninepm) $(INFIX_TESTS)
|
||||
|
||||
test-sh:
|
||||
$(test-dir)/env $(mode) $(binaries) -i /bin/sh
|
||||
|
||||
test-unit:
|
||||
$(test-dir)/env $(test-dir)/9pm/9pm.py $(UNIT_TESTS)
|
||||
$(test-dir)/env $(ninepm) $(UNIT_TESTS)
|
||||
|
||||
.PHONY: test-unit
|
||||
.PHONY: test test-sh test-unit
|
||||
|
||||
Reference in New Issue
Block a user