mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
Compare commits
161
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b04b978d40 | ||
|
|
1d1afecdc3 | ||
|
|
2cfddb448c | ||
|
|
f25a3189ed | ||
|
|
1422a0ebdd | ||
|
|
ce1892a60b | ||
|
|
4b340a38b3 | ||
|
|
2a766d7886 | ||
|
|
1bb9f778ff | ||
|
|
d2abdf0f64 | ||
|
|
5df26b217a | ||
|
|
b3ae276a14 | ||
|
|
5bbb83a3e6 | ||
|
|
e233dbc6da | ||
|
|
d83c708cd1 | ||
|
|
d333c66f9c | ||
|
|
1b11bae8eb | ||
|
|
2744e6ee85 | ||
|
|
9b41139066 | ||
|
|
d0d95f4098 | ||
|
|
84dfaf7c03 | ||
|
|
afcc40589a | ||
|
|
208f7c9f08 | ||
|
|
f8c68a38af | ||
|
|
8994879e26 | ||
|
|
b5a0ea0b1b | ||
|
|
435406af68 | ||
|
|
79107d2548 | ||
|
|
7a9cece84f | ||
|
|
b8ba1a1042 | ||
|
|
60676e21be | ||
|
|
763750b6ed | ||
|
|
23c5f16a4f | ||
|
|
5d3fce35c8 | ||
|
|
6d6064f946 | ||
|
|
a4e5c27b9d | ||
|
|
ddc57ed6ae | ||
|
|
2ae1a70bac | ||
|
|
20350fa52c | ||
|
|
f866cf15b4 | ||
|
|
7030f85484 | ||
|
|
d194b52629 | ||
|
|
674fd6c396 | ||
|
|
0229057612 | ||
|
|
b3450d004d | ||
|
|
5c3a476cbc | ||
|
|
ba677bf5c1 | ||
|
|
a0c0a5e45c | ||
|
|
0fd8696860 | ||
|
|
ebc4ce1d90 | ||
|
|
25ff6f54f5 | ||
|
|
e28532d042 | ||
|
|
797c5c09f1 | ||
|
|
be6d287b19 | ||
|
|
67ea7a74c1 | ||
|
|
a365238854 | ||
|
|
d02f45d777 | ||
|
|
19c820fac2 | ||
|
|
a47e141dc4 | ||
|
|
93db076d72 | ||
|
|
322811d712 | ||
|
|
bbc01a5e54 | ||
|
|
584d6c57df | ||
|
|
4d398bbf57 | ||
|
|
a7d2e1b806 | ||
|
|
390e60cfcb | ||
|
|
f62d049aa9 | ||
|
|
4554b77105 | ||
|
|
0c6d96f8a9 | ||
|
|
473cda35bb | ||
|
|
227f1290fd | ||
|
|
12f1edb435 | ||
|
|
f5c549f669 | ||
|
|
7e19ee8777 | ||
|
|
7e0e6f244d | ||
|
|
d326939e10 | ||
|
|
a975f55284 | ||
|
|
89392c56be | ||
|
|
67ec3faa54 | ||
|
|
9272ae382d | ||
|
|
a78eeee651 | ||
|
|
1c3086be94 | ||
|
|
cd19b626a7 | ||
|
|
b5b310f3c8 | ||
|
|
f875056706 | ||
|
|
e412c28c37 | ||
|
|
3ccbdf2f0b | ||
|
|
8b3ba1f5c5 | ||
|
|
629f0aa74a | ||
|
|
dc2d429093 | ||
|
|
b1146cbb31 | ||
|
|
10ab88d93f | ||
|
|
d07c8a9b5e | ||
|
|
c40c1d0c10 | ||
|
|
beabc1fe5b | ||
|
|
d9611c48a9 | ||
|
|
d7120df65f | ||
|
|
423e78aba9 | ||
|
|
9f03e0a34b | ||
|
|
5570b13791 | ||
|
|
2c80c65962 | ||
|
|
bfb5b14ea0 | ||
|
|
f00511d235 | ||
|
|
f8432f59fb | ||
|
|
9720c7000f | ||
|
|
974ea7260f | ||
|
|
b0bd1dad11 | ||
|
|
ff96401ce2 | ||
|
|
9853e08145 | ||
|
|
04fbbc6fc8 | ||
|
|
6e30d4f590 | ||
|
|
f9733dc52d | ||
|
|
1a33604ed6 | ||
|
|
a80a731324 | ||
|
|
6e84a75550 | ||
|
|
2ef7490f69 | ||
|
|
8b10678dee | ||
|
|
7ac15796e3 | ||
|
|
e36aac736e | ||
|
|
fc328eb79b | ||
|
|
0ac5630be0 | ||
|
|
1dda8075cb | ||
|
|
613ec53fdf | ||
|
|
b2bf034a91 | ||
|
|
12482a5ab5 | ||
|
|
f2f52f8518 | ||
|
|
917eec3f25 | ||
|
|
6271e40ce0 | ||
|
|
bc535099ec | ||
|
|
a9c722fcd5 | ||
|
|
f8f73b89cb | ||
|
|
ad28fd9db5 | ||
|
|
9d5fd5db2f | ||
|
|
76c314a23a | ||
|
|
187c19639f | ||
|
|
68dfec210c | ||
|
|
50e678232d | ||
|
|
3b56b249b5 | ||
|
|
be3c76b7bf | ||
|
|
4fa933f4f5 | ||
|
|
a2f57bdd4d | ||
|
|
f4fd538a58 | ||
|
|
5cdabe555f | ||
|
|
60afb15488 | ||
|
|
ba010c0117 | ||
|
|
61b5c8954d | ||
|
|
40147a5265 | ||
|
|
be6cf4dcd5 | ||
|
|
bbea2bceae | ||
|
|
69fbf9e854 | ||
|
|
61d4325d2c | ||
|
|
44ade8b761 | ||
|
|
4e50c96567 | ||
|
|
c0225b7096 | ||
|
|
c31c80c154 | ||
|
|
91fc7466c2 | ||
|
|
5286c16480 | ||
|
|
fc7083dd86 | ||
|
|
8e5d49665b | ||
|
|
46fb3a061c | ||
|
|
74dcd5889f |
@@ -272,7 +272,7 @@ other contributions that are not aligned to this Code of Conduct."*
|
||||
[PEP-8]: https://peps.python.org/pep-0008/
|
||||
[RDD]: https://tom.preston-werner.com/2010/08/23/readme-driven-development
|
||||
[cbeams]: https://cbea.ms/git-commit/#seven-rules
|
||||
[conduct]: CODE-OF-CONDUCT.md
|
||||
[conduct]: CODE_OF_CONDUCT.md
|
||||
[DCO]: https://developercertificate.org/
|
||||
[closing]: https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests
|
||||
[gpg-verify]: https://docs.github.com/en/authentication/managing-commit-signature-verification
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gomod
|
||||
directory: /src/webui
|
||||
schedule:
|
||||
interval: weekly
|
||||
# goyang is pinned to our kernelkit fork via a replace directive and
|
||||
# stepped by hand when we add patches; leave it for Dependabot to ignore.
|
||||
ignore:
|
||||
- dependency-name: github.com/openconfig/goyang
|
||||
|
||||
- package-ecosystem: gomod
|
||||
directory: /src/netbrowse
|
||||
schedule:
|
||||
interval: weekly
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
name: Add issue to project Infix&co
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/add-to-project@v1.0.2
|
||||
- uses: actions/add-to-project@v2
|
||||
with:
|
||||
project-url: https://github.com/orgs/kernelkit/projects/3
|
||||
github-token: ${{ secrets.ADD_TO_PROJECT }}
|
||||
|
||||
@@ -108,17 +108,11 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: "artifact-*"
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create checksums ...
|
||||
run: |
|
||||
for file in *.tar.gz; do
|
||||
sha256sum $file > $file.sha256
|
||||
done
|
||||
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
allowUpdates: true
|
||||
@@ -128,7 +122,7 @@ jobs:
|
||||
prerelease: true
|
||||
tag: "latest-boot"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
artifacts: "*.tar.gz*"
|
||||
artifacts: "*.tar.gz"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
|
||||
@@ -223,15 +223,6 @@ jobs:
|
||||
output/images/*-emmc.img*
|
||||
retention-days: 30
|
||||
|
||||
- name: Create checksums
|
||||
run: |
|
||||
cd output/images/
|
||||
for file in *-sdcard.img *-emmc.img; do
|
||||
if [ -f "$file" ]; then
|
||||
sha256sum "$file" > "$file.sha256"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Upload to release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
@@ -242,7 +233,7 @@ jobs:
|
||||
prerelease: true
|
||||
tag: "latest-boot"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
artifacts: "output/images/*-sdcard.img*,output/images/*-emmc.img*"
|
||||
artifacts: "output/images/*-sdcard.img,output/images/*-emmc.img"
|
||||
|
||||
- name: Generate summary
|
||||
run: |
|
||||
|
||||
@@ -45,6 +45,9 @@ jobs:
|
||||
target: ${{ matrix.target }}
|
||||
enabled: ${{ inputs.use_cache }}
|
||||
|
||||
# WebUI images bundle the mkdocs User's Guide via post-build.sh.
|
||||
- uses: kernelkit/actions/setup-mkdocs@v1.2
|
||||
|
||||
- name: Configure & Build
|
||||
env:
|
||||
INFIX_RELEASE: ${{ steps.vars.outputs.ver }}
|
||||
|
||||
@@ -124,6 +124,9 @@ jobs:
|
||||
with:
|
||||
target: ${{ env.TARGET }}
|
||||
|
||||
# WebUI images bundle the mkdocs User's Guide via post-build.sh.
|
||||
- uses: kernelkit/actions/setup-mkdocs@v1.2
|
||||
|
||||
- name: Configure ${{ env.TARGET }}
|
||||
run: |
|
||||
make ${{ env.TARGET }}_defconfig
|
||||
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
|
||||
- name: Create pull request
|
||||
if: steps.check.outputs.new_release == 'true'
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
github-token: ${{ secrets.KERNEL_UPDATE_TOKEN }}
|
||||
script: |
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
name: Go Vulnerability Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'src/webui/**'
|
||||
- 'src/netbrowse/**'
|
||||
- '.github/workflows/govulncheck.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/webui/**'
|
||||
- 'src/netbrowse/**'
|
||||
- '.github/workflows/govulncheck.yml'
|
||||
schedule:
|
||||
- cron: '5 0 * * 6' # Saturday at 00:05 UTC, same as Coverity
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
govulncheck:
|
||||
if: ${{ github.repository_owner == 'kernelkit' }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
module:
|
||||
- src/webui
|
||||
- src/netbrowse
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Install govulncheck
|
||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
|
||||
- name: Scan ${{ matrix.module }}
|
||||
working-directory: ${{ matrix.module }}
|
||||
run: |
|
||||
# Full report, for the run summary. govulncheck exits non-zero
|
||||
# whenever it finds anything, so don't let it fail the step here.
|
||||
{
|
||||
echo "## govulncheck: ${{ matrix.module }}"
|
||||
echo '```'
|
||||
govulncheck ./... || true
|
||||
echo '```'
|
||||
} | tee -a "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# Gate on vulnerabilities reachable from our code through a
|
||||
# dependency. govulncheck's call-graph analysis is transitive,
|
||||
# so indirect use counts too (we call a dep that calls the bad
|
||||
# symbol). trace[0] is the vulnerable symbol; we key on the
|
||||
# module it lives in. A chain that bottoms out in stdlib is
|
||||
# fixed by bumping the Buildroot host Go, not this module's
|
||||
# go.mod, so it's reported above but doesn't fail the build.
|
||||
# Keep the json scan and jq unguarded so a tool failure fails the
|
||||
# gate closed; only grep's no-match exit (all-clear) is tolerated.
|
||||
govulncheck -format json ./... > scan.json || true
|
||||
called=$(jq -r 'select(.finding.trace[0].function != null) |
|
||||
.finding.trace[0].module' scan.json | sort -u)
|
||||
vulns=$(printf '%s' "$called" | grep -vx stdlib || true)
|
||||
if [ -n "$vulns" ]; then
|
||||
echo "::error::Called vulnerabilities in dependencies: $(echo "$vulns" | paste -sd, -)"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,88 +0,0 @@
|
||||
name: Manny the Manager
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
checkout:
|
||||
required: true
|
||||
type: boolean
|
||||
cleanup:
|
||||
required: true
|
||||
type: boolean
|
||||
peek:
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
inventory:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Disk usage ...
|
||||
run: |
|
||||
cd
|
||||
du -hs .[^.]*
|
||||
- name: Disk inventory (1/2) ...
|
||||
run: |
|
||||
echo "df -h ========================================================================="
|
||||
df -h
|
||||
echo "mounts ========================================================================"
|
||||
mount
|
||||
- name: File inventory (1/2) ...
|
||||
run: |
|
||||
echo "Current directory: $(pwd)"
|
||||
echo "Files in $HOME ================================================================"
|
||||
ls $HOME
|
||||
echo "Find $HOME ===================================================================="
|
||||
find $HOME
|
||||
- name: Container inventory ...
|
||||
run: |
|
||||
echo "Available container images: ==================================================="
|
||||
docker images
|
||||
echo "Available containers: ========================================================="
|
||||
docker ps -a
|
||||
|
||||
checkout:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: kernelkit/actions/cache-restore@v1
|
||||
with:
|
||||
target: x86_64
|
||||
dl-prefix: dl-netconf
|
||||
- name: Disk inventory (2/2) ...
|
||||
run: |
|
||||
echo "df -h ========================================================================="
|
||||
df -h
|
||||
echo "mounts ========================================================================"
|
||||
mount
|
||||
- name: File inventory (2/2) ...
|
||||
run: |
|
||||
echo "Current directory: $(pwd)"
|
||||
echo "Files in $HOME ================================================================"
|
||||
ls $HOME
|
||||
echo "Find $HOME ===================================================================="
|
||||
find $HOME
|
||||
|
||||
peeky:
|
||||
if: ${{ inputs.peek }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Peek & Poke ...
|
||||
run: |
|
||||
whoami
|
||||
ls -l /mnt/
|
||||
cat /mnt/DATALOSS_WARNING_README.txt
|
||||
sudo mkdir /mnt/x-aarch64
|
||||
sudo chown $(id -un):$(id -gn) /mnt/x-aarch64
|
||||
ls -l /mnt/
|
||||
|
||||
cleanup:
|
||||
if: ${{ inputs.cleanup }}
|
||||
needs: [inventory, peeky]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cleaning up cruft ...
|
||||
run: |
|
||||
docker image prune -af
|
||||
docker volume prune -f
|
||||
docker container prune -f
|
||||
@@ -11,17 +11,11 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: "artifact-*"
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create checksums ...
|
||||
run: |
|
||||
for file in *.tar.gz; do
|
||||
sha256sum $file > $file.sha256
|
||||
done
|
||||
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
allowUpdates: true
|
||||
@@ -32,7 +26,7 @@ jobs:
|
||||
prerelease: true
|
||||
tag: "latest"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
artifacts: "*.tar.gz*"
|
||||
artifacts: "*.tar.gz"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
|
||||
@@ -78,22 +78,11 @@ jobs:
|
||||
echo "pre=${{ steps.rel.outputs.pre }}"
|
||||
echo "latest=${{ steps.rel.outputs.latest }}"
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: "artifact-*"
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create checksums ...
|
||||
run: |
|
||||
for file in *.tar.gz; do
|
||||
sha256sum $file > $file.sha256
|
||||
done
|
||||
if ls *.qcow2 &>/dev/null; then
|
||||
for file in *.qcow2; do
|
||||
sha256sum "$file" > "$file.sha256"
|
||||
done
|
||||
fi
|
||||
|
||||
- name: Extract ChangeLog entry ...
|
||||
run: |
|
||||
cat doc/ChangeLog.md | ./utils/extract-changelog.sh > release.md
|
||||
@@ -106,7 +95,7 @@ jobs:
|
||||
makeLatest: ${{ steps.rel.outputs.latest }}
|
||||
discussionCategory: ${{ steps.rel.outputs.cat }}
|
||||
bodyFile: release.md
|
||||
artifacts: "*.tar.gz*,*.qcow2*"
|
||||
artifacts: "*.tar.gz,*.qcow2"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
run: |
|
||||
make ${{ env.TARGET }}_defconfig
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: "artifact-*"
|
||||
merge-multiple: true
|
||||
@@ -143,3 +143,15 @@ jobs:
|
||||
with:
|
||||
name: test-report
|
||||
path: output/images/test-report.pdf
|
||||
|
||||
- name: Generate XPath Coverage Report for ${{ env.TARGET }}
|
||||
if: always()
|
||||
run: |
|
||||
make test-dir="$(pwd)/$TEST_PATH" xpath-coverage-report
|
||||
|
||||
- name: Upload XPath Coverage Report as Artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: xpath-coverage-report
|
||||
path: output/images/xpath-coverage-report.pdf
|
||||
|
||||
@@ -22,22 +22,11 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v6
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: "artifact-*"
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create checksums
|
||||
run: |
|
||||
for file in *.tar.gz; do
|
||||
sha256sum $file > $file.sha256
|
||||
done
|
||||
if ls *.qcow2 &>/dev/null; then
|
||||
for file in *.qcow2; do
|
||||
sha256sum "$file" > "$file.sha256"
|
||||
done
|
||||
fi
|
||||
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
tag: latest
|
||||
@@ -54,7 +43,7 @@ jobs:
|
||||
|
||||
**Commit:** ${{ github.sha }}
|
||||
**Built:** ${{ github.run_id }}
|
||||
artifacts: "*.tar.gz*,*.qcow2*"
|
||||
artifacts: "*.tar.gz,*.qcow2"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
|
||||
@@ -110,9 +110,10 @@ config INFIX_OEM_PATH
|
||||
directory (absolute path) and the Infix post-build.sh will call `git
|
||||
describe -C $INFIX_OEM_PATH`.
|
||||
|
||||
Note: for release builds the global variable INFIX_RELEASE overrides
|
||||
the version information derived from `git describe`. However, the
|
||||
GIT version is always saved as the BUILD_ID in /etc/os-releases.
|
||||
Note: the OS version (VERSION, VERSION_ID, BUILD_ID in
|
||||
/etc/os-release) is always derived from `git describe`. The global
|
||||
variable INFIX_RELEASE does not change it; it only labels the release
|
||||
channel (IMAGE_VERSION) and names the published artifacts.
|
||||
|
||||
endmenu
|
||||
|
||||
|
||||
@@ -1,63 +1,21 @@
|
||||
[![License Badge][]][License] [![Release Badge][]][Release] [![GitHub Status][]][GitHub] [![Coverity Status][]][Coverity Scan] [![Discord][discord-badge]][discord-url]
|
||||
[![License Badge][]][License] [![Release Badge][]][Release] [![GitHub Status][]][GitHub] [![Discord][discord-badge]][discord-url]
|
||||
|
||||
<img align="right" src="doc/logo.png" alt="Infix — Immutable.Friendly.Secure" width=480 padding=10>
|
||||
<img align="right" src="doc/logo.png" alt="Infix — Immutable.Friendly.Secure" width=380 padding=10>
|
||||
|
||||
Turn any ARM or x86 device into a powerful, manageable network appliance
|
||||
in minutes. From $35 Raspberry Pi boards to enterprise switches — deploy
|
||||
routers, IoT gateways, edge devices, or custom network solutions that
|
||||
just work.
|
||||
Infix turns an ARM or x86 device into a managed network appliance. The
|
||||
same OS runs on a $35 Raspberry Pi and on enterprise switching hardware,
|
||||
so you can build a router, an IoT gateway, or an edge device on whatever
|
||||
you have on hand.
|
||||
|
||||
## Our Values
|
||||
More in-depth material is available in our blog and User Guide:
|
||||
|
||||
**🔒 Immutable**
|
||||
Your system never breaks. Read-only filesystem with atomic upgrades
|
||||
means no configuration drift, no corrupted updates, and instant rollback
|
||||
if something goes wrong. Deploy once, trust forever.
|
||||
- <https://www.kernelkit.org/>
|
||||
- <https://www.kernelkit.org/infix/>
|
||||
|
||||
**🤝 Friendly**
|
||||
Actually easy to use. Auto-generated CLI from standard YANG models comes
|
||||
with built-in help for every command — just hit <kbd>?</kbd> or
|
||||
<kbd>TAB</kbd> for context-aware assistance.
|
||||
## See it in action
|
||||
|
||||
Familiar NETCONF & RESTCONF APIs and [comprehensive documentation][4]
|
||||
mean you're never stuck. Whether you're learning networking or managing
|
||||
enterprise infrastructure.
|
||||
|
||||
**🛡️ Secure**
|
||||
Built with security as a foundation, not an afterthought. Minimal
|
||||
attack surface, separation between system and data, and container
|
||||
isolation. Sleep better knowing your infrastructure is protected.
|
||||
|
||||
## Why Choose Infix
|
||||
|
||||
**Hardware Flexibility**: Start with a $35 Raspberry Pi, scale to
|
||||
enterprise switching hardware. Same OS, same tools, same reliability.
|
||||
|
||||
**Standards-Based**: Built around YANG models and IETF standards. Learn
|
||||
once, use everywhere - no vendor lock-in.
|
||||
|
||||
**Container Ready**: Run your applications alongside networking
|
||||
functions. GPIO access, dedicated Ethernet ports, custom protocols —
|
||||
your device, your rules.
|
||||
|
||||
## Use Cases
|
||||
|
||||
1. **Home Labs & Hobbyists**:
|
||||
Transform a Raspberry Pi into a full-featured router with WiFi
|
||||
1. **IoT & Edge Computing**:
|
||||
Bridge devices to the cloud with reliable, updatable gateways
|
||||
1. **Small Business Networks**:
|
||||
Enterprise-grade features without the complexity or cost
|
||||
1. **Developers & Makers**:
|
||||
Test networking concepts, prototype IoT solutions, or build custom
|
||||
appliances
|
||||
1. **Network Professionals**:
|
||||
Consistent tooling from development to production deployment.
|
||||
How about a digital twin using raw Qemu or [GNS3](https://gns3.com/infix)!
|
||||
|
||||
## Quick Example
|
||||
|
||||
Configure an interface in seconds - the CLI guides you with built-in help:
|
||||
The CLI is generated from the [YANG models][inside], so it guides you with
|
||||
built-in help. Here's setting an IP address on an interface:
|
||||
|
||||
<pre><code>admin@infix-12-34-56:/> <b>configure</b>
|
||||
admin@infix-12-34-56:/config/> <b>edit interface eth0</b>
|
||||
@@ -94,38 +52,94 @@ eth0 ethernet UP 52:54:00:12:34:56
|
||||
admin@infix-12-34-56:/> <b>copy running startup</b>
|
||||
</code></pre>
|
||||
|
||||
Notice how <kbd>TAB</kbd> completion shows available options, `show`
|
||||
displays current config, and `diff` shows exactly what changed before
|
||||
you commit your changes with the `leave` command.
|
||||
<kbd>TAB</kbd> completes available options and <kbd>?</kbd> shows online help
|
||||
for each option and argument. `show` displays the current config, and `diff`
|
||||
shows exactly what changed before you commit it with `leave`. See the [CLI
|
||||
documentation][3] for more.
|
||||
|
||||
For more information, see [CLI documentation][3].
|
||||
## Web interface
|
||||
|
||||
## Get Started
|
||||
If the CLI isn't your style, the same configuration is available through the
|
||||
web interface. Log in from a browser, keep an eye on your device from the
|
||||
Status dashboard and use the Configure > Interface setup wizard to create more
|
||||
advanced setups, or just fold out an interface to add an IP address.
|
||||
|
||||
Get [pre-built images][5] for your hardware. Use the CLI, web
|
||||
interface, or standard NETCONF/RESTCONF tools, e.g., `curl`. Add
|
||||
containers for any custom functionality you need.
|
||||
<p>
|
||||
<a href="doc/img/webui-login.png"><img src="doc/img/webui-login.png" alt="Login" align="top" width=220></a>
|
||||
<a href="doc/img/webui-dashboard.png"><img src="doc/img/webui-dashboard.png" alt="Dashboard" width=290></a>
|
||||
<a href="doc/img/webui-wizard.png"><img src="doc/img/webui-wizard.png" alt="Setup wizard" width=260></a>
|
||||
</p>
|
||||
|
||||
### Supported Platforms
|
||||
The web interface is built on the same concepts as the CLI, so operational
|
||||
status and state are kept separate from configuration and commands.
|
||||
|
||||
- **Raspberry Pi 2B/3B/4B/CM4** - Perfect for home labs, learning, and prototyping
|
||||
- **Banana Pi-R3** - Your next home router and gateway
|
||||
- **NanoPi R2S** - Compact dual-port router in a tiny package
|
||||
- **x86_64** - Run in VMs or on mini PCs for development and testing
|
||||
- **Marvell CN9130 CRB, EspressoBIN** - High-performance ARM64 platforms
|
||||
- **Microchip SparX-5i** - Enterprise switching capabilities
|
||||
- **Microchip SAMA7G54-EK** - ARM Cortex-A7
|
||||
- **NXP i.MX8MP EVK** - Highly capable ARM64 SoC
|
||||
- **StarFive VisionFive2** - RISC-V architecture support
|
||||
## Try it in 5 minutes
|
||||
|
||||
*Why start with Raspberry Pi?* It's affordable, widely available, has
|
||||
built-in WiFi + Ethernet, and runs the exact same Infix OS you'd deploy
|
||||
in production. Perfect for learning, prototyping, or even small-scale
|
||||
deployments.
|
||||
You don't need hardware to get started:
|
||||
|
||||
- **In a virtual lab** — run a full topology in [GNS3][gns3-post] and test
|
||||
networks entirely in software.
|
||||
- **From source** — [build it and `make run`][build-post] to boot Infix in
|
||||
QEMU, from `git clone` to pinging the internet.
|
||||
- **On real hardware** — grab a [pre-built image][5] for your board, or run
|
||||
the `x86_64` image in any VM.
|
||||
|
||||
Log in with `admin` / `admin` on the virtual and pre-built images. On
|
||||
shipped products the factory-reset credentials are customizable — we
|
||||
typically provision a unique per-device password stored in EEPROM/VPD.
|
||||
|
||||
## Supported hardware
|
||||
|
||||
- **Raspberry Pi 2B/3B/4B/CM4** - a good starting point; built-in WiFi and Ethernet
|
||||
- **Banana Pi-R64/R3/R3 Mini/R4** - multi-port routers and gateways
|
||||
- **NanoPi R2S** - compact dual-port router
|
||||
- **x86_64** - VMs and mini PCs, for development or production
|
||||
- **Marvell CN9130 CRB, EspressoBIN** - ARM64 development boards
|
||||
- **Microchip SparX-5i** - enterprise switching
|
||||
- **Microchip SAMA7G54-EK** - ARM Cortex-A7 evaluation kit
|
||||
- **NXP i.MX8MP EVK** - ARM64 SoC evaluation kit
|
||||
- **StarFive VisionFive2** - RISC-V board
|
||||
|
||||
*Why start with Raspberry Pi?* It's cheap, easy to get hold of, has
|
||||
built-in WiFi and Ethernet, and runs the same Infix you'd deploy in
|
||||
production — so what you learn on it carries straight over.
|
||||
|
||||
> [!TIP]
|
||||
> 📖 **[Complete documentation][4]** • 💬 **[Join our Discord][discord-url]**
|
||||
|
||||
## Technical Details
|
||||
## Why Infix
|
||||
|
||||
**🔒 Immutable**
|
||||
Read-only filesystem with atomic upgrades. An update either applies
|
||||
cleanly or rolls back, so a failed upgrade or a power cut midway through
|
||||
won't leave you with a half-broken system.
|
||||
|
||||
**🤝 Friendly**
|
||||
The CLI is generated from the YANG models, so every command carries its
|
||||
own help — hit <kbd>?</kbd> or <kbd>TAB</kbd> to see what's available.
|
||||
The same models are reachable over NETCONF and RESTCONF, with
|
||||
[documentation][4] for when you get stuck.
|
||||
|
||||
**🛡️ Secure**
|
||||
A small attack surface, separation between system and data, and
|
||||
container isolation. Since the system partition is read-only, a
|
||||
compromised service or container can't rewrite the OS underneath it.
|
||||
|
||||
## Use cases
|
||||
|
||||
1. **Home labs & hobbyists**:
|
||||
Turn a Raspberry Pi into a router with WiFi
|
||||
1. **IoT & edge**:
|
||||
Build gateways you can update in the field
|
||||
1. **Small business networks**:
|
||||
Routing, firewalling, and VLANs on affordable hardware
|
||||
1. **Developers & makers**:
|
||||
Prototype networking ideas, or build a custom appliance with containers
|
||||
1. **Network professionals**:
|
||||
The same tooling from lab to production — spin up a digital twin in raw
|
||||
Qemu or [GNS3](https://gns3.com/infix)
|
||||
|
||||
## Under the hood
|
||||
|
||||
<a href="https://bitsign.se">
|
||||
<picture>
|
||||
@@ -135,27 +149,29 @@ deployments.
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
Built on proven open-source foundations: [Linux][0], [Buildroot][1], and
|
||||
[sysrepo][2] — for reliability you can trust:
|
||||
Built on [Linux][0], [Buildroot][1], and [sysrepo][2]:
|
||||
|
||||
- **Immutable OS**: Read-only filesystem, atomic updates, instant rollback
|
||||
- **YANG Configuration**: Industry-standard models with auto-generated tooling
|
||||
- **Hardware Acceleration**: Linux switchdev support for wire-speed packet processing
|
||||
- **Container Integration**: Docker support with flexible network and hardware access
|
||||
- **Memory Efficient**: Runs comfortably on devices with as little as 256 MB RAM
|
||||
- **Code Signing**: Releases are cryptographically signed for integrity verification
|
||||
- **Immutable OS**: read-only filesystem, atomic updates, rollback on failure
|
||||
- **YANG configuration**: standard models with an auto-generated CLI and APIs
|
||||
- **Hardware acceleration**: switchdev offload for wire-speed forwarding
|
||||
- **Container integration**: Docker, with access to host network and hardware
|
||||
- **Memory efficient**: runs on devices with as little as 256 MB RAM
|
||||
- **Code signing**: releases are cryptographically signed
|
||||
|
||||
Perfect for everything from resource-constrained edge devices to
|
||||
high-throughput network appliances.
|
||||
|
||||
With the entire system modeled in YANG, scalability is no longer an
|
||||
issue, be it in development, testing, or end users deploying and
|
||||
monitoring their devices. All knobs and dials are accessible from the
|
||||
CLI (console/SSH), or remotely using the native NETCONF or RESTCONF
|
||||
APIs.
|
||||
Because the whole system is modeled in YANG, every setting is reachable
|
||||
the same way: from the CLI over console or SSH, or remotely over the
|
||||
native NETCONF and RESTCONF APIs. The same models drive development,
|
||||
testing, and day-to-day monitoring.
|
||||
|
||||
> Check the *[Latest Build][]* for bleeding-edge features.
|
||||
|
||||
## Contributing
|
||||
|
||||
Bug reports, ideas, and pull requests are welcome. Start with
|
||||
[CONTRIBUTING][contributing] and the [code of conduct][coc]. Found a
|
||||
security issue? Follow the [security policy][security]. Need a hand?
|
||||
See [support options][support] or [join us on Discord][discord-url].
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
@@ -171,6 +187,13 @@ APIs.
|
||||
[3]: https://www.kernelkit.org/infix/latest/cli/introduction/
|
||||
[4]: https://www.kernelkit.org/infix/
|
||||
[5]: https://github.com/kernelkit/infix/releases/latest
|
||||
[inside]: https://www.kernelkit.org/posts/inside-infix/
|
||||
[gns3-post]: https://www.kernelkit.org/posts/infix-in-gns3/
|
||||
[build-post]: https://www.kernelkit.org/posts/building-infix-from-source/
|
||||
[contributing]: .github/CONTRIBUTING.md
|
||||
[coc]: .github/CODE_OF_CONDUCT.md
|
||||
[security]: .github/SECURITY.md
|
||||
[support]: .github/SUPPORT.md
|
||||
[Latest Build]: https://github.com/kernelkit/infix/releases/tag/latest "Latest build"
|
||||
[License]: https://en.wikipedia.org/wiki/GPL_license
|
||||
[License Badge]: https://img.shields.io/badge/License-GPL%20v2-blue.svg
|
||||
|
||||
@@ -120,3 +120,29 @@ grep -qsE '^/bin/clish$$' "$TARGET_DIR/etc/shells" \
|
||||
if [ "$BR2_PACKAGE_HOST_PYTHON_YANGDOC" = "y" ]; then
|
||||
mkyangdoc "$BINARIES_DIR/yangdoc.html"
|
||||
fi
|
||||
|
||||
# Bundle the mkdocs User's Guide into the rootfs, served by the WebUI's
|
||||
# nginx at /guide/. Only when the WebUI is present (nothing serves it
|
||||
# otherwise, and it keeps minimal images small) and mkdocs is on the build
|
||||
# host. Best-effort: a failed build warns but does not abort the image
|
||||
# build, and the WebUI hides its User Guide entry when the docs are absent.
|
||||
mkuserguide()
|
||||
{
|
||||
local cfg dst
|
||||
cfg="$(readlink -f "$common/../..")/mkdocs.yml"
|
||||
dst="$TARGET_DIR/var/www/guide"
|
||||
|
||||
if ! command -v mkdocs >/dev/null 2>&1; then
|
||||
ixmsg "mkdocs not found, skipping User's Guide bundling"
|
||||
return
|
||||
fi
|
||||
ixmsg "Building User's Guide into $dst"
|
||||
if ! mkdocs build -f "$cfg" -d "$dst" --clean --quiet; then
|
||||
ixmsg "WARNING: mkdocs build failed, shipping without on-device User's Guide"
|
||||
rm -rf "$dst"
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$BR2_PACKAGE_WEBUI" = "y" ]; then
|
||||
mkuserguide
|
||||
fi
|
||||
|
||||
@@ -253,3 +253,19 @@ config QEMU_NET_PORTS
|
||||
int "Number of Rocker switch ports"
|
||||
depends on QEMU_NET_ROCKER
|
||||
default 10
|
||||
|
||||
comment "Wireless"
|
||||
|
||||
config QEMU_WIFI_RADIOS
|
||||
int "Number of virtual WiFi radios (mac80211_hwsim)"
|
||||
default 0
|
||||
help
|
||||
Number of simulated 802.11 radios to create with mac80211_hwsim.
|
||||
The count is passed to the guest via the opt/wifi fw_cfg and loaded
|
||||
by the 00-hwsim boot script.
|
||||
|
||||
0 (default) means no radios, so nothing wireless clutters the guest.
|
||||
Set a small number (e.g. 2) for local AP/station experiments.
|
||||
|
||||
Note: 'make run' is a single guest with no inter-guest wireless
|
||||
medium, so these radios only reach each other within this one guest.
|
||||
|
||||
@@ -310,6 +310,16 @@ EOF
|
||||
echo -n "-fw_cfg name=opt/vpd,file=$vpd_file"
|
||||
}
|
||||
|
||||
wifi_args()
|
||||
{
|
||||
# Number of mac80211_hwsim radios for the guest, read at boot by 00-hwsim
|
||||
# (fw_cfg opt/wifi). Default 0 -> no radios, no wifi clutter.
|
||||
radios=${CONFIG_QEMU_WIFI_RADIOS:-0}
|
||||
wifi="${qdir}/wifi"
|
||||
echo "$radios" > "$wifi"
|
||||
echo -n "-fw_cfg name=opt/wifi,file=$wifi "
|
||||
}
|
||||
|
||||
wdt_args()
|
||||
{
|
||||
echo -n "-device i6300esb "
|
||||
@@ -387,6 +397,7 @@ run_qemu()
|
||||
$(usb_args) \
|
||||
$(host_args) \
|
||||
$(net_args) \
|
||||
$(wifi_args) \
|
||||
$(wdt_args) \
|
||||
$(rtc_args) \
|
||||
$(vpd_args) \
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
RESTCONF_URL=https://127.0.0.1/restconf
|
||||
INSECURE_TLS=1
|
||||
# Spool firmware uploads (and any other temp files) to persistent storage
|
||||
# instead of the RAM-backed /tmp to enable push-upgrades on low-memory systems.
|
||||
TMPDIR=/var/tmp
|
||||
@@ -1,3 +0,0 @@
|
||||
service <!> name:hostapd :%i \
|
||||
[2345] hostapd -P/var/run/hostapd-%i.pid /etc/hostapd-%i.conf \
|
||||
-- Wi-Fi Access Point @%i
|
||||
@@ -0,0 +1,3 @@
|
||||
service name:wpa_supplicant :%i \
|
||||
[2345] wpa_supplicant -s -i %i -c /etc/wpa_supplicant-%i.conf -P/var/run/wpa_supplicant-%i.pid \
|
||||
-- Wi-Fi Mesh @%i
|
||||
@@ -0,0 +1 @@
|
||||
../restconf.app
|
||||
@@ -0,0 +1,3 @@
|
||||
allow 127.0.0.1;
|
||||
allow ::1;
|
||||
deny all;
|
||||
@@ -0,0 +1 @@
|
||||
restconf-access-local.conf
|
||||
@@ -1,5 +1,6 @@
|
||||
# /telemetry/optics is for streaming (not used atm)
|
||||
location ~ ^/(restconf|yang|.well-known)/ {
|
||||
include /etc/nginx/restconf-access.conf;
|
||||
grpc_pass grpc://[::1]:10080;
|
||||
grpc_set_header Host $host;
|
||||
grpc_set_header X-Real-IP $remote_addr;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
if [ -s /run/os-update ]; then
|
||||
printf '\n\033[1;33m *** %s ***\033[0m\n\n' "$(cat /run/os-update)"
|
||||
fi
|
||||
@@ -0,0 +1 @@
|
||||
f /run/os-update 0666 admin admin
|
||||
@@ -654,10 +654,17 @@ def main():
|
||||
err = 1
|
||||
|
||||
os.umask(0o337)
|
||||
# Write atomically: confd reads system.json as soon as ixinit signals
|
||||
# done, so a truncated in-place write could be read half-written. Write
|
||||
# to a temp file in the same dir, then rename (atomic on the same fs).
|
||||
# pylint: disable=invalid-name
|
||||
with open(SYSTEM_JSON, "w", encoding="ascii") as f:
|
||||
tmp = SYSTEM_JSON + ".tmp"
|
||||
with open(tmp, "w", encoding="ascii") as f:
|
||||
json.dump(out, f)
|
||||
shutil.chown(SYSTEM_JSON, user="root", group="wheel")
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
shutil.chown(tmp, user="root", group="wheel")
|
||||
os.replace(tmp, SYSTEM_JSON)
|
||||
return err
|
||||
|
||||
|
||||
|
||||
@@ -43,5 +43,12 @@ for iface in $ports; do
|
||||
ip link set "$iface" group port
|
||||
done
|
||||
|
||||
# The mac80211_hwsim monitor tap (hwsim test images only) is a debug
|
||||
# observer, not a user-facing interface. It cannot be deleted (no rtnl
|
||||
# link ops), so classify it as internal like the DSA CPU interfaces.
|
||||
if [ -d /sys/class/net/hwsim0 ]; then
|
||||
ip link set dev hwsim0 group internal
|
||||
fi
|
||||
|
||||
# At least loopback in iface group
|
||||
ip link set lo group iface
|
||||
|
||||
@@ -176,19 +176,25 @@ def parse_phy_info(phy_name):
|
||||
if current_band and current_band.get('frequencies'):
|
||||
result['bands'].append(current_band)
|
||||
|
||||
# Determine band names and assign band numbers
|
||||
# Keep only the bands Infix supports (2.4/5/6 GHz), naming them by their
|
||||
# first frequency. Hardware may expose others (e.g. S1G on hwsim) that we
|
||||
# neither configure nor report.
|
||||
bands = []
|
||||
for band in result['bands']:
|
||||
if band['frequencies']:
|
||||
freq = band['frequencies'][0]
|
||||
if 2400 <= freq <= 2500:
|
||||
band['name'] = '2.4GHz'
|
||||
band['band'] = 1
|
||||
elif 5150 <= freq <= 5900:
|
||||
band['name'] = '5GHz'
|
||||
band['band'] = 2
|
||||
elif 5955 <= freq <= 7115:
|
||||
band['name'] = '6GHz'
|
||||
band['band'] = 3
|
||||
freq = band['frequencies'][0]
|
||||
if 2400 <= freq <= 2500:
|
||||
band['name'] = '2.4GHz'
|
||||
band['band'] = 1
|
||||
elif 5150 <= freq <= 5900:
|
||||
band['name'] = '5GHz'
|
||||
band['band'] = 2
|
||||
elif 5955 <= freq <= 7115:
|
||||
band['name'] = '6GHz'
|
||||
band['band'] = 3
|
||||
else:
|
||||
continue
|
||||
bands.append(band)
|
||||
result['bands'] = bands
|
||||
|
||||
# Set max TX power
|
||||
if max_power is not None:
|
||||
@@ -207,7 +213,9 @@ def parse_phy_info(phy_name):
|
||||
|
||||
# Map driver to manufacturer
|
||||
driver_lower = driver_name.lower()
|
||||
if 'mt' in driver_lower or 'mediatek' in driver_lower:
|
||||
if 'hwsim' in driver_lower:
|
||||
result['manufacturer'] = 'Virtual (hwsim)'
|
||||
elif 'mt' in driver_lower or 'mediatek' in driver_lower:
|
||||
result['manufacturer'] = 'MediaTek Inc.'
|
||||
elif 'rtw' in driver_lower or 'realtek' in driver_lower:
|
||||
result['manufacturer'] = 'Realtek Semiconductor Corp.'
|
||||
@@ -257,15 +265,15 @@ def parse_interface_info(ifname):
|
||||
for line in output.splitlines():
|
||||
stripped = line.strip()
|
||||
|
||||
# Interface type
|
||||
# Interface type (can be multi-word, e.g. "mesh point")
|
||||
if stripped.startswith('type '):
|
||||
result['iftype'] = stripped.split()[1]
|
||||
result['iftype'] = ' '.join(stripped.split()[1:])
|
||||
|
||||
# MAC address
|
||||
elif stripped.startswith('addr '):
|
||||
result['mac'] = stripped.split()[1]
|
||||
|
||||
# SSID
|
||||
# SSID (AP mode) or mesh-id (mesh point mode) — kernel uses same attr
|
||||
elif stripped.startswith('ssid '):
|
||||
result['ssid'] = decode_iw_ssid(' '.join(stripped.split()[1:]))
|
||||
|
||||
@@ -538,6 +546,43 @@ def parse_link(ifname):
|
||||
return result
|
||||
|
||||
|
||||
def parse_phy_caps(phy_name):
|
||||
"""
|
||||
Parse 'iw phy <name> info' for HT and VHT capability bitmasks.
|
||||
Returns: {ht_cap: int, vht_cap: int}
|
||||
|
||||
iw phy info output format:
|
||||
Capabilities: 0x1ef
|
||||
...
|
||||
VHT Capabilities (0x339071b2):
|
||||
...
|
||||
"""
|
||||
actual_phy = normalize_phy_name(phy_name)
|
||||
output = run_iw('phy', actual_phy, 'info')
|
||||
if not output:
|
||||
output = run_iw(actual_phy, 'info')
|
||||
if not output:
|
||||
return {'ht_cap': 0, 'vht_cap': 0}
|
||||
|
||||
ht_cap = 0
|
||||
vht_cap = 0
|
||||
|
||||
for line in output.splitlines():
|
||||
stripped = line.strip()
|
||||
|
||||
# HT Capabilities: "Capabilities: 0x1ef"
|
||||
ht_match = re.match(r'Capabilities:\s+(0x[0-9a-fA-F]+)', stripped)
|
||||
if ht_match:
|
||||
ht_cap = int(ht_match.group(1), 16)
|
||||
|
||||
# VHT Capabilities: "VHT Capabilities (0x339071b2):"
|
||||
vht_match = re.match(r'VHT Capabilities\s+\((0x[0-9a-fA-F]+)\)', stripped)
|
||||
if vht_match:
|
||||
vht_cap = int(vht_match.group(1), 16)
|
||||
|
||||
return {'ht_cap': ht_cap, 'vht_cap': vht_cap}
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print(json.dumps({
|
||||
@@ -548,7 +593,8 @@ def main():
|
||||
'info': 'Get PHY or interface information (requires device)',
|
||||
'survey': 'Get channel survey data (requires interface)',
|
||||
'station': 'Get connected stations in AP mode (requires interface)',
|
||||
'link': 'Get link info in station mode (requires interface)'
|
||||
'link': 'Get link info in station mode (requires interface)',
|
||||
'caps': 'Get HT/VHT capability bitmasks (requires PHY/radio)'
|
||||
},
|
||||
'examples': [
|
||||
'iw.py list',
|
||||
@@ -557,7 +603,8 @@ def main():
|
||||
'iw.py info wlan0',
|
||||
'iw.py station wifi0',
|
||||
'iw.py link wlan0',
|
||||
'iw.py survey wlan0'
|
||||
'iw.py survey wlan0',
|
||||
'iw.py caps radio0'
|
||||
]
|
||||
}, indent=2))
|
||||
sys.exit(1)
|
||||
@@ -594,6 +641,11 @@ def main():
|
||||
data = {'error': 'survey command requires interface argument'}
|
||||
else:
|
||||
data = parse_survey(sys.argv[2])
|
||||
elif command == 'caps':
|
||||
if len(sys.argv) < 3:
|
||||
data = {'error': 'caps command requires PHY/radio argument'}
|
||||
else:
|
||||
data = parse_phy_caps(sys.argv[2])
|
||||
else:
|
||||
data = {'error': f'Unknown command: {command}'}
|
||||
|
||||
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/bin/sh
|
||||
# Check for available software updates and notify on login if one exists.
|
||||
# Called by the scheduler.
|
||||
|
||||
NOTIFY_FILE=/run/os-update
|
||||
TAG=os-update
|
||||
|
||||
# Source os-release for VERSION and IMAGE_ID
|
||||
if [ ! -f /etc/os-release ]; then
|
||||
logger -t "$TAG" "ERROR: /etc/os-release not found"
|
||||
exit 1
|
||||
fi
|
||||
. /etc/os-release
|
||||
|
||||
# Dev/dirty builds have no comparable semver — always show the latest release
|
||||
IS_RELEASE=true
|
||||
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then
|
||||
IS_RELEASE=false
|
||||
fi
|
||||
|
||||
# Read configured update-url from running config, fall back to upstream
|
||||
UPDATE_URL=$(copy running-config \
|
||||
-x '/ietf-system:system/infix-system:software/check-update/update-url' \
|
||||
2>/dev/null \
|
||||
| jq -r '.. | objects | ."update-url"? // empty')
|
||||
UPDATE_URL=${UPDATE_URL:-"https://github.com/kernelkit/infix"}
|
||||
|
||||
# Derive API URL from the configured update URL.
|
||||
# Default (github.com): https://github.com/org/repo → https://api.github.com/repos/org/repo
|
||||
REPO=$(echo "$UPDATE_URL" | sed 's|https://github.com/||; s|/*$||')
|
||||
API_URL="https://api.github.com/repos/${REPO}/releases/latest"
|
||||
|
||||
LATEST_TAG=$(curl -sSL --max-time 10 "$API_URL" 2>/dev/null \
|
||||
| jq -r '.tag_name // empty')
|
||||
if [ -z "$LATEST_TAG" ]; then
|
||||
logger -p daemon.info -t "$TAG" "Update check skipped: could not reach ${API_URL}"
|
||||
exit 0
|
||||
fi
|
||||
LATEST=${LATEST_TAG#v}
|
||||
|
||||
# Compare: is $1 strictly newer than $2?
|
||||
newer() {
|
||||
[ "$1" = "$2" ] && return 1
|
||||
[ "$(printf '%s\n%s' "$1" "$2" | sort -V | tail -1)" = "$1" ]
|
||||
}
|
||||
|
||||
if [ "$IS_RELEASE" = false ] || newer "$LATEST" "$VERSION"; then
|
||||
RELEASE_URL="${UPDATE_URL}/releases/${LATEST_TAG}"
|
||||
MSG="Software update available: ${LATEST_TAG}, running ${VERSION} (see ${RELEASE_URL})"
|
||||
logger -t "$TAG" "$MSG"
|
||||
printf '%s\n' "$MSG" > "$NOTIFY_FILE"
|
||||
else
|
||||
logger -p daemon.debug -t "$TAG" "No update available (current: $VERSION, latest: $LATEST)"
|
||||
printf '' > "$NOTIFY_FILE"
|
||||
fi
|
||||
+1
-1
Submodule buildroot updated: d8431707cf...dd816b9233
@@ -27,7 +27,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
|
||||
BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.33"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
@@ -175,7 +175,6 @@ BR2_PACKAGE_FIREWALL=y
|
||||
BR2_PACKAGE_IITO=y
|
||||
BR2_PACKAGE_KEYACK=y
|
||||
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
|
||||
BR2_PACKAGE_LANDING=y
|
||||
BR2_PACKAGE_LOWDOWN=y
|
||||
BR2_PACKAGE_MCD=y
|
||||
BR2_PACKAGE_MDNS_ALIAS=y
|
||||
@@ -187,6 +186,8 @@ BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
|
||||
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
|
||||
BR2_PACKAGE_TETRIS=y
|
||||
BR2_PACKAGE_ROUSETTE=y
|
||||
# BR2_PACKAGE_LANDING is not set
|
||||
BR2_PACKAGE_WEBUI=y
|
||||
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
|
||||
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
|
||||
BR2_PACKAGE_PCIUTILS=y
|
||||
|
||||
@@ -27,7 +27,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
|
||||
BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.33"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
@@ -144,6 +144,7 @@ BR2_PACKAGE_FIREWALL=y
|
||||
BR2_PACKAGE_IITO=y
|
||||
BR2_PACKAGE_KEYACK=y
|
||||
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
|
||||
BR2_PACKAGE_LANDING=y
|
||||
BR2_PACKAGE_LOWDOWN=y
|
||||
BR2_PACKAGE_MCD=y
|
||||
BR2_PACKAGE_MDNS_ALIAS=y
|
||||
|
||||
@@ -28,7 +28,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
|
||||
BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.33"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/arm/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
@@ -162,7 +162,6 @@ BR2_PACKAGE_FIREWALL=y
|
||||
BR2_PACKAGE_IITO=y
|
||||
BR2_PACKAGE_KEYACK=y
|
||||
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
|
||||
BR2_PACKAGE_LANDING=y
|
||||
BR2_PACKAGE_LOWDOWN=y
|
||||
BR2_PACKAGE_MCD=y
|
||||
BR2_PACKAGE_MDNS_ALIAS=y
|
||||
@@ -170,6 +169,8 @@ BR2_PACKAGE_NETBROWSE=y
|
||||
BR2_PACKAGE_ONIEPROM=y
|
||||
BR2_PACKAGE_TETRIS=y
|
||||
BR2_PACKAGE_ROUSETTE=y
|
||||
# BR2_PACKAGE_LANDING is not set
|
||||
BR2_PACKAGE_WEBUI=y
|
||||
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
|
||||
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
|
||||
IMAGE_ITB_AUX=y
|
||||
|
||||
@@ -28,7 +28,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
|
||||
BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.33"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/arm/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
@@ -142,6 +142,7 @@ BR2_PACKAGE_FIREWALL=y
|
||||
BR2_PACKAGE_IITO=y
|
||||
BR2_PACKAGE_KEYACK=y
|
||||
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
|
||||
BR2_PACKAGE_LANDING=y
|
||||
BR2_PACKAGE_LOWDOWN=y
|
||||
BR2_PACKAGE_MCD=y
|
||||
BR2_PACKAGE_MDNS_ALIAS=y
|
||||
|
||||
@@ -194,7 +194,6 @@ BR2_PACKAGE_FIREWALL=y
|
||||
BR2_PACKAGE_IITO=y
|
||||
BR2_PACKAGE_KEYACK=y
|
||||
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
|
||||
BR2_PACKAGE_LANDING=y
|
||||
BR2_PACKAGE_LOWDOWN=y
|
||||
BR2_PACKAGE_MCD=y
|
||||
BR2_PACKAGE_MDNS_ALIAS=y
|
||||
@@ -206,6 +205,8 @@ BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
|
||||
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
|
||||
BR2_PACKAGE_TETRIS=y
|
||||
BR2_PACKAGE_ROUSETTE=y
|
||||
# BR2_PACKAGE_LANDING is not set
|
||||
BR2_PACKAGE_WEBUI=y
|
||||
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
|
||||
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
|
||||
BR2_PACKAGE_PCIUTILS=y
|
||||
|
||||
@@ -26,7 +26,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
|
||||
BR2_ROOTFS_POST_BUILD_SCRIPT="board/qemu/x86_64/post-build.sh ${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.33"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
@@ -146,6 +146,7 @@ INFIX_DOC="https://www.kernelkit.org/infix/"
|
||||
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
|
||||
BR2_PACKAGE_FEATURE_GPS=y
|
||||
BR2_PACKAGE_FEATURE_WIFI=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_HWSIM=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_MEDIATEK=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_QUALCOMM=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_REALTEK=y
|
||||
@@ -169,7 +170,6 @@ BR2_PACKAGE_FIREWALL=y
|
||||
BR2_PACKAGE_IITO=y
|
||||
BR2_PACKAGE_KEYACK=y
|
||||
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
|
||||
BR2_PACKAGE_LANDING=y
|
||||
BR2_PACKAGE_LOWDOWN=y
|
||||
BR2_PACKAGE_MCD=y
|
||||
BR2_PACKAGE_MDNS_ALIAS=y
|
||||
@@ -181,6 +181,8 @@ BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
|
||||
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
|
||||
BR2_PACKAGE_TETRIS=y
|
||||
BR2_PACKAGE_ROUSETTE=y
|
||||
# BR2_PACKAGE_LANDING is not set
|
||||
BR2_PACKAGE_WEBUI=y
|
||||
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
|
||||
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
|
||||
BR2_PACKAGE_PCIUTILS=y
|
||||
|
||||
@@ -26,7 +26,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
|
||||
BR2_ROOTFS_POST_BUILD_SCRIPT="board/qemu/x86_64/post-build.sh ${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.33"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
@@ -141,6 +141,7 @@ BR2_PACKAGE_FIREWALL=y
|
||||
BR2_PACKAGE_IITO=y
|
||||
BR2_PACKAGE_KEYACK=y
|
||||
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
|
||||
BR2_PACKAGE_LANDING=y
|
||||
BR2_PACKAGE_LOWDOWN=y
|
||||
BR2_PACKAGE_MCD=y
|
||||
BR2_PACKAGE_MDNS_ALIAS=y
|
||||
|
||||
+74
-3
@@ -3,6 +3,76 @@ Change Log
|
||||
|
||||
All notable changes to the project are documented in this file.
|
||||
|
||||
[v26.06.0][] - 2026-07-01
|
||||
-------------------------
|
||||
|
||||
> [!NOTE]
|
||||
> Noteworthy changes and additions in this release:
|
||||
>
|
||||
> **🌐 Web Interface:** Infix gets its first-ever web interface! Browse live
|
||||
> status and a full operational tree, handle common tasks from curated
|
||||
> configuration pages, and drop into a YANG tree editor for everything else.
|
||||
> A maintenance section covers firmware upgrade, backup & restore, and more.
|
||||
>
|
||||
> **📶 Wi-Fi Roaming & Mesh:** Access points sharing an SSID can hand clients
|
||||
> off seamlessly with 802.11k/v/r, form a cable-free 802.11s mesh backhaul,
|
||||
> and steer dual-band clients onto the faster 5/6 GHz band.
|
||||
>
|
||||
> **🗓️ System Scheduling:** Reusable time schedules based on ietf-schedule
|
||||
> (RFC 9922) let features like scheduled reboot and software update checks
|
||||
> run on a recurring, cron-style calendar.
|
||||
|
||||
### Changes
|
||||
|
||||
- Upgrade Linux kernel to 6.18.37 (LTS)
|
||||
- Upgrade Buildroot to 2025.02.15 (LTS)
|
||||
- Add basic web interface: static status pages and a tree view of operational
|
||||
status. Curated configuration pages for some common tasks and a YANG tree
|
||||
editor for the rest. Also includes a maintenance section for firmware
|
||||
upgrade, backup & restore, and more
|
||||
- Add Wi-Fi roaming for fast, seamless handoff between access points that
|
||||
share an SSID: 802.11k, 802.11v and 802.11r (over-the-air FT). See the
|
||||
[Wi-Fi][wifi] guide for details
|
||||
- Add Wi-Fi 802.11s mesh support, letting access points form a wireless
|
||||
backhaul between each other without cabling
|
||||
- Add band steering for dual-band access points, nudging dual-band
|
||||
clients onto the faster 5/6 GHz band
|
||||
- Add `legacy-rates` option to re-enable 802.11b rates on 2.4 GHz for
|
||||
old IoT devices (disabled by default)
|
||||
- Add system scheduling based on ietf-schedule (RFC 9922), using the
|
||||
iCalendar recurrence grouping pruned to cron-expressible rules. Schedules
|
||||
are reusable time-specs; features (`scheduled-reboot`,
|
||||
`software/check-update`) trigger off them via a schedule reference
|
||||
- Configuring multiple BSS (more than one SSID) on a single Wi-Fi radio now
|
||||
requires an explicitly configured MAC address per BSS
|
||||
- New operational `advertised-pmd-types` leaf-list on each Ethernet interface,
|
||||
exposing the link modes currently advertised, to compare against the
|
||||
`supported-pmd-types` introduced in v26.05.0
|
||||
- Release assets no longer ship separate `.sha256` checksum files; the
|
||||
download page now publishes a SHA-256 checksum for each asset directly
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix #941: a VETH pair can now connect two containers directly, with both
|
||||
ends assigned to containers.
|
||||
- Enabling IP masquerading in the firewall no longer enables IP forwarding on
|
||||
all interfaces. This has been an issue ever since the firewall support was
|
||||
introduced in v25.10.0
|
||||
- Fix file permission regression in `/cfg/startup-config.cfg`, causing the
|
||||
default `admin` user no permission to read or write the file from shell
|
||||
- Fix admin url shown for HTTP/HTTPS links in <https://network.local> browser,
|
||||
used pre-conflict resolution hostname.local, instead of hostname-2.local
|
||||
- Fix unreadable per-port temperature sensor names in `show hardware` on
|
||||
Marvell based switches: each sensor is now named after the front-panel port
|
||||
it serves (e.g. `e1`, `e2`) instead of a raw device-tree path. `show
|
||||
system` also reports a representative SoC temperature on CN913x platforms
|
||||
- Fix missing `contact` and `location` settings in operational status; the
|
||||
values were configurable but never returned on RESTCONF/NETCONF reads
|
||||
- Fix spurious YANG validation warnings, for NTP and WireGuard configuration,
|
||||
emitted on every NETCONF session and schema load
|
||||
|
||||
[wifi]: https://www.kernelkit.org/infix/latest/wifi/
|
||||
|
||||
[v26.05.0][] - 2026-05-29
|
||||
-------------------------
|
||||
|
||||
@@ -60,9 +130,9 @@ All notable changes to the project are documented in this file.
|
||||
show`, which were not expanded, so `show interface` and other operational
|
||||
reads failed. Ranges are now expanded and listed correctly
|
||||
|
||||
[ethernet]: ethernet.md#restricting-advertised-link-modes
|
||||
[ethernet]: https://www.kernelkit.org/infix/latest/ethernet/#restricting-advertised-link-modes
|
||||
[BPI-R3]: https://docs.banana-pi.org/en/BPI-R3/BananaPi_BPI-R3
|
||||
[AcerConnectVero]: ../board/aarch64/acer-connect-vero-w6m/
|
||||
[AcerConnectVero]: https://github.com/kernelkit/infix/tree/main/board/aarch64/acer-connect-vero-w6m
|
||||
|
||||
[v26.04.0][] - 2026-04-30
|
||||
-------------------------
|
||||
@@ -2135,7 +2205,8 @@ Supported YANG models in addition to those used by sysrepo and netopeer:
|
||||
- N/A
|
||||
|
||||
[buildroot]: https://buildroot.org/
|
||||
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v26.05.0...HEAD
|
||||
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v26.06.0...HEAD
|
||||
[v26.06.0]: https://github.com/kernelkit/infix/compare/v26.05.0...v26.06.0
|
||||
[v26.05.0]: https://github.com/kernelkit/infix/compare/v26.04.0...v26.05.0
|
||||
[v26.04.0]: https://github.com/kernelkit/infix/compare/v26.03.0...v26.04.0
|
||||
[v26.03.0]: https://github.com/kernelkit/infix/compare/v26.02.0...v26.03.0
|
||||
|
||||
@@ -21,6 +21,7 @@ regression test system solely relies on NETCONF and RESTCONF.
|
||||
- [Introduction](introduction.md)
|
||||
- [System Configuration](system.md)
|
||||
- [Network Configuration](networking.md)
|
||||
- [Wi-Fi](wifi.md)
|
||||
- [DHCP Server](dhcp.md)
|
||||
- [Syslog Support](syslog.md)
|
||||
- **Infix In-Depth**
|
||||
|
||||
+3
-5
@@ -668,11 +668,9 @@ set:
|
||||
|
||||
For an example of both, see the next section.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **VETH Pair Limitation:** When using VETH pairs with containers, at least
|
||||
> one side of the pair must remain in the host namespace. It is currently
|
||||
> not possible to create VETH pairs where both ends are assigned to different
|
||||
> containers. One end must always be accessible from the host.
|
||||
> [!TIP]
|
||||
> Both ends of a VETH pair may be assigned to containers, connecting two
|
||||
> containers directly without involving the host namespace.
|
||||
|
||||
[^3]: Something which the container bridge network type does behind the
|
||||
scenes with one end of an automatically created VETH pair.
|
||||
|
||||
+13
-5
@@ -122,13 +122,21 @@ recommend using `pipx` to install the necessary tooling:
|
||||
```bash
|
||||
$ sudo apt install pipx
|
||||
$ pipx install mkdocs
|
||||
$ pipx inject mkdocs mkdocs-material pymdown-extensions mkdocs-callouts mike mkdocs-to-pdf
|
||||
$ pipx inject mkdocs mkdocs-material pymdown-extensions mkdocs-callouts mike mkdocs-to-pdf mkdocs-glightbox
|
||||
```
|
||||
|
||||
The last two packages, `mike` and `mkdocs-to-pdf`, are used for online
|
||||
versioning and PDF generation by GitHub Actions, but since they are in
|
||||
the `mkdocs.yml` file, everyone who wants to preview the documentation
|
||||
have to install all the tooling.
|
||||
The `mike` and `mkdocs-to-pdf` packages are used for online versioning
|
||||
and PDF generation by GitHub Actions, but since every plugin is listed
|
||||
in `mkdocs.yml`, anyone who wants to preview the documentation has to
|
||||
install all the tooling.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> MkDocs is also required to **build a WebUI image**. The build bundles
|
||||
> the User's Guide into the image (served on-device at `/guide/`), so
|
||||
> `make` runs `mkdocs build` from `post-build.sh` when the `webui`
|
||||
> package is selected. If MkDocs is missing the build still succeeds,
|
||||
> but the image ships without the on-device guide. Minimal images and
|
||||
> any build without the `webui` package skip this step entirely.
|
||||
|
||||
Preview with:
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 15 KiB |
@@ -297,6 +297,36 @@ image. The device will boot into test mode on first power-on.
|
||||
> unless `startup_override()` is called first (or the marker is removed
|
||||
> and a normal startup config is saved).
|
||||
|
||||
Simulated Wi-Fi
|
||||
---------------
|
||||
|
||||
The virtual topology has no real radios, so Wi-Fi tests run against
|
||||
`mac80211_hwsim` devices and the "air" between guests is faked. A small
|
||||
relay, `wifimedium` (`package/feature-wifi/wifimedium`), takes every frame
|
||||
a radio transmits, forwards it to the other guests, and injects what
|
||||
arrives back into the local radios.
|
||||
|
||||
Each radio (`radio0`, `radio1`, ...) is paired with a carrier NIC of the
|
||||
same name, and that carrier joins the multicast group (a *cell*) the
|
||||
topology wires it to. Two radios hear each other only when their carriers
|
||||
share a cell. Radios on the same cell that tune to different channels are
|
||||
still kept apart, as on real hardware.
|
||||
|
||||
By convention the two general cells carry `radio0` and `radio1` across all
|
||||
DUTs, so most tests put communicating radios on the same index: a station
|
||||
joins an AP on the same `radioN`. The mesh-roaming test, for instance,
|
||||
uses `radio1` for both the gateway APs and the client. That is only a
|
||||
convention, though. A cell can carry any set of radios, including two of
|
||||
the *same* DUT. The band-steering test relies on that: it wires one DUT's
|
||||
`radio2` (2.4GHz) and `radio3` (5GHz) onto a dedicated cell so a single
|
||||
client radio hears the same SSID on both bands.
|
||||
|
||||
How many radios each DUT gets depends on the topology. The number of
|
||||
`radioN` ports wired to a node is passed in via the `opt/wifi` fw_cfg and
|
||||
loaded at boot by the `00-hwsim` script, so a node with no wifi links has
|
||||
no radios at all. The wiring for the standard suite lives in
|
||||
`test/virt/quad`.
|
||||
|
||||
[9PM]: https://github.com/rical/9pm
|
||||
[Qeneth]: https://github.com/wkz/qeneth
|
||||
[TAP]: https://testanything.org/
|
||||
|
||||
+317
-9
@@ -156,6 +156,11 @@ admin@example:/config/hardware/component/radio0/wifi-radio/> <b>leave</b>
|
||||
- `channel-width`: AP channel bandwidth. Supported values are `auto`, `20MHz`,
|
||||
`40MHz`, `80MHz`, and `160MHz`. Wider channels require matching hardware,
|
||||
regulatory approval, and are only available on 5GHz/6GHz where supported.
|
||||
- `legacy-rates`: Allow legacy 802.11b rates (1, 2, 5.5, 11 Mbps) on 2.4GHz
|
||||
(default: disabled). Slow 802.11b clients consume excessive airtime and
|
||||
degrade throughput for all stations, so the rates are normally suppressed.
|
||||
Enable only when old 2.4GHz-only IoT devices need them to associate. No
|
||||
effect on 5GHz/6GHz.
|
||||
- `probe-timeout`: Seconds to wait for PHY detection at boot (default: 0). Set
|
||||
to a non-zero value (e.g., 30) for USB WiFi dongles that are slow to
|
||||
initialize due to firmware loading
|
||||
@@ -165,6 +170,69 @@ admin@example:/config/hardware/component/radio0/wifi-radio/> <b>leave</b>
|
||||
> constraints and hardware capabilities. Channel width can now be set
|
||||
> explicitly for AP mode, or left at `auto` to let the driver choose.
|
||||
|
||||
### Bands and Channels
|
||||
|
||||
Each band strikes a different balance between range and capacity. The
|
||||
`country-code` decides which channels are legal in your location; the
|
||||
lists below are the common allocations, and your regulatory domain may
|
||||
allow fewer.
|
||||
|
||||
**2.4 GHz**
|
||||
|
||||
Channels 1-13 are available in most of the world, 1-11 in the US and
|
||||
Canada, and 14 in Japan (802.11b only). At 20 MHz only three channels
|
||||
avoid overlap: 1, 6, and 11. A 40 MHz channel takes up most of the
|
||||
band, so it is seldom worth using here.
|
||||
|
||||
Drawbacks:
|
||||
|
||||
- This is the most crowded band. It is shared with Bluetooth, Zigbee,
|
||||
cordless phones, microwave ovens, and most of the neighboring Wi-Fi.
|
||||
- Narrow channels and constant contention hold real throughput well
|
||||
below 5 and 6 GHz.
|
||||
- The upside is range: 2.4 GHz reaches further and passes through walls
|
||||
better, which keeps it useful for distant clients and 2.4 GHz-only
|
||||
IoT devices.
|
||||
|
||||
**5 GHz**
|
||||
|
||||
UNII-1 (channels 36-48) and UNII-3 (149-165) need no radar checks.
|
||||
UNII-2 (channels 52-64 and 100-144) shares spectrum with radar and
|
||||
requires DFS. ETSI regions such as the EU do not include UNII-3, so the
|
||||
only non-DFS 5 GHz channels there are 36-48. This band supports 20, 40,
|
||||
80, and 160 MHz, so it is the one to use for wide, fast channels.
|
||||
|
||||
Drawbacks:
|
||||
|
||||
- Shorter range than 2.4 GHz, and a weaker signal through walls and
|
||||
floors.
|
||||
- A DFS channel must be monitored for radar for 60 seconds (up to 10
|
||||
minutes near some weather radars) before the AP may transmit, which
|
||||
delays start-up. If radar appears later, the AP has to leave the
|
||||
channel within 10 seconds and avoid it for 30 minutes, dropping
|
||||
clients during the move.
|
||||
- The widest 80 and 160 MHz channels almost always sit on DFS spectrum,
|
||||
so the same radar rules apply to them.
|
||||
|
||||
**6 GHz**
|
||||
|
||||
The FCC regions open 59 channels (1, 5, 9 ... 233) across 5925-7125 MHz.
|
||||
ETSI regions, including the EU, currently open only the lower part,
|
||||
5945-6425 MHz (channels 1-93), for indoor use. Clients find networks on
|
||||
the 15 Preferred Scanning Channels (5, 21, 37 ... 229) spaced every
|
||||
80 MHz, and `auto` selects channel 37. There is no DFS in 6 GHz, so
|
||||
there is no radar start-up delay.
|
||||
|
||||
Drawbacks:
|
||||
|
||||
- The shortest range and the weakest wall penetration of the three
|
||||
bands.
|
||||
- Only Wi-Fi 6E and newer clients can use it; older phones and IoT
|
||||
devices cannot see the band at all.
|
||||
- AP operation requires WPA3-Personal (SAE) with management frame
|
||||
protection, so WPA2-only and open networks are rejected.
|
||||
- Indoor power limits cap coverage further.
|
||||
|
||||
### WiFi 6 Support
|
||||
|
||||
WiFi 6 (802.11ax) is always enabled in AP mode on all bands, providing improved
|
||||
@@ -236,6 +304,7 @@ in-octets : 148388
|
||||
out-octets : 24555
|
||||
mode : station
|
||||
ssid : MyNetwork
|
||||
bssid : b4:fb:e4:17:b6:a7
|
||||
signal : -45 dBm (good)
|
||||
rx bitrate : 72.2 Mbps
|
||||
tx bitrate : 86.6 Mbps
|
||||
@@ -251,6 +320,11 @@ In the CLI, signal strength is reported as: excellent, good, fair or bad.
|
||||
For precise signal strength values in dBm, use NETCONF or RESTCONF to access
|
||||
the `signal-strength` leaf in the operational datastore.
|
||||
|
||||
When connected, `bssid` shows the MAC address of the access point the
|
||||
station is associated to. It appears only while connected. When several
|
||||
access points share one SSID (a roaming network), the `bssid` is what
|
||||
tells them apart, and it changes as the station roams between them.
|
||||
|
||||
## Passphrase Requirements
|
||||
|
||||
To ensure your connection is secure and compatible with all network
|
||||
@@ -310,6 +384,7 @@ operational status : up
|
||||
physical address : f0:09:0d:36:5f:86
|
||||
mode : station
|
||||
ssid : MyHomeNetwork
|
||||
bssid : b4:fb:e4:17:b6:a7
|
||||
signal : -52 dBm (good)
|
||||
</code></pre>
|
||||
|
||||
@@ -486,20 +561,253 @@ radio settings, and `show interface` to see all active AP interfaces.
|
||||
> AP and Station modes cannot be mixed on the same radio. All virtual interfaces
|
||||
> on a radio must be the same mode (all APs or all Stations).
|
||||
|
||||
### AP as Bridge Port
|
||||
## Fast Roaming Between Access Points
|
||||
|
||||
WiFi AP interfaces can be added to bridges to integrate wireless devices
|
||||
into your LAN:
|
||||
Fast roaming enables seamless client handoff between access points through
|
||||
802.11k/r/v standards. These features can be enabled individually based on
|
||||
your requirements.
|
||||
|
||||
### 802.11r - Fast BSS Transition
|
||||
|
||||
Enable 802.11r for fast handoff (<50ms) between APs with the same SSID:
|
||||
|
||||
```
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11r
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11r mobility-domain 4f57
|
||||
```
|
||||
|
||||
**Requirements:**
|
||||
- All APs in roaming group must have **identical** SSID
|
||||
- All APs must have **identical** passphrase (same keystore secret)
|
||||
- All APs must use the **same mobility-domain** identifier
|
||||
|
||||
**Mobility Domain Options:**
|
||||
- Explicit 4-character hex value (e.g., `4f57`) - default if not specified
|
||||
- `hash` - Automatically derive from SSID using MD5 (OpenWrt-compatible)
|
||||
|
||||
Using `hash` allows multiple APs with the same SSID to automatically share
|
||||
the same mobility domain without manual configuration:
|
||||
|
||||
```
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11r mobility-domain hash
|
||||
```
|
||||
|
||||
The NAS-Identifier (Network Access Server Identifier) is a string that
|
||||
uniquely identifies each AP within the 802.11r mobility domain. APs
|
||||
exchange this identifier during fast BSS transition so they can look up
|
||||
the correct PMK-R1 key for the roaming client. It must be unique per AP
|
||||
BSS and stable across reboots.
|
||||
|
||||
```
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11r nas-identifier auto
|
||||
```
|
||||
|
||||
`auto` derives the identifier as:
|
||||
|
||||
`<interface-name>-<hostname>.<mobility-domain>`
|
||||
|
||||
Or set an explicit string:
|
||||
|
||||
```
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11r nas-identifier ap01.wifi0.4f57
|
||||
```
|
||||
|
||||
### 802.11k - Radio Resource Management
|
||||
|
||||
Enable 802.11k for client neighbor discovery and better roaming decisions:
|
||||
|
||||
```
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11k
|
||||
```
|
||||
|
||||
Enables neighbor reports and beacon reports, allowing clients to discover
|
||||
nearby APs before roaming.
|
||||
|
||||
### 802.11v - BSS Transition Management
|
||||
|
||||
Enable 802.11v for network-assisted roaming:
|
||||
|
||||
```
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11v
|
||||
```
|
||||
|
||||
Allows APs to suggest better APs to clients, improving roaming decisions.
|
||||
|
||||
#### Band Steering (MBO)
|
||||
|
||||
Enabling `dot11v` also turns on MBO (Multi-Band Operation), advertised in
|
||||
beacons and association responses. MBO lets a dual-band client see that
|
||||
the same SSID exists on another band and decide for itself when to move,
|
||||
while 802.11v BSS Transition Management lets the AP suggest a better
|
||||
target.
|
||||
|
||||
On top of the client-cooperative hints, the AP applies active steering:
|
||||
on a 2.4 GHz access-point it suppresses probe responses to clients that
|
||||
were recently seen on the same SSID on the 5/6 GHz band, nudging
|
||||
dual-band clients onto the higher band. MBO is **enabled by default**
|
||||
whenever `dot11v` is enabled:
|
||||
|
||||
```
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11v
|
||||
```
|
||||
|
||||
To turn it off while keeping BSS Transition Management:
|
||||
|
||||
```
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11v band-steering false
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Band steering only matters when the same SSID is offered on two or more
|
||||
> bands (one access-point per radio). On a single-band network there is
|
||||
> no other band to move to, so it has no effect.
|
||||
|
||||
### Opportunistic Key Caching (OKC)
|
||||
|
||||
OKC reduces re-authentication time for roaming clients that do not
|
||||
support 802.11r. The AP caches the PMK from previous associations and
|
||||
shares it with other APs in the same mobility group. It is **enabled by
|
||||
default** and only activates when both AP and client support it:
|
||||
|
||||
```
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming okc false
|
||||
```
|
||||
|
||||
### Recommended Configuration
|
||||
|
||||
For optimal roaming experience, enable all three features:
|
||||
|
||||
```
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11k
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11r
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11v
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11r mobility-domain 4f57
|
||||
```
|
||||
|
||||
Or use `hash` for automatic mobility domain derivation from SSID:
|
||||
|
||||
```
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11k
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11r
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11v
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11r mobility-domain hash
|
||||
```
|
||||
|
||||
Repeat for all APs that should participate in the roaming group.
|
||||
|
||||
> [!NOTE]
|
||||
> Not all client devices support all roaming features. Modern devices typically
|
||||
> support 802.11k/r/v, but older devices may only support basic roaming without
|
||||
> fast transition.
|
||||
|
||||
## 802.11s Mesh Point Mode
|
||||
|
||||
IEEE 802.11s is a wireless mesh networking standard operating at Layer 2.
|
||||
Mesh nodes form peer links directly with each other and route traffic
|
||||
using HWMP (Hybrid Wireless Mesh Protocol), which is built into the
|
||||
Linux mac80211 subsystem. There is no central controller; nodes
|
||||
discover peers and find paths on their own.
|
||||
|
||||
The standard defines two node roles:
|
||||
|
||||
- **Mesh Point (MP)** - a basic mesh node that forwards traffic within
|
||||
the mesh
|
||||
- **Mesh Portal (MPP)** - a mesh node that bridges traffic between the
|
||||
mesh and an external network (e.g., a wired LAN)
|
||||
|
||||
In practice, a node bridging the mesh interface to a LAN acts as a mesh
|
||||
portal.
|
||||
|
||||
> [!NOTE]
|
||||
> Not all WiFi hardware supports 802.11s mesh. The driver must implement
|
||||
> mesh point mode in mac80211. Check your adapter's capabilities with
|
||||
> `iw phy <phy> info` and look for "mesh point" under "Supported interface
|
||||
> modes".
|
||||
|
||||
### 802.11s vs EasyMesh
|
||||
|
||||
| | **802.11s** | **EasyMesh** |
|
||||
|-----------------------------|--------------------------|--------------------------------|
|
||||
| **Standard** | IEEE (open, ratified) | Wi-Fi Alliance (certification) |
|
||||
| **Topology** | Peer-to-peer, any-to-any | Controller-based tree |
|
||||
| **Single point of failure** | None | Controller |
|
||||
| **Multi-hop** | True N-hop | Limited (1-2 hops) |
|
||||
| **Vendor lock-in** | None | Common |
|
||||
| **Linux support** | Kernel-native (mac80211) | Requires proprietary firmware |
|
||||
|
||||
Infix uses 802.11s because it runs entirely in the kernel with no
|
||||
proprietary components.
|
||||
|
||||
### Mesh configuration
|
||||
|
||||
A mesh point requires the radio to have `band`, `channel`, and a valid
|
||||
`country-code` configured. Mesh and AP modes cannot coexist on the same
|
||||
radio.
|
||||
|
||||
**Step 1: Configure the radio**
|
||||
|
||||
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
||||
admin@example:/config/> <b>edit interface br0</b>
|
||||
admin@example:/config/interface/br0/> <b>set type bridge</b>
|
||||
|
||||
admin@example:/config/> <b>edit interface wifi0</b>
|
||||
admin@example:/config/interface/wifi0/> <b>set bridge-port bridge br0</b>
|
||||
admin@example:/config/interface/wifi0/> <b>leave</b>
|
||||
admin@example:/config/> <b>edit hardware component radio1 wifi-radio</b>
|
||||
admin@example:/config/hardware/component/radio1/wifi-radio/> <b>set country-code DE</b>
|
||||
admin@example:/config/hardware/component/radio1/wifi-radio/> <b>set band 5GHz</b>
|
||||
admin@example:/config/hardware/component/radio1/wifi-radio/> <b>set channel 36</b>
|
||||
admin@example:/config/hardware/component/radio1/wifi-radio/> <b>leave</b>
|
||||
</code></pre>
|
||||
|
||||
**Step 2: Create keystore entry for mesh security**
|
||||
|
||||
All mesh links use WPA3-SAE encryption. All nodes in the same mesh
|
||||
network must share the same passphrase:
|
||||
|
||||
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
||||
admin@example:/config/> <b>edit keystore symmetric-key mesh-secret</b>
|
||||
admin@example:/config/keystore/…/mesh-secret/> <b>set key-format passphrase-key-format</b>
|
||||
admin@example:/config/keystore/…/mesh-secret/> <b>change cleartext-symmetric-key</b>
|
||||
Passphrase: ************
|
||||
Retype passphrase: ************
|
||||
admin@example:/config/keystore/…/mesh-secret/> <b>end</b>
|
||||
</code></pre>
|
||||
|
||||
**Step 3: Configure the mesh interface**
|
||||
|
||||
<pre class="cli"><code>admin@example:/config/> <b>edit interface wifi-mesh</b>
|
||||
admin@example:/config/interface/wifi-mesh/> <b>set type wifi</b>
|
||||
admin@example:/config/interface/wifi-mesh/> <b>set wifi radio radio1</b>
|
||||
admin@example:/config/interface/wifi-mesh/> <b>set wifi mesh-point mesh-id my-mesh</b>
|
||||
admin@example:/config/interface/wifi-mesh/> <b>set wifi mesh-point security secret mesh-secret</b>
|
||||
admin@example:/config/interface/wifi-mesh/> <b>leave</b>
|
||||
</code></pre>
|
||||
|
||||
**Mesh parameters:**
|
||||
|
||||
- `mesh-id`: Network identifier, 1-32 characters. All nodes in the mesh
|
||||
must use the same mesh ID
|
||||
- `forwarding`: L2 mesh forwarding (default: true). When enabled, the
|
||||
interface can be added to a bridge as a mesh portal
|
||||
- `security secret`: Keystore reference for the WPA3-SAE passphrase
|
||||
|
||||
### Mesh portal (bridge integration)
|
||||
|
||||
To connect the wireless mesh to a wired LAN, add the mesh interface to
|
||||
a bridge:
|
||||
|
||||
<pre class="cli"><code>admin@example:/config/> <b>edit interface wifi-mesh</b>
|
||||
admin@example:/config/interface/wifi-mesh/> <b>set bridge-port bridge br0</b>
|
||||
admin@example:/config/interface/wifi-mesh/> <b>leave</b>
|
||||
</code></pre>
|
||||
|
||||
### Mesh with roaming APs
|
||||
|
||||
You can combine 802.11s mesh backhaul with roaming-enabled access
|
||||
points. Each node has a mesh interface for backhaul on one radio and
|
||||
AP interfaces for clients on another:
|
||||
|
||||

|
||||
|
||||
With 802.11r/k/v roaming enabled on the APs (same SSID, same
|
||||
passphrase, same mobility domain), clients hand off between nodes while
|
||||
the mesh carries backhaul traffic.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Use `show interface wifi0` to verify signal strength and connection status.
|
||||
|
||||
@@ -4,10 +4,12 @@ oem-dir := $(call qstrip,$(INFIX_OEM_PATH))
|
||||
INFIX_TOPDIR = $(if $(oem-dir),$(oem-dir),$(BR2_EXTERNAL_INFIX_PATH))
|
||||
|
||||
# Unless the user specifies an explicit build id, source it from git.
|
||||
# The build id also becomes the image version, unless an official
|
||||
# release is being built.
|
||||
export INFIX_BUILD_ID ?= $(shell git -C $(INFIX_TOPDIR) describe --dirty --always --tags)
|
||||
export INFIX_VERSION = $(if $(INFIX_RELEASE),$(INFIX_RELEASE),$(INFIX_BUILD_ID))
|
||||
# Exclude the moving 'latest*' tags so the version always resolves to a
|
||||
# real release tag, see issue #1524. The build id is also the version
|
||||
# shown to users; INFIX_RELEASE only labels the release channel and names
|
||||
# artifacts (see INFIX_ARTIFACT below).
|
||||
export INFIX_BUILD_ID ?= $(shell git -C $(INFIX_TOPDIR) describe --dirty --always --tags --exclude 'latest*')
|
||||
export INFIX_VERSION = $(INFIX_BUILD_ID)
|
||||
export INFIX_ARTIFACT = $(call qstrip,$(INFIX_IMAGE_ID)$(if $(INFIX_RELEASE),-$(INFIX_RELEASE)))
|
||||
|
||||
INFIX_CFLAGS:=-Wall -Werror -Wextra -Wno-unused-parameter -Wformat=2 -Wformat-overflow=2 -Winit-self -Wstrict-overflow=4 -Wno-format-truncation -Wno-format-nonliteral
|
||||
|
||||
@@ -42,6 +42,7 @@ source "$BR2_EXTERNAL_INFIX_PATH/package/tetris/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/libyang-cpp/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/sysrepo-cpp/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/rousette/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/webui/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/nghttp2-asio/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/date-cpp/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/rauc-installation-status/Config.in"
|
||||
|
||||
+10
-1
@@ -36,8 +36,10 @@ else
|
||||
CONFD_CONF_OPTS += --disable-gps
|
||||
endif
|
||||
define CONFD_INSTALL_EXTRA
|
||||
for fn in confd.conf resolvconf.conf; do \
|
||||
for fn in confd.conf crond.conf resolvconf.conf; do \
|
||||
cp $(CONFD_PKGDIR)/$$fn $(FINIT_D)/available/; \
|
||||
done
|
||||
for fn in confd.conf resolvconf.conf; do \
|
||||
ln -sf ../available/$$fn $(FINIT_D)/enabled/$$fn; \
|
||||
done
|
||||
cp $(CONFD_PKGDIR)/tmpfiles.conf $(TARGET_DIR)/etc/tmpfiles.d/confd.conf
|
||||
@@ -91,6 +93,12 @@ define CONFD_INSTALL_YANG_MODULES_GPS
|
||||
$(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/gps.inc
|
||||
endef
|
||||
endif
|
||||
ifeq ($(BR2_PACKAGE_WEBUI),y)
|
||||
define CONFD_INSTALL_YANG_MODULES_WEBUI
|
||||
$(COMMON_SYSREPO_ENV) \
|
||||
$(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/web.inc
|
||||
endef
|
||||
endif
|
||||
|
||||
# PER_PACKAGE_DIR
|
||||
# Since the last package in the dependency chain that runs sysrepoctl is confd, we need to
|
||||
@@ -121,6 +129,7 @@ CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES
|
||||
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_CONTAINERS
|
||||
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_WIFI
|
||||
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_GPS
|
||||
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_WEBUI
|
||||
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_IN_ROMFS
|
||||
CONFD_TARGET_FINALIZE_HOOKS += CONFD_CLEANUP
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# Cron daemon for infix-schedule
|
||||
service [2345] crond -f -- Cron daemon
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/bin/sh
|
||||
# Load mac80211_hwsim virtual radios sized to this DUT's topology, then wait
|
||||
# for udev to rename the phys (phyN -> radioN) before returning.
|
||||
#
|
||||
# The radio count comes from a dedicated fw_cfg file (opt/wifi) that qeneth
|
||||
# writes per node from the number of radioN carrier ports the topology wired
|
||||
# to it. A node with no wifi links gets 0 (or no file), so hwsim is not loaded
|
||||
# at all -- no phys, nothing clutters its interface view.
|
||||
#
|
||||
# Runs early in ixinit, before 00-probe, so the system probe records the radios
|
||||
# by their final radioN names. ixinit is past the <pid/syslogd> / udev-settle
|
||||
# barrier, so 60-rename-wifi-phy.rules (phyN -> radioN) and
|
||||
# 70-remove-virtual-wifi-interfaces.rules (drop the stray wlanN) are loaded and
|
||||
# fire on the phy add events. On success it sets the <usr/hwsim> condition so
|
||||
# wifimedium starts only where radios exist. Installed only with
|
||||
# BR2_PACKAGE_FEATURE_WIFI_HWSIM.
|
||||
|
||||
wifi=/sys/firmware/qemu_fw_cfg/by_name/opt/wifi/raw
|
||||
ieee=/sys/class/ieee80211
|
||||
|
||||
# Number of radios to create, from the topology (via fw_cfg).
|
||||
radios=0
|
||||
[ -r "$wifi" ] && radios=$(cat "$wifi" 2>/dev/null)
|
||||
|
||||
# No radios wanted -> don't load hwsim, keep the view wifi-free.
|
||||
[ "$radios" -gt 0 ] 2>/dev/null || exit 0
|
||||
|
||||
modprobe mac80211_hwsim radios="$radios" || exit 0
|
||||
|
||||
# Wait (bounded ~10s) for the phyN -> radioN rename to finish: the class has
|
||||
# entries and none are still named phyN. The rename is a udev RUN, so it lags
|
||||
# modprobe's return. On timeout the radios never settled -- fail (and log) so
|
||||
# it is visible why 00-probe may record stale phyN names.
|
||||
TIMEOUT=100
|
||||
i=0
|
||||
while [ "$i" -lt "$TIMEOUT" ]; do
|
||||
names=$(ls "$ieee" 2>/dev/null)
|
||||
if [ -n "$names" ] && ! echo "$names" | grep -q '^phy'; then
|
||||
initctl -bq cond set hwsim
|
||||
exit 0
|
||||
fi
|
||||
i=$((i + 1))
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
logger -p user.err -t 00-hwsim "timed out waiting for hwsim phys to be renamed radioN"
|
||||
exit 1
|
||||
@@ -5,15 +5,31 @@ config BR2_PACKAGE_FEATURE_WIFI
|
||||
select BR2_PACKAGE_WPA_SUPPLICANT_DEBUG_SYSLOG
|
||||
select BR2_PACKAGE_WPA_SUPPLICANT_AUTOSCAN
|
||||
select BR2_PACKAGE_WPA_SUPPLICANT_CLI
|
||||
select BR2_PACKAGE_WPA_SUPPLICANT_AP_SUPPORT
|
||||
select BR2_PACKAGE_WPA_SUPPLICANT_MESH_NETWORKING
|
||||
select BR2_PACKAGE_WIRELESS_REGDB
|
||||
select BR2_PACKAGE_HOSTAPD
|
||||
select BR2_PACKAGE_HOSTAPD_DRIVER_NL80211
|
||||
select BR2_PACKAGE_HOSTAPD_WPA3
|
||||
select BR2_PACKAGE_HOSTAPD_WPS
|
||||
select BR2_PACKAGE_HOSTAPD_WNM
|
||||
select BR2_PACKAGE_HOSTAPD_MBO
|
||||
select BR2_PACKAGE_IW
|
||||
help
|
||||
Enables WiFi in Infix. Enables all requried applications.
|
||||
|
||||
config BR2_PACKAGE_FEATURE_WIFI_HWSIM
|
||||
bool "Virtual Wi-Fi radios (mac80211_hwsim)"
|
||||
depends on BR2_PACKAGE_FEATURE_WIFI
|
||||
help
|
||||
Build the mac80211_hwsim virtual radio driver (as a module) for the
|
||||
QEMU test target, to exercise AP, station, and mesh modes without
|
||||
physical WiFi adapters. The module is autoloaded at boot (see the
|
||||
x86_64 /etc/modules-load.d) and frames are bridged between guests by
|
||||
the wifimedium relay.
|
||||
|
||||
Leave disabled on real hardware; it only provides simulated radios.
|
||||
|
||||
config BR2_PACKAGE_FEATURE_WIFI_MEDIATEK
|
||||
bool "Mediatek WiFi Devices"
|
||||
depends on BR2_PACKAGE_FEATURE_WIFI
|
||||
|
||||
@@ -14,6 +14,15 @@ define FEATURE_WIFI_LINUX_CONFIG_FIXUPS
|
||||
$(call KCONFIG_SET_OPT,CONFIG_CFG80211,m)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_MAC80211_MESH)
|
||||
|
||||
# Virtual radio for automated testing (mac80211_hwsim), built as a
|
||||
# module and gated behind BR2_PACKAGE_FEATURE_WIFI_HWSIM so it is not
|
||||
# built on real boards. When selected, the module is loaded early in
|
||||
# ixinit by 00-hwsim -- sized to the DUT's topology, so radio-less nodes
|
||||
# load nothing -- and bridged between QEMU guests by the wifimedium relay
|
||||
# (both installed by FEATURE_WIFI_INSTALL_HWSIM below).
|
||||
$(if $(BR2_PACKAGE_FEATURE_WIFI_HWSIM),
|
||||
$(call KCONFIG_SET_OPT,CONFIG_MAC80211_HWSIM,m))
|
||||
|
||||
$(if $(filter y,$(BR2_PACKAGE_FEATURE_WIFI_MEDIATEK)),
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_MT7601U)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_MT76x0U)
|
||||
@@ -69,12 +78,27 @@ define FEATURE_WIFI_LINUX_CONFIG_FIXUPS
|
||||
endef
|
||||
|
||||
define FEATURE_WIFI_INSTALL_IN_ROMFS
|
||||
mkdir -p $(TARGET_DIR)/etc/modprobe.d $(TARGET_DIR)/etc/udev/rules.d
|
||||
mkdir -p $(TARGET_DIR)/etc/modprobe.d $(TARGET_DIR)/etc/udev/rules.d $(TARGET_DIR)/usr/libexec
|
||||
cp $(FEATURE_WIFI_PKGDIR)/mt7915e.conf $(TARGET_DIR)/etc/modprobe.d/mt7915e.conf
|
||||
cp $(FEATURE_WIFI_PKGDIR)/60-rename-wifi-phy.rules $(TARGET_DIR)/etc/udev/rules.d/60-rename-wifi-phy.rules
|
||||
cp $(FEATURE_WIFI_PKGDIR)/70-remove-virtual-wifi-interfaces.rules $(TARGET_DIR)/etc/udev/rules.d/70-remove-virtual-wifi-interfaces.rules
|
||||
endef
|
||||
FEATURE_WIFI_POST_INSTALL_TARGET_HOOKS += FEATURE_WIFI_INSTALL_IN_ROMFS
|
||||
|
||||
# Virtual-radio (mac80211_hwsim) runtime, installed only when the option is
|
||||
# selected: the 00-hwsim ixinit script loads the module sized to the opt/wifi
|
||||
# fw_cfg qeneth writes per node (no radios -> not loaded), and the wifimedium
|
||||
# relay + its Finit service bridge hwsim frames between QEMU guests so they can
|
||||
# associate "wirelessly".
|
||||
ifeq ($(BR2_PACKAGE_FEATURE_WIFI_HWSIM),y)
|
||||
define FEATURE_WIFI_INSTALL_HWSIM
|
||||
$(INSTALL) -D -m 0755 $(FEATURE_WIFI_PKGDIR)/00-hwsim $(TARGET_DIR)/usr/libexec/infix/init.d/00-hwsim
|
||||
$(INSTALL) -D -m 0755 $(FEATURE_WIFI_PKGDIR)/wifimedium $(TARGET_DIR)/usr/libexec/infix/wifimedium
|
||||
$(INSTALL) -D -m 0644 $(FEATURE_WIFI_PKGDIR)/wifimedium.conf $(TARGET_DIR)/etc/finit.d/available/wifimedium.conf
|
||||
ln -sf ../available/wifimedium.conf $(TARGET_DIR)/etc/finit.d/enabled/wifimedium.conf
|
||||
endef
|
||||
FEATURE_WIFI_POST_INSTALL_TARGET_HOOKS += FEATURE_WIFI_INSTALL_HWSIM
|
||||
endif
|
||||
|
||||
|
||||
$(eval $(generic-package))
|
||||
|
||||
Executable
+401
@@ -0,0 +1,401 @@
|
||||
#!/usr/bin/env python3
|
||||
"""wifimedium - a virtual WiFi medium for mac80211_hwsim, extended over Ethernet.
|
||||
|
||||
mac80211_hwsim simulates 802.11 radios in software. By default all radios in
|
||||
one kernel share an in-kernel medium; radios in *different* kernels (i.e.
|
||||
different QEMU guests) cannot hear each other. The moment a userspace process
|
||||
registers on the hwsim generic-netlink family, the kernel stops delivering
|
||||
frames itself and hands every transmitted frame to that process, which becomes
|
||||
responsible for delivery.
|
||||
|
||||
This daemon is that process. On each DUT it:
|
||||
|
||||
* registers on the "mac80211_hwsim" genl family and receives every frame the
|
||||
local radios transmit (HWSIM_CMD_FRAME),
|
||||
* acknowledges each transmit back to the kernel (HWSIM_CMD_TX_INFO_FRAME) so
|
||||
mac80211's TX path completes, and
|
||||
* relays the frame per radio: each radio (phy radioN) is paired by name with a
|
||||
carrier NIC (netdev radioN) that joins one multicast "cell" -- a QEMU socket
|
||||
multicast group shared by every DUT's radioN (see test/virt/quad and
|
||||
test/qeneth). A radio's frame is sent only onto its own carrier, and a
|
||||
carrier's frames are injected only into its own radio
|
||||
(HWSIM_CMD_FRAME with HWSIM_ATTR_ADDR_RECEIVER).
|
||||
|
||||
Each cell is a shared medium: every DUT's radioN is "in range" of every other
|
||||
radioN. Radios on different indices are on different cells, so two radios
|
||||
communicate only if they share an index -- the convention the test topologies
|
||||
follow (a station uses the same radio index as the AP it joins). Channel
|
||||
filtering in mac80211 still separates radios that share a cell but tune to
|
||||
different frequencies.
|
||||
|
||||
This is intended for the QEMU/qeneth test topology only. On real hardware the
|
||||
radios use the air, there are no radioN carrier netdevs, and this daemon idles.
|
||||
See doc/wifi.md.
|
||||
"""
|
||||
import os
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
import subprocess
|
||||
import sys
|
||||
import select
|
||||
|
||||
# ---- generic netlink / hwsim ABI (drivers/net/wireless/virtual/mac80211_hwsim.h)
|
||||
NETLINK_GENERIC = 16
|
||||
GENL_ID_CTRL = 16
|
||||
CTRL_CMD_GETFAMILY = 3
|
||||
CTRL_ATTR_FAMILY_ID = 1
|
||||
CTRL_ATTR_FAMILY_NAME = 2
|
||||
CTRL_ATTR_MCAST_GROUPS = 7
|
||||
CTRL_ATTR_MCAST_GRP_NAME = 1
|
||||
CTRL_ATTR_MCAST_GRP_ID = 2
|
||||
|
||||
SOL_NETLINK = 270
|
||||
NETLINK_ADD_MEMBERSHIP = 1
|
||||
|
||||
HWSIM_CMD_REGISTER = 1
|
||||
HWSIM_CMD_FRAME = 2
|
||||
HWSIM_CMD_TX_INFO_FRAME = 3
|
||||
|
||||
HWSIM_ATTR_ADDR_RECEIVER = 1
|
||||
HWSIM_ATTR_ADDR_TRANSMITTER = 2
|
||||
HWSIM_ATTR_FRAME = 3
|
||||
HWSIM_ATTR_FLAGS = 4
|
||||
HWSIM_ATTR_RX_RATE = 5
|
||||
HWSIM_ATTR_SIGNAL = 6
|
||||
HWSIM_ATTR_TX_INFO = 7
|
||||
HWSIM_ATTR_COOKIE = 8
|
||||
HWSIM_ATTR_FREQ = 19
|
||||
HWSIM_ATTR_TX_INFO_FLAGS = 21
|
||||
|
||||
HWSIM_TX_STAT_ACK = 1 << 2
|
||||
|
||||
# Signal/rate reported for injected (received) frames. A strong, fixed signal
|
||||
# keeps mac80211 happy; the exact value is not significant for a virtual cell.
|
||||
RX_SIGNAL = -30
|
||||
RX_RATE = 0
|
||||
|
||||
# The medium is carried as raw Ethernet frames with a custom EtherType on the
|
||||
# configured interface -- no IP addressing needed (the carrier link has none).
|
||||
# A broadcast destination means every DUT on the segment is "in range".
|
||||
ETH_P_WIFIMEDIUM = 0x88B5 # local-experimental EtherType
|
||||
ETH_BROADCAST = b"\xff\xff\xff\xff\xff\xff"
|
||||
ETYPE = struct.pack("!H", ETH_P_WIFIMEDIUM)
|
||||
SOL_PACKET = 263
|
||||
PACKET_IGNORE_OUTGOING = 23 # don't deliver our own TX back to the socket
|
||||
|
||||
NLMSG_HDR = struct.Struct("=IHHII") # len, type, flags, seq, pid
|
||||
GENL_HDR = struct.Struct("=BBH") # cmd, version, reserved
|
||||
|
||||
|
||||
DEBUG = bool(os.environ.get("WIFIMEDIUM_DEBUG"))
|
||||
|
||||
|
||||
def log(msg):
|
||||
sys.stderr.write(f"wifimedium: {msg}\n")
|
||||
sys.stderr.flush()
|
||||
|
||||
|
||||
def dbg(msg):
|
||||
if DEBUG:
|
||||
log(msg)
|
||||
|
||||
|
||||
def nla_align(n):
|
||||
return (n + 3) & ~3
|
||||
|
||||
|
||||
def put_attr(atype, payload):
|
||||
hdr = struct.pack("=HH", len(payload) + 4, atype)
|
||||
return hdr + payload + b"\0" * (nla_align(len(payload) + 4) - (len(payload) + 4))
|
||||
|
||||
|
||||
def parse_attrs(buf):
|
||||
attrs = {}
|
||||
off = 0
|
||||
while off + 4 <= len(buf):
|
||||
alen, atype = struct.unpack_from("=HH", buf, off)
|
||||
if alen < 4:
|
||||
break
|
||||
attrs[atype & 0x7fff] = buf[off + 4:off + alen]
|
||||
off += nla_align(alen)
|
||||
return attrs
|
||||
|
||||
|
||||
class Netlink:
|
||||
"""Minimal generic-netlink client for the hwsim family."""
|
||||
|
||||
def __init__(self):
|
||||
self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, NETLINK_GENERIC)
|
||||
self.sock.bind((0, 0))
|
||||
self.seq = 0
|
||||
# NB: the genl family is "MAC80211_HWSIM" (uppercase); the lowercase
|
||||
# "mac80211_hwsim" is the platform driver name, not the family.
|
||||
self.family_id, self.mcast_id = self._resolve("MAC80211_HWSIM", "config")
|
||||
|
||||
def fileno(self):
|
||||
return self.sock.fileno()
|
||||
|
||||
def _send(self, family_id, cmd, attrs=b"", flags=0):
|
||||
self.seq += 1
|
||||
payload = GENL_HDR.pack(cmd, 1, 0) + attrs
|
||||
msg = NLMSG_HDR.pack(NLMSG_HDR.size + len(payload), family_id,
|
||||
flags | 1, self.seq, 0) + payload # NLM_F_REQUEST
|
||||
self.sock.send(msg)
|
||||
|
||||
def _resolve(self, name, grpname):
|
||||
self._send(GENL_ID_CTRL, CTRL_CMD_GETFAMILY,
|
||||
put_attr(CTRL_ATTR_FAMILY_NAME, name.encode() + b"\0"))
|
||||
data = self.sock.recv(65536)
|
||||
_, mtype, _, _, _ = NLMSG_HDR.unpack_from(data, 0)
|
||||
if mtype == 2: # NLMSG_ERROR (also used for ACKs; err<0 is a failure)
|
||||
err = struct.unpack_from("=i", data, NLMSG_HDR.size)[0]
|
||||
raise RuntimeError(f"genl GETFAMILY({name}) failed: "
|
||||
f"{os.strerror(-err) if err else 'empty reply'}")
|
||||
attrs = parse_attrs(data[NLMSG_HDR.size + GENL_HDR.size:])
|
||||
family_id = struct.unpack("=H", attrs[CTRL_ATTR_FAMILY_ID][:2])[0]
|
||||
|
||||
mcast_id = None
|
||||
for _, grp in _parse_nested(attrs.get(CTRL_ATTR_MCAST_GROUPS, b"")):
|
||||
gattrs = parse_attrs(grp)
|
||||
gname = gattrs.get(CTRL_ATTR_MCAST_GRP_NAME, b"").rstrip(b"\0").decode()
|
||||
if gname == grpname:
|
||||
mcast_id = struct.unpack("=I", gattrs[CTRL_ATTR_MCAST_GRP_ID])[0]
|
||||
if mcast_id is None:
|
||||
raise RuntimeError(f"hwsim multicast group '{grpname}' not found")
|
||||
return family_id, mcast_id
|
||||
|
||||
def register(self):
|
||||
self.sock.setsockopt(SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, self.mcast_id)
|
||||
self._send(self.family_id, HWSIM_CMD_REGISTER)
|
||||
|
||||
def recv(self):
|
||||
return self.sock.recv(65536)
|
||||
|
||||
def inject(self, receiver, frame, freq):
|
||||
attrs = put_attr(HWSIM_ATTR_ADDR_RECEIVER, receiver)
|
||||
attrs += put_attr(HWSIM_ATTR_FRAME, frame)
|
||||
attrs += put_attr(HWSIM_ATTR_RX_RATE, struct.pack("=I", RX_RATE))
|
||||
attrs += put_attr(HWSIM_ATTR_SIGNAL, struct.pack("=i", RX_SIGNAL))
|
||||
if freq:
|
||||
attrs += put_attr(HWSIM_ATTR_FREQ, struct.pack("=I", freq))
|
||||
self._send(self.family_id, HWSIM_CMD_FRAME, attrs)
|
||||
|
||||
def tx_ack(self, transmitter, flags, tx_info, tx_info_flags, cookie):
|
||||
attrs = put_attr(HWSIM_ATTR_ADDR_TRANSMITTER, transmitter)
|
||||
attrs += put_attr(HWSIM_ATTR_FLAGS, struct.pack("=I", flags | HWSIM_TX_STAT_ACK))
|
||||
attrs += put_attr(HWSIM_ATTR_SIGNAL, struct.pack("=i", RX_SIGNAL))
|
||||
if tx_info:
|
||||
attrs += put_attr(HWSIM_ATTR_TX_INFO, tx_info)
|
||||
if tx_info_flags:
|
||||
attrs += put_attr(HWSIM_ATTR_TX_INFO_FLAGS, tx_info_flags)
|
||||
if cookie:
|
||||
attrs += put_attr(HWSIM_ATTR_COOKIE, cookie)
|
||||
self._send(self.family_id, HWSIM_CMD_TX_INFO_FRAME, attrs)
|
||||
|
||||
|
||||
def _parse_nested(buf):
|
||||
off = 0
|
||||
while off + 4 <= len(buf):
|
||||
alen, atype = struct.unpack_from("=HH", buf, off)
|
||||
if alen < 4:
|
||||
break
|
||||
yield atype & 0x7fff, buf[off + 4:off + alen]
|
||||
off += nla_align(alen)
|
||||
|
||||
|
||||
def radio_addr1(phy):
|
||||
"""The address mac80211_hwsim identifies a radio by on the medium.
|
||||
|
||||
A hwsim radio has three addresses (kernel new_radio()). sysfs 'macaddress'
|
||||
is addresses[0]; the address the kernel stamps as HWSIM_ATTR_ADDR_TRANSMITTER
|
||||
and matches HWSIM_ATTR_ADDR_RECEIVER against (its rhashtable key) is
|
||||
addresses[1] -- addresses[0] with bit 0x40 set in the first octet, for the
|
||||
default no-perm_addr radios we use. So both the transmitter we observe and
|
||||
the receiver we must inject to are this 0x40 variant, not the sysfs value.
|
||||
(Note custom-phys-address changes the vif/netdev MAC, not these.)
|
||||
"""
|
||||
with open(f"/sys/class/ieee80211/{phy}/macaddress") as fh:
|
||||
addr = bytearray.fromhex(fh.read().strip().replace(":", ""))
|
||||
addr[0] |= 0x40
|
||||
return bytes(addr)
|
||||
|
||||
|
||||
def open_medium(ifname):
|
||||
"""Raw-Ethernet socket on the medium interface, or (None, None).
|
||||
|
||||
Returns (sock, mac); mac is the interface's own (unique) address, used as
|
||||
the Ethernet source and to drop frames we transmitted ourselves -- the
|
||||
multicast cell loops our own frames back to us."""
|
||||
if not ifname:
|
||||
return None, None
|
||||
# The carrier NIC is unconfigured (it is not a real DUT port), so Infix
|
||||
# leaves it administratively down -- a raw packet socket on a down link
|
||||
# carries no frames. Bring it up; wifimedium owns the medium, like hwsim0.
|
||||
ifup(ifname)
|
||||
sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
|
||||
socket.htons(ETH_P_WIFIMEDIUM))
|
||||
sock.bind((ifname, ETH_P_WIFIMEDIUM))
|
||||
# Don't receive our own transmitted frames (avoids a self-feedback path
|
||||
# and any reliance on the source MAC, which can collide between guests).
|
||||
try:
|
||||
sock.setsockopt(SOL_PACKET, PACKET_IGNORE_OUTGOING, 1)
|
||||
except OSError:
|
||||
pass # pre-4.20 kernels; the length check below still applies
|
||||
with open(f"/sys/class/net/{ifname}/address") as fh:
|
||||
mac = bytes.fromhex(fh.read().strip().replace(":", ""))
|
||||
return sock, mac
|
||||
|
||||
|
||||
# Wire format on the medium: 6-byte transmitter MAC, 4-byte LE freq, then the
|
||||
# raw 802.11 frame.
|
||||
WIRE = struct.Struct("=6sI")
|
||||
|
||||
|
||||
def wait_radios_renamed(timeout=20):
|
||||
"""Wait until the hwsim phys have been renamed phyN -> radioN.
|
||||
|
||||
60-rename-wifi-phy.rules renames each phy *asynchronously* (a udev RUN),
|
||||
and the carrier NICs are already named radioN (mactab/nameif, early boot).
|
||||
We pair phy and carrier by name, so discovering while a phy is still 'phyN'
|
||||
would miss it -- and we discover only once. This raced our start (gated
|
||||
merely on the module load) and left some boots relaying nothing until a
|
||||
restart. Block until every phy is renamed (no 'phyN' left) and the set has
|
||||
settled, so discovery sees radioN phys that match the radioN carriers.
|
||||
"""
|
||||
base = "/sys/class/ieee80211"
|
||||
prev, stable = None, 0
|
||||
for _ in range(timeout * 4):
|
||||
phys = sorted(os.listdir(base)) if os.path.isdir(base) else []
|
||||
if phys and not any(p.startswith("phy") for p in phys):
|
||||
if phys == prev:
|
||||
stable += 1
|
||||
if stable >= 2: # unchanged for ~0.5s
|
||||
return
|
||||
else:
|
||||
stable = 0
|
||||
prev = phys
|
||||
time.sleep(0.25)
|
||||
|
||||
|
||||
def discover_radios():
|
||||
"""Pair each local hwsim radio with its multicast carrier interface.
|
||||
|
||||
A radio's phy is named radioN (60-rename-wifi-phy.rules) under
|
||||
/sys/class/ieee80211/. In the qeneth test topology each radio also has a
|
||||
carrier NIC of the SAME name under /sys/class/net/ -- the topology names the
|
||||
port radioN, so the mactab/nameif rename the carrier to match -- joined to
|
||||
one multicast cell shared by every DUT's radioN. Pairing them by name gives
|
||||
a per-radio relay: a radio's frames go onto its own carrier (its cell) and a
|
||||
carrier's frames are injected only into its own radio.
|
||||
|
||||
Returns a list of {name, addr1, sock, mac}. A phy with no matching carrier
|
||||
netdev (e.g. real hardware) is skipped, so the daemon simply idles there.
|
||||
"""
|
||||
wait_radios_renamed()
|
||||
radios = []
|
||||
base = "/sys/class/ieee80211"
|
||||
if not os.path.isdir(base):
|
||||
return radios
|
||||
for phy in sorted(os.listdir(base)):
|
||||
if not os.path.isdir(f"/sys/class/net/{phy}"):
|
||||
continue # no carrier of this name -> not a relayed radio
|
||||
sock, mac = open_medium(phy)
|
||||
if sock:
|
||||
radios.append({"name": phy, "addr1": radio_addr1(phy),
|
||||
"sock": sock, "mac": mac})
|
||||
return radios
|
||||
|
||||
|
||||
def ifup(ifname):
|
||||
"""Bring an interface up (best effort)."""
|
||||
subprocess.run(["ip", "link", "set", ifname, "up"], check=False)
|
||||
|
||||
|
||||
def main():
|
||||
# The hwsim monitor interface is down after boot; bring it up so the
|
||||
# simulated medium is observable (tcpdump -i hwsim0) and active.
|
||||
ifup("hwsim0")
|
||||
|
||||
# Discover carriers first. With none -- e.g. 'make run', which creates
|
||||
# radios but no inter-guest medium -- registering on the hwsim family would
|
||||
# make the kernel hand us every frame with nowhere to relay it, breaking
|
||||
# delivery between this guest's own radios. So leave the medium to the
|
||||
# kernel and idle instead of registering.
|
||||
radios = discover_radios()
|
||||
if not radios:
|
||||
log("no radio carriers found; leaving local hwsim medium to the kernel")
|
||||
while True:
|
||||
time.sleep(3600)
|
||||
|
||||
nl = Netlink()
|
||||
nl.register()
|
||||
log(f"registered on hwsim family {nl.family_id}; "
|
||||
f"radios={' '.join(r['name'] for r in radios)}")
|
||||
|
||||
# TX routing: a frame's transmitter (addresses[1]) -> that radio's carrier.
|
||||
by_addr1 = {r["addr1"]: r for r in radios}
|
||||
dbg(f"netlink portid={nl.sock.getsockname()[0]}; "
|
||||
+ "; ".join(f"{r['name']} addr1={r['addr1'].hex()} "
|
||||
f"carrier={r['mac'].hex()}" for r in radios))
|
||||
|
||||
socks = [nl] + [r["sock"] for r in radios]
|
||||
while True:
|
||||
ready, _, _ = select.select(socks, [], [])
|
||||
|
||||
if nl in ready:
|
||||
data = nl.recv()
|
||||
off = 0
|
||||
while off + NLMSG_HDR.size <= len(data):
|
||||
mlen, mtype, _, _, _ = NLMSG_HDR.unpack_from(data, off)
|
||||
if mlen < NLMSG_HDR.size:
|
||||
break
|
||||
if mtype == nl.family_id and \
|
||||
data[off + NLMSG_HDR.size] == HWSIM_CMD_FRAME:
|
||||
a = parse_attrs(data[off + NLMSG_HDR.size + GENL_HDR.size:
|
||||
off + mlen])
|
||||
tx = a.get(HWSIM_ATTR_ADDR_TRANSMITTER)
|
||||
frame = a.get(HWSIM_ATTR_FRAME)
|
||||
if tx and frame:
|
||||
freq = struct.unpack("=I", a[HWSIM_ATTR_FREQ])[0] \
|
||||
if HWSIM_ATTR_FREQ in a else 0
|
||||
flags = struct.unpack("=I", a[HWSIM_ATTR_FLAGS])[0] \
|
||||
if HWSIM_ATTR_FLAGS in a else 0
|
||||
# Complete the kernel TX path regardless of delivery.
|
||||
nl.tx_ack(tx, flags, a.get(HWSIM_ATTR_TX_INFO),
|
||||
a.get(HWSIM_ATTR_TX_INFO_FLAGS),
|
||||
a.get(HWSIM_ATTR_COOKIE))
|
||||
# Send only onto the transmitting radio's own carrier.
|
||||
r = by_addr1.get(tx)
|
||||
if r:
|
||||
r["sock"].send(ETH_BROADCAST + r["mac"] + ETYPE +
|
||||
WIRE.pack(tx, freq) + frame)
|
||||
dbg(f"tx {r['name']} freq={freq} len={len(frame)}")
|
||||
else:
|
||||
dbg(f"tx from unknown radio {tx.hex()} -- dropped")
|
||||
off += nla_align(mlen)
|
||||
|
||||
for r in radios:
|
||||
if r["sock"] not in ready:
|
||||
continue
|
||||
pkt = r["sock"].recv(65536)
|
||||
if len(pkt) < 14 + WIRE.size:
|
||||
continue
|
||||
# The cell multicasts our own frames back to us; PACKET_IGNORE_-
|
||||
# OUTGOING does not catch that looped copy, so drop anything whose
|
||||
# Ethernet source is our own carrier (carrier MACs are unique per
|
||||
# DUT; transmitter addrs are not, so we must key on the carrier).
|
||||
if pkt[6:12] == r["mac"]:
|
||||
continue
|
||||
tx, freq = WIRE.unpack_from(pkt, 14)
|
||||
frame = pkt[14 + WIRE.size:]
|
||||
# Inject into THIS radio only -- its carrier is its cell.
|
||||
nl.inject(r["addr1"], frame, freq)
|
||||
dbg(f"rx {r['name']} tx={tx.hex()} freq={freq} len={len(frame)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
@@ -0,0 +1,14 @@
|
||||
# Virtual WiFi medium for mac80211_hwsim (QEMU test target).
|
||||
#
|
||||
# Installed only when BR2_PACKAGE_FEATURE_WIFI_HWSIM is set. Bridges hwsim
|
||||
# frames between the local radios and -- when a medium interface is configured
|
||||
# (WIFIMEDIUM_IFACE env or 'wifimedium.iface=' on the kernel cmdline) -- to
|
||||
# peer DUTs over that Ethernet segment, so two QEMU guests can associate
|
||||
# "wirelessly". hwsim is loaded by the 00-hwsim ixinit script, which sets the
|
||||
# <usr/hwsim> condition once radios exist and are renamed -- so this service
|
||||
# starts only on nodes that actually have radios (registering on the hwsim genl
|
||||
# family would fail otherwise). On real hardware neither is shipped and the
|
||||
# radios use the air.
|
||||
service env:-/etc/default/wifimedium name:wifimedium \
|
||||
[2345] <usr/hwsim> \
|
||||
/usr/libexec/infix/wifimedium -- Virtual WiFi medium (hwsim)
|
||||
@@ -14,6 +14,8 @@ define LANDING_INSTALL_TARGET_CMDS
|
||||
mkdir -p $(TARGET_DIR)/usr/html/
|
||||
cp $(@D)/*.html $(TARGET_DIR)/usr/html/
|
||||
cp $(@D)/*.png $(TARGET_DIR)/usr/html/
|
||||
$(INSTALL) -D -m 0644 $(LANDING_PKGDIR)/default.conf \
|
||||
$(TARGET_DIR)/etc/nginx/available/default.conf
|
||||
endef
|
||||
|
||||
$(eval $(generic-package))
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Loading…</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="refresh" content="3">
|
||||
<style>
|
||||
html { color-scheme: light dark; font-family: Tahoma, Verdana, Arial, sans-serif; }
|
||||
body { max-width: 32em; margin: 4em auto; text-align: center; }
|
||||
h1 { font-weight: 500; margin-bottom: 0.5em; }
|
||||
p { color: #888; margin: 0.5em 0; }
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
margin-top: 1em;
|
||||
border: 3px solid currentColor;
|
||||
border-top-color: transparent;
|
||||
border-radius: 50%;
|
||||
animation: rot 1s linear infinite;
|
||||
}
|
||||
@keyframes rot { to { transform: rotate(360deg); } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Loading…</h1>
|
||||
<p>The device is finishing its startup. This page refreshes automatically.</p>
|
||||
<p><span class="spinner" aria-hidden="true"></span></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,9 @@
|
||||
config BR2_PACKAGE_WEBUI
|
||||
bool "webui"
|
||||
depends on BR2_PACKAGE_HOST_GO_TARGET_ARCH_SUPPORTS
|
||||
depends on BR2_PACKAGE_ROUSETTE
|
||||
depends on !BR2_PACKAGE_LANDING
|
||||
help
|
||||
Web management interface for Infix, a Go+HTMX application
|
||||
that provides browser-based configuration and monitoring
|
||||
via RESTCONF.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Throttle POST /login so a brute-force attempt can't pin the box on
|
||||
# the bcrypt-shaped credential check inside rousette/PAM. GET is left
|
||||
# unmetered: page loads after a 401-driven HX-Redirect shouldn't eat
|
||||
# into the budget.
|
||||
#
|
||||
# Zone is 32k (~500 IPs) — the minimum nginx accepts via shared-memory
|
||||
# allocation. With the cache full an attacker rotating source addresses
|
||||
# still tops out at ~2500 attempts/min total, all serialised through
|
||||
# bcrypt one at a time.
|
||||
#
|
||||
# 429 instead of the default 503 keeps the rate-limit response out of
|
||||
# the /50x.html error_page rewrite below (which auto-refreshes — would
|
||||
# loop a throttled client straight back to /login).
|
||||
map $request_method $webui_login_key {
|
||||
POST $binary_remote_addr;
|
||||
default "";
|
||||
}
|
||||
limit_req_zone $webui_login_key zone=webui_login:32k rate=5r/m;
|
||||
limit_req_status 429;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name _;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name _;
|
||||
include ssl.conf;
|
||||
|
||||
# 404 also points at /50x.html: the page is a "Loading…" screen
|
||||
# with a meta-refresh, so the early-boot window where the Go
|
||||
# backend isn't up yet (and any other transient 404 / 5xx) self-
|
||||
# recovers as soon as upstream comes back.
|
||||
error_page 404 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root html;
|
||||
}
|
||||
|
||||
include /etc/nginx/app/*.conf;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Shared proxy-pass shape for the webui upstream. Nested locations that
|
||||
# declare their own proxy_* directives don't inherit from the outer block,
|
||||
# so each location includes this file rather than relying on inheritance.
|
||||
|
||||
proxy_pass http://127.0.0.1:10000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_redirect off;
|
||||
@@ -0,0 +1,72 @@
|
||||
# Must be at server scope, not on an inner location: client_max_body_size
|
||||
# does not inherit into a nested location that declares its own proxy_pass,
|
||||
# and the http-level 1m default would silently apply and reject bundle
|
||||
# uploads with 413.
|
||||
client_max_body_size 256m;
|
||||
|
||||
location / {
|
||||
include /etc/nginx/webui-proxy.conf;
|
||||
}
|
||||
|
||||
# burst=3 nodelay: three POSTs land back-to-back; the next needs a
|
||||
# fresh token (~12 s at 5r/m) or 429s.
|
||||
location = /login {
|
||||
limit_req zone=webui_login burst=3 nodelay;
|
||||
include /etc/nginx/webui-proxy.conf;
|
||||
}
|
||||
|
||||
# Keep proxy_request_buffering on (the default). Streaming the body races
|
||||
# the Go handler's response close, producing RST instead of FIN at
|
||||
# Content-Length and a client-visible 502. nginx spools the body to
|
||||
# /var/cache/nginx/client-body during the upload instead.
|
||||
location = /software/upload {
|
||||
proxy_read_timeout 600s;
|
||||
include /etc/nginx/webui-proxy.conf;
|
||||
}
|
||||
|
||||
# SSE progress stream: RAUC's Progress D-Bus property doesn't change while
|
||||
# it's writing a slot, so the upstream goes minutes without a frame. The
|
||||
# default 60 s proxy_read_timeout closes the stream and the browser sees
|
||||
# a transient error. Raise to cover a slow image write end-to-end.
|
||||
location = /software/progress {
|
||||
proxy_read_timeout 1800s;
|
||||
proxy_buffering off;
|
||||
include /etc/nginx/webui-proxy.conf;
|
||||
}
|
||||
|
||||
# SSE live-tail stream for Maintenance > Logs. Same buffering / timeout
|
||||
# concerns as the software progress stream: nginx must not buffer the
|
||||
# event frames, and the connection has to stay open through quiet logs.
|
||||
# The Go side sends a 15 s heartbeat, so 600 s of read timeout is plenty
|
||||
# of safety margin.
|
||||
location ~ ^/maintenance/logs/[^/]+/tail$ {
|
||||
proxy_read_timeout 600s;
|
||||
proxy_buffering off;
|
||||
include /etc/nginx/webui-proxy.conf;
|
||||
}
|
||||
|
||||
# Support bundle collection runs `support collect`, which emits nothing
|
||||
# until it finishes (~50 s) and then sends the whole archive at once.
|
||||
# The upstream is silent for that window, so the default 60 s
|
||||
# proxy_read_timeout would 504 just as the bundle is ready. Buffering
|
||||
# stays on — it's a plain file download, not a stream.
|
||||
location = /maintenance/support-bundle {
|
||||
proxy_read_timeout 300s;
|
||||
include /etc/nginx/webui-proxy.conf;
|
||||
}
|
||||
|
||||
# On-device User's Guide: the mkdocs site bundled into the rootfs at
|
||||
# /var/www/guide by post-build.sh. Served as static files (no upstream
|
||||
# call), public like the published guide. Present only when the build
|
||||
# host had mkdocs; the WebUI hides its User Guide entry when it's absent.
|
||||
location /guide/ {
|
||||
alias /var/www/guide/;
|
||||
index index.html;
|
||||
}
|
||||
|
||||
# Liveness probe — nginx-only, no upstream call. Used by the watchdog
|
||||
# div in base.html and the reboot-overlay poller.
|
||||
location = /device-status {
|
||||
access_log off;
|
||||
return 204;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
################################################################################
|
||||
#
|
||||
# webui
|
||||
#
|
||||
################################################################################
|
||||
|
||||
WEBUI_VERSION = 1.0
|
||||
WEBUI_SITE_METHOD = local
|
||||
WEBUI_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/webui
|
||||
WEBUI_GOMOD = infix/webui
|
||||
WEBUI_LICENSE = MIT
|
||||
WEBUI_LICENSE_FILES = LICENSE
|
||||
WEBUI_REDISTRIBUTE = NO
|
||||
|
||||
define WEBUI_INSTALL_EXTRA
|
||||
$(INSTALL) -D -m 0644 $(WEBUI_PKGDIR)/webui.svc \
|
||||
$(FINIT_D)/available/webui.conf
|
||||
$(INSTALL) -D -m 0644 $(WEBUI_PKGDIR)/webui.conf \
|
||||
$(TARGET_DIR)/etc/nginx/app/webui.conf
|
||||
$(INSTALL) -D -m 0644 $(WEBUI_PKGDIR)/webui-proxy.conf \
|
||||
$(TARGET_DIR)/etc/nginx/webui-proxy.conf
|
||||
$(INSTALL) -D -m 0644 $(WEBUI_PKGDIR)/default.conf \
|
||||
$(TARGET_DIR)/etc/nginx/available/default.conf
|
||||
$(INSTALL) -D -m 0644 $(WEBUI_PKGDIR)/50x.html \
|
||||
$(TARGET_DIR)/usr/html/50x.html
|
||||
endef
|
||||
WEBUI_POST_INSTALL_TARGET_HOOKS += WEBUI_INSTALL_EXTRA
|
||||
|
||||
$(eval $(golang-package))
|
||||
@@ -0,0 +1,3 @@
|
||||
service <!> name:webui log:prio:daemon.info,tag:webui \
|
||||
[2345] env:-/etc/default/webui webui -listen 127.0.0.1:10000 \
|
||||
-- Web management interface
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
From 9899169d6dcb07aaecdde09c77ef59b56f66e3e5 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
|
||||
Date: Fri, 5 Jun 2026 09:10:15 +0200
|
||||
Subject: [PATCH 3/3] fix(functions): do not touch global ip_forward sysctl
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Infix manages IPv4/IPv6 forwarding per-interface via the sysctls
|
||||
net.ipv4.conf.<iface>.forwarding and net.ipv6.conf.<iface>.forwarding.
|
||||
firewalld's enable_ip_forwarding() instead writes the *global*
|
||||
net.ipv4.ip_forward and net.ipv6.conf.all.forwarding knobs. The kernel
|
||||
propagates those global values to every interface, which clobbers the
|
||||
per-interface forwarding configuration Infix sets.
|
||||
|
||||
Make enable_ip_forwarding() a no-op. The backends still install the
|
||||
masquerade and forward-port nftables rules; the per-packet forwarding
|
||||
decision is governed by the inbound interface's forwarding flag, so
|
||||
routing keeps working on the interfaces where Infix enabled it while
|
||||
the global knob is left untouched.
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/firewall/functions.py | 13 ++++++++-----
|
||||
1 file changed, 8 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/src/firewall/functions.py b/src/firewall/functions.py
|
||||
index 1b8a32c..cd87f5d 100644
|
||||
--- a/src/firewall/functions.py
|
||||
+++ b/src/firewall/functions.py
|
||||
@@ -495,11 +495,14 @@ def writefile(filename, line):
|
||||
|
||||
|
||||
def enable_ip_forwarding(ipv):
|
||||
- if ipv == "ipv4":
|
||||
- return writefile("/proc/sys/net/ipv4/ip_forward", "1\n")
|
||||
- elif ipv == "ipv6":
|
||||
- return writefile("/proc/sys/net/ipv6/conf/all/forwarding", "1\n")
|
||||
- return False
|
||||
+ # Infix manages IP forwarding per-interface via the sysctls
|
||||
+ # net.ipv4.conf.<iface>.forwarding and net.ipv6.conf.<iface>.forwarding.
|
||||
+ # Writing the global net.ipv4.ip_forward / net.ipv6.conf.all.forwarding
|
||||
+ # knobs would clobber those per-interface settings, since the kernel
|
||||
+ # propagates the global value to every interface. Make this a no-op:
|
||||
+ # firewalld still installs the masquerade and forward-port nft rules, and
|
||||
+ # forwarding works on the interfaces where Infix has enabled it.
|
||||
+ return True
|
||||
|
||||
|
||||
def get_nf_conntrack_short_name(module):
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
|
||||
index 72a0bf503..9bdc2b41d 100644
|
||||
--- a/src/ap/hostapd.c
|
||||
+++ b/src/ap/hostapd.c
|
||||
@@ -916,12 +916,6 @@ static int hostapd_validate_bssid_configuration(struct hostapd_iface *iface)
|
||||
if (bits < j)
|
||||
bits = j;
|
||||
|
||||
- if (bits > 40) {
|
||||
- wpa_printf(MSG_ERROR, "Too many bits in the BSSID mask (%u)",
|
||||
- bits);
|
||||
- return -1;
|
||||
- }
|
||||
-
|
||||
os_memset(mask, 0xff, ETH_ALEN);
|
||||
j = bits / 8;
|
||||
for (i = 5; i > 5 - j; i--)
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 2fe4584b70875e1624053f90cecfff25d55925ea Mon Sep 17 00:00:00 2001
|
||||
From f614327b6eca20319f5eee20ac954ea8d92276e4 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 19 Sep 2023 18:38:10 +0200
|
||||
Subject: [PATCH 01/50] net: phy: marvell10g: Support firmware loading on
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 7a8bc364e2a1fe55aef7eb189405a799f69bc848 Mon Sep 17 00:00:00 2001
|
||||
From 7d453cb838611f44d7566b6174ae95a5d2265fa1 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 21 Nov 2023 20:15:24 +0100
|
||||
Subject: [PATCH 02/50] net: phy: marvell10g: Fix power-up when strapped to
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 6d4aa58ed9394b0b842e89fb4f7e080491e75bed Mon Sep 17 00:00:00 2001
|
||||
From 4b08a36e3107c4018ddf3a19760cc87d02281bd7 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 15 Nov 2023 20:58:42 +0100
|
||||
Subject: [PATCH 03/50] net: phy: marvell10g: Add LED support for 88X3310
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From deb7af02b1600f51052bca3d7f566019966062aa Mon Sep 17 00:00:00 2001
|
||||
From e5e81eb1172db22be7c83efc8071108d0d42565e Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 12 Dec 2023 09:51:05 +0100
|
||||
Subject: [PATCH 04/50] net: phy: marvell10g: Support LEDs tied to a single
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
From 0777c7502a8c98c1f0987a58019ec43d230768eb Mon Sep 17 00:00:00 2001
|
||||
From 1f735096f1b8920ee6f21bf60a4c2d3f6376adeb Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 27 Mar 2024 10:10:19 +0100
|
||||
Subject: [PATCH 05/50] net: phy: Do not resume PHY when attaching
|
||||
@@ -19,10 +19,10 @@ administratively down.
|
||||
1 file changed, 1 deletion(-)
|
||||
|
||||
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
|
||||
index dea8b94286d1..f8c376c26fac 100644
|
||||
index 26b08e3dbd1d..7597308534a0 100644
|
||||
--- a/drivers/net/phy/phy_device.c
|
||||
+++ b/drivers/net/phy/phy_device.c
|
||||
@@ -1750,7 +1750,6 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
|
||||
@@ -1753,7 +1753,6 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
|
||||
if (err)
|
||||
goto error;
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
From 7e47e0fdb4b1d95f58e75d3ee1fd52d2d64866c9 Mon Sep 17 00:00:00 2001
|
||||
From e38d2d2f07f55819d2191d4282dc2d4688075d7d Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Mon, 4 Mar 2024 16:47:28 +0100
|
||||
Subject: [PATCH 06/50] net: bridge: avoid classifying unknown multicast as
|
||||
@@ -170,7 +170,7 @@ index 2cbae0f9ae1f..0c539bf4bebe 100644
|
||||
br_multicast_flood(mdst, skb, brmctx, local_rcv, false);
|
||||
}
|
||||
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
|
||||
index 5855eb050208..06641afb99a8 100644
|
||||
index b77b3250afbd..f27cb9b54211 100644
|
||||
--- a/net/bridge/br_multicast.c
|
||||
+++ b/net/bridge/br_multicast.c
|
||||
@@ -3836,6 +3836,11 @@ static void br_multicast_err_count(const struct net_bridge *br,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 099962fe582c35da76fee0b88ac0051050270e66 Mon Sep 17 00:00:00 2001
|
||||
From a9b80b26c96e5492ab32a72ee36e3321a2b90ee9 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Tue, 5 Mar 2024 06:44:41 +0100
|
||||
Subject: [PATCH 07/50] net: bridge: Ignore router ports when forwarding L2
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
From 003a394f39682faabb14d968a32504306a5a97b5 Mon Sep 17 00:00:00 2001
|
||||
From 01e41d87efa1b63d292f0acfe4d1fb0e39baab20 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Thu, 4 Apr 2024 16:36:30 +0200
|
||||
Subject: [PATCH 08/50] net: bridge: drop delay for applying strict multicast
|
||||
@@ -24,7 +24,7 @@ Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
2 files changed, 8 insertions(+), 38 deletions(-)
|
||||
|
||||
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
|
||||
index 06641afb99a8..bacff6c2e968 100644
|
||||
index f27cb9b54211..bfb38b18c1da 100644
|
||||
--- a/net/bridge/br_multicast.c
|
||||
+++ b/net/bridge/br_multicast.c
|
||||
@@ -1760,10 +1760,6 @@ static void br_ip6_multicast_querier_expired(struct timer_list *t)
|
||||
@@ -133,7 +133,7 @@ index 06641afb99a8..bacff6c2e968 100644
|
||||
timer_delete_sync(&brmctx->ip6_own_query.timer);
|
||||
#endif
|
||||
}
|
||||
@@ -4726,8 +4710,6 @@ EXPORT_SYMBOL_GPL(br_multicast_router);
|
||||
@@ -4747,8 +4731,6 @@ EXPORT_SYMBOL_GPL(br_multicast_router);
|
||||
|
||||
int br_multicast_set_querier(struct net_bridge_mcast *brmctx, unsigned long val)
|
||||
{
|
||||
@@ -142,7 +142,7 @@ index 06641afb99a8..bacff6c2e968 100644
|
||||
val = !!val;
|
||||
|
||||
spin_lock_bh(&brmctx->br->multicast_lock);
|
||||
@@ -4738,19 +4720,9 @@ int br_multicast_set_querier(struct net_bridge_mcast *brmctx, unsigned long val)
|
||||
@@ -4759,19 +4741,9 @@ int br_multicast_set_querier(struct net_bridge_mcast *brmctx, unsigned long val)
|
||||
if (!val)
|
||||
goto unlock;
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
From 2d870ee42b147399d95dee79144195919dabb2fe Mon Sep 17 00:00:00 2001
|
||||
From 944c77d075c8e0655bd3a28e96892261465a5cf4 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Thu, 16 May 2024 14:51:54 +0200
|
||||
Subject: [PATCH 09/50] net: bridge: Differentiate MDB additions from
|
||||
@@ -62,7 +62,7 @@ index 400eb872b403..cd7a2cd0911a 100644
|
||||
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 bacff6c2e968..a5f3e4935edc 100644
|
||||
index bfb38b18c1da..cc7ce7fc6580 100644
|
||||
--- a/net/bridge/br_multicast.c
|
||||
+++ b/net/bridge/br_multicast.c
|
||||
@@ -616,7 +616,7 @@ static void br_multicast_fwd_src_handle(struct net_bridge_group_src *src)
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
From 763289c999ed2b0a9ecdb417b32c9d5a2f6b104a Mon Sep 17 00:00:00 2001
|
||||
From 3f58061ab8f869a6432a1256ee7422bb157b624e Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Fri, 24 Nov 2023 23:29:55 +0100
|
||||
Subject: [PATCH 10/50] nvmem: layouts: onie-tlv: Let device probe even when
|
||||
@@ -17,10 +17,10 @@ simply refrain from registering any cells in those cases.
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/drivers/nvmem/layouts/onie-tlv.c b/drivers/nvmem/layouts/onie-tlv.c
|
||||
index 0967a32319a2..48547d5bb502 100644
|
||||
index 8b0f3c1b8a0e..de85690aaa30 100644
|
||||
--- a/drivers/nvmem/layouts/onie-tlv.c
|
||||
+++ b/drivers/nvmem/layouts/onie-tlv.c
|
||||
@@ -197,7 +197,7 @@ static int onie_tlv_parse_table(struct nvmem_layout *layout)
|
||||
@@ -198,7 +198,7 @@ static int onie_tlv_parse_table(struct nvmem_layout *layout)
|
||||
|
||||
if (!onie_tlv_hdr_is_valid(dev, &hdr)) {
|
||||
dev_err(dev, "Invalid ONIE TLV header\n");
|
||||
@@ -29,7 +29,7 @@ index 0967a32319a2..48547d5bb502 100644
|
||||
}
|
||||
|
||||
hdr_len = sizeof(hdr.id) + sizeof(hdr.version) + sizeof(hdr.data_len);
|
||||
@@ -205,7 +205,7 @@ static int onie_tlv_parse_table(struct nvmem_layout *layout)
|
||||
@@ -206,7 +206,7 @@ static int onie_tlv_parse_table(struct nvmem_layout *layout)
|
||||
table_len = hdr_len + data_len;
|
||||
if (table_len > ONIE_TLV_MAX_LEN) {
|
||||
dev_err(dev, "Invalid ONIE TLV data length\n");
|
||||
@@ -38,7 +38,7 @@ index 0967a32319a2..48547d5bb502 100644
|
||||
}
|
||||
|
||||
table = devm_kmalloc(dev, table_len, GFP_KERNEL);
|
||||
@@ -217,7 +217,7 @@ static int onie_tlv_parse_table(struct nvmem_layout *layout)
|
||||
@@ -218,7 +218,7 @@ static int onie_tlv_parse_table(struct nvmem_layout *layout)
|
||||
return ret;
|
||||
|
||||
if (!onie_tlv_crc_is_valid(dev, table_len, table))
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
From 7e779610739e75b5d1e3b66bc8b0aa0c8ad68c9f Mon Sep 17 00:00:00 2001
|
||||
From f492d4efac3a65bca4d53e383336c36113e28a8b Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Sun, 11 Aug 2024 11:27:35 +0200
|
||||
Subject: [PATCH 11/50] net: usb: r8152: add r8153b support for link/activity
|
||||
@@ -18,7 +18,7 @@ Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
1 file changed, 8 insertions(+)
|
||||
|
||||
diff --git a/drivers/net/usb/r8152.c b/drivers/net/usb/r8152.c
|
||||
index d61074178279..701cc975cf11 100644
|
||||
index 8cf4e81f8f88..1bb93efb96c6 100644
|
||||
--- a/drivers/net/usb/r8152.c
|
||||
+++ b/drivers/net/usb/r8152.c
|
||||
@@ -41,6 +41,11 @@
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From fe101bb64d6f6c558c679630733d76bdf4ab3b5f Mon Sep 17 00:00:00 2001
|
||||
From a2448b84e5483b5b91b46ac31b27609352fd9ed6 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Sun, 10 Aug 2025 18:52:54 +0200
|
||||
Subject: [PATCH 12/50] arm64: dts: mediatek: mt7986a: rename BPi R3 ports to
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 672339f6b4a1b8849cf96fe9b6150bd6a49b15b4 Mon Sep 17 00:00:00 2001
|
||||
From c4ac90dcc5aefbb704c70ebbfe041dee683b882b Mon Sep 17 00:00:00 2001
|
||||
From: Mattias Walström <lazzer@gmail.com>
|
||||
Date: Wed, 20 Aug 2025 21:38:24 +0200
|
||||
Subject: [PATCH 13/50] drm/panel-simple: Add a timing for the Raspberry Pi 7"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 9ece9e9fc5269cfadeb53d6e8cafc124217f606f Mon Sep 17 00:00:00 2001
|
||||
From 8da101ec2429ba0bf1410346260f9a7ddfb0bdbe Mon Sep 17 00:00:00 2001
|
||||
From: Mattias Walström <lazzer@gmail.com>
|
||||
Date: Thu, 21 Aug 2025 11:20:23 +0200
|
||||
Subject: [PATCH 14/50] input:touchscreen:edt-ft5x06: Add polled mode
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 3eecd843969fec2c1532c32b39f5e81bb51425b3 Mon Sep 17 00:00:00 2001
|
||||
From 93646f890903157bb78ef369ddf9d99a9161de0d Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 12 Mar 2024 10:27:24 +0100
|
||||
Subject: [PATCH 15/50] [FIX] net: dsa: mv88e6xxx: Fix timeout on waiting for
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 861654c5d7ce2180cc633517221eac516988c813 Mon Sep 17 00:00:00 2001
|
||||
From 29cfa1b5ffba41728dc5e0991d1ee27b903d569c Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 27 Mar 2024 15:52:43 +0100
|
||||
Subject: [PATCH 16/50] net: dsa: mv88e6xxx: Improve indirect register access
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 6b84a1215d87bd03432ed6b79bd55e08d84e5125 Mon Sep 17 00:00:00 2001
|
||||
From a7fa9a45329781519702d46ba05815433fff4acc Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Mon, 22 Apr 2024 23:18:01 +0200
|
||||
Subject: [PATCH 17/50] net: dsa: mv88e6xxx: Honor ports being managed via
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From aaa6dce534b6c509f8e36055660bd670d2bc2758 Mon Sep 17 00:00:00 2001
|
||||
From cda7100b0a29e6a9bd29a67ff9db3bcccb72065c Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 24 Apr 2024 22:41:04 +0200
|
||||
Subject: [PATCH 18/50] net: dsa: mv88e6xxx: Limit rsvd2cpu policy to user
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From f00c52f509a093dc503d14854cc8ae4ae2b5be11 Mon Sep 17 00:00:00 2001
|
||||
From ccf9f9262e8570cfd522b841465f3edf76b3447a Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 28 May 2024 10:38:42 +0200
|
||||
Subject: [PATCH 19/50] net: dsa: tag_dsa: Use tag priority as initial
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 64c5a7544da9777934ff971ec4b3c46e97e653ae Mon Sep 17 00:00:00 2001
|
||||
From 3200169701ef4d9afdea35355b3fe41d6f363134 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 16 Jan 2024 16:00:55 +0100
|
||||
Subject: [PATCH 20/50] net: dsa: Support MDB memberships whose L2 addresses
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 6d9c872aafa7ec5a330778c82af2dde46e0959b1 Mon Sep 17 00:00:00 2001
|
||||
From dea9ff6c0bdf7c3e4abc2fb507d2ab4b94dabb63 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Thu, 21 Mar 2024 19:12:15 +0100
|
||||
Subject: [PATCH 21/50] net: dsa: Support EtherType based priority overrides
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From ff0b28ef9545851cf670d2786e71db7868fb310f Mon Sep 17 00:00:00 2001
|
||||
From 1dc01d7420e3d511babfbdeda742d93527d72abb Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Fri, 22 Mar 2024 16:15:43 +0100
|
||||
Subject: [PATCH 22/50] net: dsa: mv88e6xxx: Support EtherType based priority
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From d92fe419bd632fcd07254b95a11f5a2ada340555 Mon Sep 17 00:00:00 2001
|
||||
From caed0d150badbb040e403975645bf09ba3f44e76 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 28 May 2024 11:04:22 +0200
|
||||
Subject: [PATCH 23/50] net: dsa: mv88e6xxx: Add mqprio qdisc support
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 71ad38fb7f6bde1dedc16f1939ce2d224feb7e60 Mon Sep 17 00:00:00 2001
|
||||
From 1bd5b0e8a5574209b386d6d8bc1cf3bcf2e4fd5e Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 29 May 2024 13:20:41 +0200
|
||||
Subject: [PATCH 24/50] net: dsa: mv88e6xxx: Use VLAN prio over IP when both
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From dd65f95bbbe1897452ddb520699a0c758f0a495c Mon Sep 17 00:00:00 2001
|
||||
From 7894882d70e8223939a6d9a0e15cc6bbc08ae33c Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 26 Nov 2024 19:45:59 +0100
|
||||
Subject: [PATCH 25/50] [FIX] net: dsa: mv88e6xxx: Trap locally terminated
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 79f68061eb996858459b131ffed45e3abeaf37df Mon Sep 17 00:00:00 2001
|
||||
From 09cb11eaf5cc754274620ff6eaedf768c9605e44 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Thu, 16 Jan 2025 12:35:12 +0100
|
||||
Subject: [PATCH 26/50] net: dsa: mv88e6xxx: collapse disabled state into
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From baeccc8d6402fca534a9706e1343e35bd04eb505 Mon Sep 17 00:00:00 2001
|
||||
From 7127fd4194cde754cbc9ea869c9e65e335701d95 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 12 Feb 2025 22:03:14 +0100
|
||||
Subject: [PATCH 27/50] net: dsa: mv88e6xxx: Only activate LAG offloading when
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From e1f1e6e36c398652f5bf121f4ae4c5b0c5af3e8a Mon Sep 17 00:00:00 2001
|
||||
From c2f3d9277e12e7622651b521af8b43b1fe036cf0 Mon Sep 17 00:00:00 2001
|
||||
From: Mattias Walström <lazzer@gmail.com>
|
||||
Date: Wed, 14 Jan 2026 18:22:41 +0100
|
||||
Subject: [PATCH 28/50] net: dsa: mv88e6xxx: Add LED support for 6393X
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 787b93706592b82184bb5d2cf38ebd6ebc33068c Mon Sep 17 00:00:00 2001
|
||||
From ac98be8b6fd1b6afa264c68685d5dc82d73da1f8 Mon Sep 17 00:00:00 2001
|
||||
From: Mattias Walström <lazzer@gmail.com>
|
||||
Date: Thu, 15 Jan 2026 22:47:37 +0100
|
||||
Subject: [PATCH 29/50] wifi: brcmfmac: support deletion and recreation of
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user