mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-31 21:13:00 +02:00
Merge pull request #1258 from kernelkit/hostname-stability
Hostname stability Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
+27
-29
@@ -3,41 +3,39 @@ Checklists for Pull Requests and Releases
|
|||||||
|
|
||||||
Maintainer checklists for reviewing pull requests and doing releases.
|
Maintainer checklists for reviewing pull requests and doing releases.
|
||||||
|
|
||||||
|
|
||||||
Pull Requests
|
Pull Requests
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
- If applicable, is there a readable ChangeLog entry?
|
- If applicable, is there a readable ChangeLog entry?
|
||||||
- If any LICENSE file has been updated, has the `.hash` file been updated?
|
- If any LICENSE file has been updated, has the `.hash` file been updated?
|
||||||
- If any change to a Finit `.svc` file, does any run/task linger?
|
- If any change to a Finit `.svc` file, does any run/task linger?
|
||||||
I.e., is there a runlevel and/or condition defined to prevent them
|
I.e., is there a runlevel and/or condition defined to prevent them
|
||||||
from running outside of their intended runlevel?
|
from running outside of their intended runlevel?
|
||||||
- If any change to grub or qemu/qeneth setup, has it been tested in GNS3?
|
- If any change to grub or qemu/qeneth setup, has it been tested in GNS3?
|
||||||
- If any change to u-boot/buildroot, has it been tested with `<booloader>_defconfig`
|
- If any change to u-boot/buildroot, has it been tested with `<booloader>_defconfig`
|
||||||
- If any change to logging, have the resulting logs been audited?
|
- If any change to logging, have the resulting logs been audited?
|
||||||
- Check for duplicate entries, misspellings
|
- Check for duplicate entries, misspellings
|
||||||
- Check for sneaky severity changes, e.g., error vs note, error vs warning
|
- Check for sneaky severity changes, e.g., error vs note, error vs warning
|
||||||
- If new subsystem, or major changes to a subsystem, have the docs been updated?
|
- If new subsystem, or major changes to a subsystem, have the docs been updated?
|
||||||
- If change to mDNS, has it been tested with netbrowse?
|
- If change to mDNS, has it been tested with netbrowse?
|
||||||
- If change to `_defconfig`, verify `local.mk` and sync with other archs
|
- If change to `_defconfig`, verify `local.mk` and sync with other archs
|
||||||
- Test manually as well, e.g., CLI changes do not have ha regression tests
|
- Test manually as well, e.g., CLI changes do not have ha regression tests
|
||||||
- Build from distclean, or use artifacts built by build servers, for manual tests
|
- Build from distclean, or use artifacts built by build servers, for manual tests
|
||||||
|
|
||||||
|
|
||||||
Releases
|
Releases
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Recommended checkpoints, use at your own discretion:
|
Recommended checkpoints, use at your own discretion:
|
||||||
|
|
||||||
- Make at least one -betaN release to verify the GitHub workflow well in time release day
|
- Make at least one -betaN release to verify the GitHub workflow well in time release day
|
||||||
- Stuff happens, remember kernelkit/infix#735
|
- Stuff happens, remember kernelkit/infix#735
|
||||||
- Make at least one -rcN to flush out any issues in customer repos
|
- Make at least one -rcN to flush out any issues in customer repos
|
||||||
- Easy to forget adaptations/hacks in customer repos -- may need Infix change/support
|
- Easy to forget adaptations/hacks in customer repos -- may need Infix change/support
|
||||||
- Verify release artifacts (checksums, completeness, no corrupted files)
|
- Verify release artifacts (checksums, completeness, no corrupted files)
|
||||||
- Test on actual hardware for at least one architecture
|
- Test on actual hardware for at least one architecture
|
||||||
- Review ChangeLog for completeness
|
- Review ChangeLog for completeness
|
||||||
- Check for release-blocking issues
|
- Check for release-blocking issues
|
||||||
- Verify generated GNS3 appliance, no marketplace update on -rc builds
|
- Verify generated GNS3 appliance, no marketplace update on -rc builds
|
||||||
- Ensure the markdown link for the release diff is updated
|
- Ensure the markdown link for the release diff is updated
|
||||||
- Ensure subrepos are tagged (can be automated, see kernelkit/infix#393)
|
- Ensure subrepos are tagged (can be automated, see kernelkit/infix#393)
|
||||||
- Sync tags for all repo. sync activities
|
- Sync tags for all repo. sync activities
|
||||||
|
|||||||
+154
-64
@@ -9,15 +9,15 @@ forms of collaboration as well. [Let's talk!][support] :handshake:
|
|||||||
|
|
||||||
If you are unsure how to start implementing an idea or fix:
|
If you are unsure how to start implementing an idea or fix:
|
||||||
|
|
||||||
- :bug: open an issue, there are human friendly templates for _bugs_
|
- :bug: open an issue, there are human friendly templates for _bugs_
|
||||||
and _feature requests_ at <https://github.com/kernelkit/infix/issues>
|
and _feature requests_ at <https://github.com/kernelkit/infix/issues>
|
||||||
- :speech_balloon: use the [Q&A Forum][discuss]
|
- :speech_balloon: use the [Q&A Forum][discuss]
|
||||||
- :technologist: The [Developer's Guide][devguide] is also a useful start
|
- :technologist: The [Developer's Guide][devguide] is also a useful start
|
||||||
|
|
||||||
> _Talking about code and problems first is often the best way to get
|
> [!IMPORTANT]
|
||||||
|
> Talking about code and problems first is often the best way to get
|
||||||
> started before submitting a pull request. We have found it always
|
> started before submitting a pull request. We have found it always
|
||||||
> saves time, yours and ours._
|
> saves time, yours and ours.
|
||||||
|
|
||||||
|
|
||||||
:sparkles: General Guidelines
|
:sparkles: General Guidelines
|
||||||
-----------------------------
|
-----------------------------
|
||||||
@@ -27,21 +27,20 @@ version the change is made against, what it does, and, more importantly
|
|||||||
*why* -- from your perspective, why is it a bug, why does the code need
|
*why* -- from your perspective, why is it a bug, why does the code need
|
||||||
changing in this way. Start with why.
|
changing in this way. Start with why.
|
||||||
|
|
||||||
- :bug: Bug reports need metadata like Infix version or commit hash
|
- :bug: Bug reports need metadata like Infix version or commit hash
|
||||||
- :adhesive_bandage: Bug fixes also need version, and (preferably) a
|
- :adhesive_bandage: Bug fixes also need version, and (preferably) a
|
||||||
corresponding issue number for the ChangeLog
|
corresponding issue number for the ChangeLog
|
||||||
- :new: New features, you need to get approval of the YANG model first!
|
- :new: New features, you need to get approval of the YANG model first!
|
||||||
:speech_balloon: Please use the [Forum][discuss], e.g., category:
|
:speech_balloon: Please use the [Forum][discuss], e.g., category:
|
||||||
*Ideas*, or open a :pray: feature request issue
|
*Ideas*, or open a :pray: feature request issue
|
||||||
- :white_check_mark: New features also need new regression tests, this
|
- :white_check_mark: New features also need new regression tests, this
|
||||||
can be basic tests or more complex use-case tests comprising multiple
|
can be basic tests or more complex use-case tests comprising multiple
|
||||||
subsystems, see [Testing Changes](#test_tube-testing-changes), below
|
subsystems, see [Testing Changes](#test_tube-testing-changes), below
|
||||||
|
|
||||||
Please take care to ensure you follow the project coding style and the
|
Please take care to ensure you follow the project coding style and the
|
||||||
commit message format. If you follow these recommendations you help
|
commit message format. If you follow these recommendations you help
|
||||||
the maintainers and make it easier for them to include your code.
|
the maintainers and make it easier for them to include your code.
|
||||||
|
|
||||||
|
|
||||||
:woman_technologist: Coding Style
|
:woman_technologist: Coding Style
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
@@ -51,39 +50,40 @@ and it is expected that you provide a human-readable summary for the
|
|||||||
release notes (ChangeLog) and at least a configuration example in the
|
release notes (ChangeLog) and at least a configuration example in the
|
||||||
manual for new features.
|
manual for new features.
|
||||||
|
|
||||||
> **Tip:** consider ["Readme driven development"][RDD] for new features.
|
> [!TIP]
|
||||||
> It is amazing how many flaws in your own bright ideas come to bare
|
> Consider ["Readme driven development"][RDD] for new features. It is
|
||||||
> when you suddenly have to explain them to someone else!
|
> amazing how many flaws in your own bright ideas come to bare when you
|
||||||
|
> suddenly have to explain them to someone else!
|
||||||
|
|
||||||
We expect code contributions for:
|
We expect code contributions for:
|
||||||
|
|
||||||
- C code in [Linux Coding Style][Linux]
|
- C code in [Linux Coding Style][Linux]
|
||||||
- Python code should follow [PEP-8][]
|
- Python code should follow [PEP-8][]
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
> **However,** always submit code that follows the style of surrounding
|
> **However,** always submit code that follows the style of surrounding
|
||||||
> code! Legacy takes precedence, and remember, we read code a lot more
|
> code! Legacy takes precedence, and remember, we read code a lot more
|
||||||
> than write it, so legibility is important.
|
> than write it, so legibility is important.
|
||||||
|
|
||||||
The ChangeLog deserves a separate mention:
|
The ChangeLog deserves a separate mention:
|
||||||
|
|
||||||
- Releases are listed in reverse chronological order order, so the
|
- Releases are listed in reverse chronological order order, so the
|
||||||
latest/next release is at the beginning of the file
|
latest/next release is at the beginning of the file
|
||||||
- Only *user-facing bugs and features* are detailed, so code refactor,
|
- Only *user-facing bugs and features* are detailed, so code refactor,
|
||||||
new tests, etc. are not listed.
|
new tests, etc. are not listed.
|
||||||
- Add your changes/features to the Changes section
|
- Add your changes/features to the Changes section
|
||||||
- Add your Fix line in the Fixes section, in numeric order
|
- Add your Fix line in the Fixes section, in numeric order
|
||||||
- Changes and fixes without an issue number are listed after all
|
- Changes and fixes without an issue number are listed after all
|
||||||
numbered ones
|
numbered ones
|
||||||
- YANG model changes are documented in their respective model, for
|
- YANG model changes are documented in their respective model, for
|
||||||
standard models, e.g., for `ietf-interfaces.yang`, the corresponding
|
standard models, e.g., for `ietf-interfaces.yang`, the corresponding
|
||||||
`infix-interfaces.yang` detail augments/deviations as revisions.
|
`infix-interfaces.yang` detail augments/deviations as revisions.
|
||||||
|
|
||||||
A final note, lines of code are allowed to be longer than 72 characters
|
A final note, lines of code are allowed to be longer than 72 characters
|
||||||
these days, unless you live by PEP-8 (see above). There is no enforced
|
these days, unless you live by PEP-8 (see above). There is no enforced
|
||||||
maximum, but the team usually keep it around 100 characters for both C
|
maximum, but the team usually keep it around 100 characters for both C
|
||||||
and Python.
|
and Python.
|
||||||
|
|
||||||
|
|
||||||
:test_tube: Testing Changes
|
:test_tube: Testing Changes
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
@@ -97,9 +97,8 @@ the same pull request.
|
|||||||
|
|
||||||
For help getting started with testing, see the following resources:
|
For help getting started with testing, see the following resources:
|
||||||
|
|
||||||
- [Developer's Guide][devguide]
|
- [Developer's Guide][devguide]
|
||||||
- [Regression Testing][testing]
|
- [Regression Testing][testing]
|
||||||
|
|
||||||
|
|
||||||
:memo: Commit Messages
|
:memo: Commit Messages
|
||||||
----------------------
|
----------------------
|
||||||
@@ -110,45 +109,135 @@ proud of your work and set up a proper GIT identity for your commits:
|
|||||||
|
|
||||||
<img src="../doc/jack.png" width=70 align="right">
|
<img src="../doc/jack.png" width=70 align="right">
|
||||||
|
|
||||||
$ git config --global user.name "Jacky Linker"
|
```bash
|
||||||
$ git config --global user.email jacky.linker@example.com
|
$ git config --global user.name "Jacky Linker"
|
||||||
|
$ git config --global user.email jacky.linker@example.com
|
||||||
|
```
|
||||||
|
|
||||||
Example commit message from one of many [online guides][cbeams]. Use
|
Example commit message from one of many [online guides][cbeams]. Use
|
||||||
`git commit -s` to automatically add a `Signed-off-by` for proof of
|
`git commit -s` to automatically add a `Signed-off-by` for proof of
|
||||||
origin, see [DCO][] for more info.
|
origin, see [DCO][] for more info.
|
||||||
|
|
||||||
subsystem: brief, but clear and concise summary of changes
|
```text
|
||||||
|
subsystem: brief, but clear and concise summary of changes
|
||||||
More detailed explanatory text, if necessary. Wrap it to about 72
|
|
||||||
characters or so. In some contexts, the first line is treated as
|
|
||||||
the subject of an email and the rest of the text as the body. The
|
|
||||||
empty line separating summary from body is critical. Tools like
|
|
||||||
rebase can get confused if the empty line is missing.
|
|
||||||
|
|
||||||
Further paragraphs should be separated with empty lines.
|
|
||||||
|
|
||||||
- Bullet points are okay, too
|
|
||||||
|
|
||||||
- Typically a hyphen or asterisk is used for the bullet, preceded
|
|
||||||
by a single space, with blank lines in between, but conventions
|
|
||||||
vary here
|
|
||||||
|
|
||||||
If you use an issue tracker, put references to them at the bottom,
|
More detailed explanatory text, if necessary. Wrap it to about 72
|
||||||
like this:
|
characters or so. In some contexts, the first line is treated as
|
||||||
|
the subject of an email and the rest of the text as the body. The
|
||||||
|
empty line separating summary from body is critical. Tools like
|
||||||
|
rebase can get confused if the empty line is missing.
|
||||||
|
|
||||||
Resolves: #123
|
Further paragraphs should be separated with empty lines.
|
||||||
See also: #456, #789
|
|
||||||
|
|
||||||
Signed-off-by: Jacky Linker <jacky.linker@example.com>
|
- Bullet points are okay, too
|
||||||
|
|
||||||
|
- Typically a hyphen or asterisk is used for the bullet, preceded
|
||||||
|
by a single space, with blank lines in between, but conventions
|
||||||
|
vary here
|
||||||
|
|
||||||
|
If you use an issue tracker, put references to them at the bottom,
|
||||||
|
like this:
|
||||||
|
|
||||||
|
Resolves: #123
|
||||||
|
See also: #456, #789
|
||||||
|
|
||||||
|
Signed-off-by: Jacky Linker <jacky.linker@example.com>
|
||||||
|
```
|
||||||
|
|
||||||
This is an example of how to [automatically close][closing] an issue
|
This is an example of how to [automatically close][closing] an issue
|
||||||
when the commit is merged to mainline. Several keywords are available.
|
when the commit is merged to mainline. Several keywords are available.
|
||||||
|
|
||||||
|
:lock_with_ink_pen: Signing Commits with GPG
|
||||||
|
---------------------------------------------
|
||||||
|
|
||||||
|
To ensure the authenticity and integrity of your contributions, we
|
||||||
|
**require** all commits to be signed with GPG. This cryptographically
|
||||||
|
verifies that commits come from a trusted source.
|
||||||
|
|
||||||
|
### Generating a GPG Key
|
||||||
|
|
||||||
|
If you don't already have a GPG key, generate one:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ gpg --full-generate-key
|
||||||
|
```
|
||||||
|
|
||||||
|
When prompted, choose:
|
||||||
|
- Key type: `RSA and RSA` (default)
|
||||||
|
- Key size: `4096` bits (recommended for security)
|
||||||
|
- Expiration: `0` (key does not expire)
|
||||||
|
- Real name and email: Use the same email as your Git configuration
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> We recommend keys that do not expire for signing commits. Expiration
|
||||||
|
> creates a "usability time bomb" without providing meaningful security
|
||||||
|
> benefits for code signing. See [this article][pgpfan] for details.
|
||||||
|
|
||||||
|
### Configuring Git to Sign Commits
|
||||||
|
|
||||||
|
First, find your GPG key ID:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ gpg --list-secret-keys --keyid-format=long
|
||||||
|
```
|
||||||
|
|
||||||
|
Look for the line starting with `sec`, the key ID is the part after the `/`.
|
||||||
|
For example, in `sec rsa4096/ABCD1234EFGH5678`, the key ID is `ABCD1234EFGH5678`.
|
||||||
|
|
||||||
|
Configure Git to use your GPG key:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git config --global user.signingkey ABCD1234EFGH5678
|
||||||
|
$ git config --global commit.gpgsign true
|
||||||
|
```
|
||||||
|
|
||||||
|
The second command enables automatic signing for all commits. Alternatively,
|
||||||
|
you can sign individual commits with `git commit -S`.
|
||||||
|
|
||||||
|
### Publishing Your Public Key
|
||||||
|
|
||||||
|
To allow others to verify your signatures, publish your public key to a
|
||||||
|
keyserver:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ gpg --keyserver hkps://keys.openpgp.org --send-keys ABCD1234EFGH5678
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> The keyserver will send a verification email to the address associated
|
||||||
|
> with your key. You **must** click the link in that email to confirm
|
||||||
|
> ownership before your key becomes searchable by email address.
|
||||||
|
|
||||||
|
Alternative keyservers you can use:
|
||||||
|
- `hkps://keyserver.ubuntu.com`
|
||||||
|
- `hkps://pgp.mit.edu`
|
||||||
|
|
||||||
|
### Adding Your GPG Key to GitHub
|
||||||
|
|
||||||
|
For GitHub to show your commits as "Verified", you need to add your public
|
||||||
|
key to your account:
|
||||||
|
|
||||||
|
1. Export your public key:
|
||||||
|
```bash
|
||||||
|
$ gpg --armor --export ABCD1234EFGH5678
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Copy the entire output, including the `-----BEGIN PGP PUBLIC KEY BLOCK-----`
|
||||||
|
and `-----END PGP PUBLIC KEY BLOCK-----` lines.
|
||||||
|
|
||||||
|
3. Go to [GitHub Settings → SSH and GPG keys](https://github.com/settings/keys)
|
||||||
|
|
||||||
|
4. Click **New GPG key** and paste your public key.
|
||||||
|
|
||||||
|
Now your signed commits will display a "Verified" badge on GitHub! :white_check_mark:
|
||||||
|
|
||||||
|
For more details, see GitHub's [official documentation on commit signature verification][gpg-verify].
|
||||||
|
|
||||||
:twisted_rightwards_arrows: Pull Requests
|
:twisted_rightwards_arrows: Pull Requests
|
||||||
-----------------------------------------
|
-----------------------------------------
|
||||||
|
|
||||||
> _The git repository is the canonical location for information._
|
> [!NOTE]
|
||||||
|
> _The git repository is the canonical location for all information._
|
||||||
|
|
||||||
A pull request should preferably address a single issue or change. This
|
A pull request should preferably address a single issue or change. This
|
||||||
may of course include multiple related changes, but what is important to
|
may of course include multiple related changes, but what is important to
|
||||||
@@ -167,7 +256,6 @@ Buildroot, consider the pull request message body similar to the cover
|
|||||||
letter for a series of patches -- it's a summary of changes, and it is
|
letter for a series of patches -- it's a summary of changes, and it is
|
||||||
lost when the changes are merged to the mainline branch.
|
lost when the changes are merged to the mainline branch.
|
||||||
|
|
||||||
|
|
||||||
:balance_scale: Code of Conduct
|
:balance_scale: Code of Conduct
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
@@ -184,6 +272,8 @@ other contributions that are not aligned to this Code of Conduct."*
|
|||||||
[PEP-8]: https://peps.python.org/pep-0008/
|
[PEP-8]: https://peps.python.org/pep-0008/
|
||||||
[RDD]: https://tom.preston-werner.com/2010/08/23/readme-driven-development
|
[RDD]: https://tom.preston-werner.com/2010/08/23/readme-driven-development
|
||||||
[cbeams]: https://cbea.ms/git-commit/#seven-rules
|
[cbeams]: https://cbea.ms/git-commit/#seven-rules
|
||||||
[conduct]: CODE-OF-CONDUCT.md
|
[conduct]: CODE-OF-CONDUCT.md
|
||||||
[DCO]: https://developercertificate.org/
|
[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
|
[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
|
||||||
|
[pgpfan]: https://articles.59.ca/doku.php?id=pgpfan:expire
|
||||||
|
|||||||
+2
-1
@@ -109,7 +109,8 @@
|
|||||||
{"id": "hostname"},
|
{"id": "hostname"},
|
||||||
{"id": "dns-server"},
|
{"id": "dns-server"},
|
||||||
{"id": "router"},
|
{"id": "router"},
|
||||||
{"id": "netmask"}
|
{"id": "netmask"},
|
||||||
|
{"id": "vendor-class", "value": "Banana Pi BPI-R3"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -68,7 +68,8 @@
|
|||||||
{"id": "hostname"},
|
{"id": "hostname"},
|
||||||
{"id": "dns-server"},
|
{"id": "dns-server"},
|
||||||
{"id": "router"},
|
{"id": "router"},
|
||||||
{"id": "netmask"}
|
{"id": "netmask"},
|
||||||
|
{"id": "vendor-class", "value": "NanoPi R2S"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -47,7 +47,8 @@
|
|||||||
{"id": "domain"},
|
{"id": "domain"},
|
||||||
{"id": "hostname"},
|
{"id": "hostname"},
|
||||||
{"id": "dns-server"},
|
{"id": "dns-server"},
|
||||||
{"id": "ntp-server"}
|
{"id": "ntp-server"},
|
||||||
|
{"id": "vendor-class", "value": "Raspberry Pi 4 Model B"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Deterministically set system hostname from /etc/hostname.d/
|
||||||
|
#
|
||||||
|
# Highest numbered file wins (lexicographic sort, 90-dhcp > 50-configured > 10-default)
|
||||||
|
#
|
||||||
|
# Priority scheme:
|
||||||
|
# 10-default - Bootstrap/factory default (%h-%m format)
|
||||||
|
# 50-configured - From confd /system/hostname
|
||||||
|
# 90-dhcp-<iface> - From DHCP clietn on interface (highest priority)
|
||||||
|
|
||||||
|
HOSTNAME_D="/etc/hostname.d"
|
||||||
|
|
||||||
|
# Ensure directory exists
|
||||||
|
mkdir -p "$HOSTNAME_D"
|
||||||
|
|
||||||
|
# Find the highest priority file (reverse sort, take first)
|
||||||
|
hostname_file=$(ls -1 "$HOSTNAME_D" 2>/dev/null | sort -r | head -1)
|
||||||
|
|
||||||
|
if [ -z "$hostname_file" ]; then
|
||||||
|
logger -it confd "No hostname sources found in $HOSTNAME_D"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read hostname from the file (first line only, strip whitespace)
|
||||||
|
new_hostname=$(cat "$HOSTNAME_D/$hostname_file" | head -1 | tr -d '\n\r\t ')
|
||||||
|
if [ -z "$new_hostname" ]; then
|
||||||
|
logger -it confd "Empty hostname in $hostname_file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#new_hostname} -gt 64 ]; then
|
||||||
|
logger -it confd "Hostname too long (${#new_hostname} > 64) in $hostname_file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if hostname has actually changed
|
||||||
|
current_hostname=$(hostname)
|
||||||
|
if [ "$new_hostname" = "$current_hostname" ]; then
|
||||||
|
# No change needed, exit silently
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set the hostname
|
||||||
|
logger -it confd "Setting hostname to '$new_hostname' from $hostname_file"
|
||||||
|
hostname "$new_hostname"
|
||||||
|
|
||||||
|
# Update /etc/hostname (for persistence across reboots)
|
||||||
|
echo "$new_hostname" > /etc/hostname
|
||||||
|
|
||||||
|
# Update /etc/hosts (127.0.1.1 entry for proper name resolution)
|
||||||
|
if grep -q "^127\.0\.1\.1" /etc/hosts; then
|
||||||
|
sed -i -E "s/^(127\.0\.1\.1\s+).*/\1$new_hostname/" /etc/hosts
|
||||||
|
else
|
||||||
|
# Add entry if it doesn't exist
|
||||||
|
echo "127.0.1.1 $new_hostname" >> /etc/hosts
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Notify services of hostname change, skip while in bootstrap
|
||||||
|
initctl -nbq touch sysklogd
|
||||||
|
if ! runlevel >/dev/null 2>&1; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
initctl -bq status lldpd && lldpcli configure system hostname "$new_hostname" 2>/dev/null
|
||||||
|
initctl -bq status mdns && avahi-set-host-name "$new_hostname" 2>/dev/null
|
||||||
|
initctl -bq touch netbrowse 2>/dev/null
|
||||||
|
|
||||||
|
# If called from dhcp script we need to reload to activate new name in syslogd
|
||||||
|
# Otherwise we're called from confd, which does the reload when all is done.
|
||||||
|
if [ -n "$1" ]; then
|
||||||
|
initctl -b reload
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -146,6 +146,87 @@ class DTSystem:
|
|||||||
flat_devices = [device for sublist in self.infix_devices("vpds") for device in sublist]
|
flat_devices = [device for sublist in self.infix_devices("vpds") for device in sublist]
|
||||||
return [self.into_vpd(device) for device in flat_devices]
|
return [self.into_vpd(device) for device in flat_devices]
|
||||||
|
|
||||||
|
def vendor_name(self):
|
||||||
|
"""Extract vendor name from devicetree compatible property"""
|
||||||
|
compatible = self.base.str_array("compatible")
|
||||||
|
if not compatible or len(compatible) == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Map of common devicetree vendor prefixes to proper names
|
||||||
|
vendor_map = {
|
||||||
|
"raspberrypi": "Raspberry Pi Foundation",
|
||||||
|
"brcm": "Broadcom Inc.",
|
||||||
|
"marvell": "Marvell Technology, Inc.",
|
||||||
|
"fsl": "NXP Semiconductors N.V.",
|
||||||
|
"nxp": "NXP Semiconductors N.V.",
|
||||||
|
"ti": "Texas Instruments Inc.",
|
||||||
|
"qcom": "Qualcomm Inc.",
|
||||||
|
"rockchip": "Rockchip Electronics Co., Ltd.",
|
||||||
|
"amlogic": "Amlogic Inc.",
|
||||||
|
"allwinner": "Allwinner Technology Co., Ltd.",
|
||||||
|
"mediatek": "MediaTek Inc.",
|
||||||
|
"st": "STMicroelectronics N.V.",
|
||||||
|
"xlnx": "Xilinx, Inc.",
|
||||||
|
"intel": "Intel Corporation",
|
||||||
|
"amd": "Advanced Micro Devices, Inc.",
|
||||||
|
"nvidia": "NVIDIA Corporation",
|
||||||
|
"bananapi": "SinoVoip Co., Ltd.",
|
||||||
|
"sinovoip": "SinoVoip Co., Ltd.",
|
||||||
|
"friendlyarm": "FriendlyElec",
|
||||||
|
"friendlyelec": "FriendlyElec",
|
||||||
|
"microchip": "Microchip Technology Inc.",
|
||||||
|
"atmel": "Microchip Technology Inc.",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the first (most specific) compatible string
|
||||||
|
compat = compatible[0]
|
||||||
|
|
||||||
|
# Extract vendor prefix (part before comma)
|
||||||
|
if ',' in compat:
|
||||||
|
prefix = compat.split(',')[0].lower()
|
||||||
|
# Return mapped name or capitalized prefix
|
||||||
|
return vendor_map.get(prefix, prefix.capitalize())
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class DMISystem:
|
||||||
|
BASE = "/sys/class/dmi/id"
|
||||||
|
|
||||||
|
def read_dmi(self, attr):
|
||||||
|
"""Read DMI attribute from /sys/class/dmi/id/"""
|
||||||
|
path = os.path.join(DMISystem.BASE, attr)
|
||||||
|
if not os.path.exists(path):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
value = f.read().strip()
|
||||||
|
return value if value else None
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def populate(self, out):
|
||||||
|
"""Read DMI/SMBIOS data and populate output dictionary"""
|
||||||
|
vendor = self.read_dmi("sys_vendor")
|
||||||
|
if vendor:
|
||||||
|
out["vendor"] = vendor
|
||||||
|
|
||||||
|
product_name = self.read_dmi("product_name")
|
||||||
|
if product_name:
|
||||||
|
out["product-name"] = product_name
|
||||||
|
|
||||||
|
serial = self.read_dmi("product_serial")
|
||||||
|
if serial:
|
||||||
|
out["serial-number"] = serial
|
||||||
|
|
||||||
|
version = self.read_dmi("product_version")
|
||||||
|
if version:
|
||||||
|
out["product-version"] = version
|
||||||
|
|
||||||
|
def vpds(self):
|
||||||
|
"""DMI systems don't have VPD in the traditional sense"""
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
class QEMUSystem:
|
class QEMUSystem:
|
||||||
BASE = "/sys/firmware/qemu_fw_cfg"
|
BASE = "/sys/firmware/qemu_fw_cfg"
|
||||||
@@ -355,6 +436,9 @@ def probe_qemusystem(out):
|
|||||||
if not out[attr]:
|
if not out[attr]:
|
||||||
out[attr] = default
|
out[attr] = default
|
||||||
|
|
||||||
|
if os.path.exists(DMISystem.BASE):
|
||||||
|
DMISystem().populate(out)
|
||||||
|
|
||||||
if not out["factory-password-hash"] and \
|
if not out["factory-password-hash"] and \
|
||||||
not out["vpd"]["product"]["available"]:
|
not out["vpd"]["product"]["available"]:
|
||||||
# Virtual instance without VPD emulation, fallback to
|
# Virtual instance without VPD emulation, fallback to
|
||||||
@@ -397,6 +481,21 @@ def generic_usb_ports(out):
|
|||||||
out["usb-ports"] = [{"name": f"USB{p['num']}", "path": p["path"]} for p in ports]
|
out["usb-ports"] = [{"name": f"USB{p['num']}", "path": p["path"]} for p in ports]
|
||||||
|
|
||||||
|
|
||||||
|
def probe_dmisystem(out):
|
||||||
|
"""Probe DMI/SMBIOS based system (x86/AMD64)"""
|
||||||
|
dmisys = DMISystem()
|
||||||
|
|
||||||
|
dmisys.populate(out)
|
||||||
|
generic_usb_ports(out)
|
||||||
|
|
||||||
|
if not out["mac-address"]:
|
||||||
|
out["mac-address"] = fallback_base_mac()
|
||||||
|
|
||||||
|
vpd_inject(out, dmisys.vpds())
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def probe_dtsystem(out):
|
def probe_dtsystem(out):
|
||||||
"""Probe DTS based system, expects a VPD in ONIE PROM format."""
|
"""Probe DTS based system, expects a VPD in ONIE PROM format."""
|
||||||
dtsys = DTSystem()
|
dtsys = DTSystem()
|
||||||
@@ -413,12 +512,24 @@ def probe_dtsystem(out):
|
|||||||
|
|
||||||
out["compatible"] = dtsys.base.str_array("compatible")
|
out["compatible"] = dtsys.base.str_array("compatible")
|
||||||
|
|
||||||
|
# Extract vendor from compatible string if not already set
|
||||||
|
if not out["vendor"]:
|
||||||
|
vendor = dtsys.vendor_name()
|
||||||
|
if vendor:
|
||||||
|
out["vendor"] = vendor
|
||||||
|
|
||||||
staticpw = dtsys.infix.str("factory-password-hash")
|
staticpw = dtsys.infix.str("factory-password-hash")
|
||||||
if not out["factory-password-hash"]:
|
if not out["factory-password-hash"]:
|
||||||
out["factory-password-hash"] = staticpw
|
out["factory-password-hash"] = staticpw
|
||||||
|
|
||||||
vpd_inject(out, vpds)
|
vpd_inject(out, vpds)
|
||||||
|
|
||||||
|
# Fallback to devicetree serial-number if VPD doesn't provide one
|
||||||
|
if not out["serial-number"]:
|
||||||
|
serial = dtsys.base.str("serial-number")
|
||||||
|
if serial:
|
||||||
|
out["serial-number"] = serial
|
||||||
|
|
||||||
# Fallback to interface MAC if VPD doesn't provide one (e.g., SBCs)
|
# Fallback to interface MAC if VPD doesn't provide one (e.g., SBCs)
|
||||||
if not out["mac-address"]:
|
if not out["mac-address"]:
|
||||||
out["mac-address"] = fallback_base_mac()
|
out["mac-address"] = fallback_base_mac()
|
||||||
@@ -430,6 +541,7 @@ def main():
|
|||||||
out = {
|
out = {
|
||||||
"vendor": None,
|
"vendor": None,
|
||||||
"product-name": None,
|
"product-name": None,
|
||||||
|
"product-version": None,
|
||||||
"part-number": None,
|
"part-number": None,
|
||||||
"serial-number": None,
|
"serial-number": None,
|
||||||
"mac-address": None,
|
"mac-address": None,
|
||||||
@@ -440,6 +552,8 @@ def main():
|
|||||||
|
|
||||||
if os.path.exists(QEMUSystem.REV):
|
if os.path.exists(QEMUSystem.REV):
|
||||||
err = probe_qemusystem(out)
|
err = probe_qemusystem(out)
|
||||||
|
elif os.path.exists(DMISystem.BASE):
|
||||||
|
err = probe_dmisystem(out)
|
||||||
elif os.path.exists(DTSystem.BASE):
|
elif os.path.exists(DTSystem.BASE):
|
||||||
err = probe_dtsystem(out)
|
err = probe_dtsystem(out)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Initialize default hostname for hostname.d pattern
|
||||||
|
# This runs very early in boot to set up the default hostname entry
|
||||||
|
|
||||||
|
HOSTNAME_D="/etc/hostname.d"
|
||||||
|
|
||||||
|
# Ensure directory exists
|
||||||
|
mkdir -p "$HOSTNAME_D"
|
||||||
|
|
||||||
|
# If no default exists yet, create it from /etc/hostname (from squashfs)
|
||||||
|
if [ ! -f "$HOSTNAME_D/10-default" ] && [ -f /etc/hostname ]; then
|
||||||
|
cp /etc/hostname "$HOSTNAME_D/10-default"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Apply hostname using the deterministic helper
|
||||||
|
if [ -x /usr/libexec/infix/hostname ]; then
|
||||||
|
/usr/libexec/infix/hostname
|
||||||
|
fi
|
||||||
@@ -50,6 +50,26 @@ wait_for_ipv6_default_route()
|
|||||||
err "Timed out waiting for IPv6 default route!"
|
err "Timed out waiting for IPv6 default route!"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Check if a DHCP option was requested in the parameter request list
|
||||||
|
# Returns: 0 if requested, 1 if not requested or config unavailable
|
||||||
|
was_option_requested()
|
||||||
|
{
|
||||||
|
local opt_num="$1"
|
||||||
|
local config="/etc/finit.d/available/dhcp-client-${interface}.conf"
|
||||||
|
|
||||||
|
if [ ! -f "$config" ]; then
|
||||||
|
dbg "config file not found: $config"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract udhcpc command line and check for -O <option_num>
|
||||||
|
if grep -q -- "-O ${opt_num}\b" "$config"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# RFC3442: If the DHCP server returns both a Classless
|
# RFC3442: If the DHCP server returns both a Classless
|
||||||
# Static Routes option and a Router option, the DHCP
|
# Static Routes option and a Router option, the DHCP
|
||||||
# client MUST ignore the Router option.
|
# client MUST ignore the Router option.
|
||||||
@@ -57,17 +77,25 @@ set_dhcp_routes()
|
|||||||
{
|
{
|
||||||
echo "! Generated by udhcpc" > "$NEXT"
|
echo "! Generated by udhcpc" > "$NEXT"
|
||||||
if [ -n "$staticroutes" ]; then
|
if [ -n "$staticroutes" ]; then
|
||||||
# format: dest1/mask gw1 ... destn/mask gwn
|
if was_option_requested 121; then
|
||||||
set -- $staticroutes
|
# format: dest1/mask gw1 ... destn/mask gwn
|
||||||
while [ -n "$1" -a -n "$2" ]; do
|
set -- $staticroutes
|
||||||
dbg "adding route $1 via $2 metric $metric tag 100"
|
while [ -n "$1" -a -n "$2" ]; do
|
||||||
echo "ip route $1 $2 $metric tag 100" >> "$NEXT"
|
dbg "adding route $1 via $2 metric $metric tag 100"
|
||||||
shift 2
|
echo "ip route $1 $2 $metric tag 100" >> "$NEXT"
|
||||||
done
|
shift 2
|
||||||
|
done
|
||||||
|
else
|
||||||
|
log "ignoring unrequested staticroutes (option 121)"
|
||||||
|
fi
|
||||||
elif [ -n "$router" ] ; then
|
elif [ -n "$router" ] ; then
|
||||||
for i in $router ; do
|
if was_option_requested 3; then
|
||||||
echo "ip route 0.0.0.0/0 $i $metric tag 100" >> "$NEXT"
|
for i in $router ; do
|
||||||
done
|
echo "ip route 0.0.0.0/0 $i $metric tag 100" >> "$NEXT"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
log "ignoring unrequested router (option 3)"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Reduce changes needed by comparing with previous route(s)
|
# Reduce changes needed by comparing with previous route(s)
|
||||||
@@ -108,6 +136,11 @@ case "$ACTION" in
|
|||||||
# drop info from this interface
|
# drop info from this interface
|
||||||
rm -f "$RESOLV_CONF"
|
rm -f "$RESOLV_CONF"
|
||||||
rm -f "$NTPFILE"
|
rm -f "$NTPFILE"
|
||||||
|
if [ -f "/etc/hostname.d/90-dhcp-${interface}" ]; then
|
||||||
|
log "removing /etc/hostname.d/90-dhcp-${interface}"
|
||||||
|
rm -f "/etc/hostname.d/90-dhcp-${interface}"
|
||||||
|
/usr/libexec/infix/hostname dhcp
|
||||||
|
fi
|
||||||
if [ -x /usr/sbin/avahi-autoipd ]; then
|
if [ -x /usr/sbin/avahi-autoipd ]; then
|
||||||
/usr/sbin/avahi-autoipd -c $interface && /usr/sbin/avahi-autoipd -k $interface
|
/usr/sbin/avahi-autoipd -c $interface && /usr/sbin/avahi-autoipd -k $interface
|
||||||
fi
|
fi
|
||||||
@@ -133,21 +166,35 @@ case "$ACTION" in
|
|||||||
|
|
||||||
set_dhcp_routes
|
set_dhcp_routes
|
||||||
|
|
||||||
# set hostname if given
|
# set hostname if given and requested
|
||||||
if [ -n "$hostname" ]; then
|
if [ -n "$hostname" ]; then
|
||||||
log "setting new hostname: $hostname"
|
if was_option_requested 12; then
|
||||||
hostname "$hostname"
|
log "received DHCP hostname: $hostname"
|
||||||
sed -i -E "s/^(127\.0\.1\.1\s+).*/\1$hostname/" /etc/hosts
|
mkdir -p /etc/hostname.d
|
||||||
|
echo "$hostname" > "/etc/hostname.d/90-dhcp-${interface}"
|
||||||
|
/usr/libexec/infix/hostname dhcp
|
||||||
|
else
|
||||||
|
log "ignoring unrequested hostname (option 12): $hostname"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# drop info from this interface
|
# drop info from this interface
|
||||||
truncate -s 0 "$RESOLV_CONF"
|
truncate -s 0 "$RESOLV_CONF"
|
||||||
|
|
||||||
# prefer rfc3397 domain search list (option 119) if available
|
# prefer rfc3397 domain search list (option 119) if available
|
||||||
|
search_list=""
|
||||||
if [ -n "$search" ]; then
|
if [ -n "$search" ]; then
|
||||||
search_list=$search
|
if was_option_requested 119; then
|
||||||
|
search_list=$search
|
||||||
|
else
|
||||||
|
log "ignoring unrequested search (option 119): $search"
|
||||||
|
fi
|
||||||
elif [ -n "$domain" ]; then
|
elif [ -n "$domain" ]; then
|
||||||
search_list=$domain
|
if was_option_requested 15; then
|
||||||
|
search_list=$domain
|
||||||
|
else
|
||||||
|
log "ignoring unrequested domain (option 15): $domain"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$search_list" ]; then
|
if [ -n "$search_list" ]; then
|
||||||
@@ -155,19 +202,29 @@ case "$ACTION" in
|
|||||||
echo "search $search_list # $interface" >> $RESOLV_CONF
|
echo "search $search_list # $interface" >> $RESOLV_CONF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for i in $dns ; do
|
if [ -n "$dns" ]; then
|
||||||
dbg "adding dns $i"
|
if was_option_requested 6; then
|
||||||
echo "nameserver $i # $interface" >> $RESOLV_CONF
|
for i in $dns ; do
|
||||||
resolvconf -u
|
dbg "adding dns $i"
|
||||||
done
|
echo "nameserver $i # $interface" >> $RESOLV_CONF
|
||||||
|
resolvconf -u
|
||||||
|
done
|
||||||
|
else
|
||||||
|
log "ignoring unrequested dns (option 6): $dns"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -n "$ntpsrv" ]; then
|
if [ -n "$ntpsrv" ]; then
|
||||||
truncate -s 0 "$NTPFILE"
|
if was_option_requested 42; then
|
||||||
for srv in $ntpsrv; do
|
truncate -s 0 "$NTPFILE"
|
||||||
dbg "got NTP server $srv"
|
for srv in $ntpsrv; do
|
||||||
echo "server $srv iburst" >> "$NTPFILE"
|
dbg "got NTP server $srv"
|
||||||
done
|
echo "server $srv iburst" >> "$NTPFILE"
|
||||||
chronyc reload sources >/dev/null
|
done
|
||||||
|
chronyc reload sources >/dev/null
|
||||||
|
else
|
||||||
|
log "ignoring unrequested ntpsrv (option 42): $ntpsrv"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -52,11 +52,13 @@ All notable changes to the project are documented in this file.
|
|||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Fix #855: User admin sometimes fails to be added to `wheel` group
|
- Fix #855: User admin sometimes fails to be added to `wheel` group
|
||||||
|
- Fix #1112: setting hostname via DHCP client sometimes gets overridden by the
|
||||||
|
configured system hostname
|
||||||
- Fix #1247: Prevent invalid configuration of OSPF backbone area (0.0.0.0) as
|
- Fix #1247: Prevent invalid configuration of OSPF backbone area (0.0.0.0) as
|
||||||
stub or NSSA. The backbone must always be a normal area per RFC 2328. Any
|
stub or NSSA. The backbone must always be a normal area per RFC 2328. Any
|
||||||
existing invalid configurations are automatically corrected during upgrade
|
existing invalid configurations are automatically corrected during upgrade
|
||||||
- Fix serious regression in boot time, introduced in v25.10, delays the
|
- Fix #1255: serious regression in boot time, introduced in v25.10, delays the
|
||||||
boot step "Mounting filesystems ..." with up to 30 seconds!
|
boot step "Mounting filesystems ...", from 30 seconds up to five minutes!
|
||||||
- Fix broken intra-document links in container and tunnel documentation
|
- Fix broken intra-document links in container and tunnel documentation
|
||||||
|
|
||||||
[lastest-boot]: https://github.com/kernelkit/infix/releases/latest-boot
|
[lastest-boot]: https://github.com/kernelkit/infix/releases/latest-boot
|
||||||
|
|||||||
+22
-73
@@ -206,11 +206,6 @@ done:
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sys_reload_services(void)
|
|
||||||
{
|
|
||||||
return systemf("initctl -nbq touch sysklogd");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#define TIMEZONE_CONF "/etc/timezone"
|
#define TIMEZONE_CONF "/etc/timezone"
|
||||||
#define TIMEZONE_PREV TIMEZONE_CONF "-"
|
#define TIMEZONE_PREV TIMEZONE_CONF "-"
|
||||||
@@ -1558,11 +1553,10 @@ int hostnamefmt(struct confd *confd, const char *fmt, char *hostnm, size_t hostl
|
|||||||
|
|
||||||
static int change_hostname(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd)
|
static int change_hostname(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd)
|
||||||
{
|
{
|
||||||
const char *hostip = "127.0.1.1";
|
|
||||||
char hostnm[65], domain[65];
|
char hostnm[65], domain[65];
|
||||||
char buf[256], *fmt;
|
int rc = SR_ERR_OK;
|
||||||
FILE *nfp, *fp;
|
char *fmt;
|
||||||
int err, fd;
|
FILE *fp;
|
||||||
|
|
||||||
if (event != SR_EV_DONE || !lydx_get_xpathf(diff, XPATH_HOSTNAME_))
|
if (event != SR_EV_DONE || !lydx_get_xpathf(diff, XPATH_HOSTNAME_))
|
||||||
return SR_ERR_OK;
|
return SR_ERR_OK;
|
||||||
@@ -1572,82 +1566,36 @@ static int change_hostname(sr_session_ctx_t *session, struct lyd_node *config, s
|
|||||||
fmt = strdup(nm);
|
fmt = strdup(nm);
|
||||||
|
|
||||||
if (hostnamefmt(confd, fmt, hostnm, sizeof(hostnm), domain, sizeof(domain))) {
|
if (hostnamefmt(confd, fmt, hostnm, sizeof(hostnm), domain, sizeof(domain))) {
|
||||||
err = SR_ERR_SYS;
|
rc = SR_ERR_SYS;
|
||||||
goto err;
|
goto failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = sethostname(hostnm, strlen(hostnm));
|
/* Use hostname.d for deterministic hostname management */
|
||||||
if (err) {
|
systemf("mkdir -p /etc/hostname.d");
|
||||||
ERROR("failed setting hostname");
|
|
||||||
err = SR_ERR_SYS;
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
fp = fopen("/etc/hostname.d/50-configured", "w");
|
||||||
|
if (!fp)
|
||||||
|
goto failed;
|
||||||
|
|
||||||
|
fprintf(fp, "%s\n", hostnm);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
/* Handle domain name if present */
|
||||||
if (domain[0] && setdomainname(domain, strlen(domain))) {
|
if (domain[0] && setdomainname(domain, strlen(domain))) {
|
||||||
ERROR("failed setting domain name");
|
ERROR("failed setting domain name");
|
||||||
/* Not cause for failing this function */
|
/* Not cause for failing this function */
|
||||||
}
|
}
|
||||||
|
|
||||||
fp = fopen(_PATH_HOSTNAME, "w");
|
if (systemf("/usr/libexec/infix/hostname")) {
|
||||||
if (!fp) {
|
failed:
|
||||||
err = SR_ERR_INTERNAL;
|
ERROR("failed setting hostname");
|
||||||
goto err;
|
rc = SR_ERR_SYS;
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(fp, "%s\n", hostnm);
|
|
||||||
fclose(fp);
|
|
||||||
|
|
||||||
nfp = fopen(_PATH_HOSTS "+", "w");
|
|
||||||
if (!nfp) {
|
|
||||||
err = SR_ERR_INTERNAL;
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
fd = fileno(nfp);
|
|
||||||
if (fd == -1 || fchown(fd, 0, 0) || fchmod(fd, 0644)) {
|
|
||||||
fclose(nfp);
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
fp = fopen(_PATH_HOSTS, "r");
|
|
||||||
if (!fp) {
|
|
||||||
err = SR_ERR_INTERNAL;
|
|
||||||
fclose(nfp);
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (fgets(buf, sizeof(buf), fp)) {
|
|
||||||
if (!strncmp(buf, hostip, strlen(hostip))) {
|
|
||||||
if (domain[0])
|
|
||||||
snprintf(buf, sizeof(buf), "%s\t%s.%s %s\n", hostip, hostnm, domain, hostnm);
|
|
||||||
else
|
|
||||||
snprintf(buf, sizeof(buf), "%s\t%s\n", hostip, hostnm);
|
|
||||||
}
|
|
||||||
fputs(buf, nfp);
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(fp);
|
|
||||||
fclose(nfp);
|
|
||||||
if (rename(_PATH_HOSTS "+", _PATH_HOSTS))
|
|
||||||
ERRNO("Failed activating changes to "_PATH_HOSTS);
|
|
||||||
|
|
||||||
/* skip in bootstrap, lldpd and avahi have not started yet */
|
|
||||||
if (systemf("runlevel >/dev/null 2>&1"))
|
|
||||||
goto err;
|
|
||||||
|
|
||||||
/* Inform any running lldpd and avahi of the change ... */
|
|
||||||
systemf("initctl -bq status lldpd && lldpcli configure system hostname %s", hostnm);
|
|
||||||
systemf("initctl -bq status mdns && avahi-set-host-name %s", hostnm);
|
|
||||||
systemf("initctl -bq touch netbrowse");
|
|
||||||
err:
|
|
||||||
if (fmt)
|
if (fmt)
|
||||||
free(fmt);
|
free(fmt);
|
||||||
|
if (rc)
|
||||||
if (err) {
|
return rc;
|
||||||
ERROR("Failed activating changes.");
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
if (sys_reload_services())
|
|
||||||
return SR_ERR_SYS;
|
|
||||||
|
|
||||||
return SR_ERR_OK;
|
return SR_ERR_OK;
|
||||||
}
|
}
|
||||||
@@ -1656,6 +1604,7 @@ err:
|
|||||||
int ietf_system_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd)
|
int ietf_system_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd)
|
||||||
{
|
{
|
||||||
int rc = SR_ERR_OK;
|
int rc = SR_ERR_OK;
|
||||||
|
|
||||||
if ((rc = change_auth(session, config, diff, event, confd)))
|
if ((rc = change_auth(session, config, diff, event, confd)))
|
||||||
return rc;
|
return rc;
|
||||||
if ((rc = change_ntp(session, config, diff, event, confd)))
|
if ((rc = change_ntp(session, config, diff, event, confd)))
|
||||||
|
|||||||
@@ -50,6 +50,20 @@ static char *ip_cache(const char *ifname, char *str, size_t len)
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *get_vendor_class(struct lyd_node *cfg)
|
||||||
|
{
|
||||||
|
struct lyd_node *option;
|
||||||
|
|
||||||
|
LYX_LIST_FOR_EACH(lyd_child(cfg), option, "option") {
|
||||||
|
struct lyd_node *id = lydx_get_child(option, "id");
|
||||||
|
const char *name = lyd_get_value(id);
|
||||||
|
|
||||||
|
if (strcmp(name, "vendor-class") == 0 || strcmp(name, "60") == 0)
|
||||||
|
return lydx_get_cattr(option, "value");
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static char *fallback_options(const char *ifname)
|
static char *fallback_options(const char *ifname)
|
||||||
{
|
{
|
||||||
@@ -84,9 +98,11 @@ static void add(const char *ifname, struct lyd_node *cfg)
|
|||||||
{
|
{
|
||||||
const char *metric = lydx_get_cattr(cfg, "route-preference");
|
const char *metric = lydx_get_cattr(cfg, "route-preference");
|
||||||
const char *client_id = lydx_get_cattr(cfg, "client-id");
|
const char *client_id = lydx_get_cattr(cfg, "client-id");
|
||||||
char vendor[128] = { 0 }, do_arp[20] = { 0 };
|
|
||||||
char *cid = NULL, *options = NULL;
|
char *cid = NULL, *options = NULL;
|
||||||
const char *action = "disable";
|
const char *action = "disable";
|
||||||
|
const char *vendor_class;
|
||||||
|
char vendor[128] = { 0 };
|
||||||
|
char do_arp[20] = { 0 };
|
||||||
bool arping;
|
bool arping;
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
|
|
||||||
@@ -112,7 +128,12 @@ static void add(const char *ifname, struct lyd_node *cfg)
|
|||||||
|
|
||||||
options = dhcp_options(ifname, cfg);
|
options = dhcp_options(ifname, cfg);
|
||||||
|
|
||||||
dhcp_os_name_version(vendor, sizeof(vendor));
|
/* Check for vendor-class option (option 60) */
|
||||||
|
vendor_class = get_vendor_class(cfg);
|
||||||
|
if (vendor_class)
|
||||||
|
snprintf(vendor, sizeof(vendor), "-V \"%s\"", vendor_class);
|
||||||
|
else
|
||||||
|
snprintf(vendor, sizeof(vendor), "-V \"\"");
|
||||||
|
|
||||||
fp = fopenf("w", "/etc/finit.d/available/dhcp-client-%s.conf", ifname);
|
fp = fopenf("w", "/etc/finit.d/available/dhcp-client-%s.conf", ifname);
|
||||||
if (!fp) {
|
if (!fp) {
|
||||||
|
|||||||
@@ -97,33 +97,6 @@ char *dhcp_fqdn(const char *val, char *str, size_t len)
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *dhcp_os_name_version(char *str, size_t len)
|
|
||||||
{
|
|
||||||
char *val;
|
|
||||||
|
|
||||||
if (!str || !len)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
str[0] = 0;
|
|
||||||
|
|
||||||
val = fgetkey("/etc/os-release", "NAME");
|
|
||||||
if (val)
|
|
||||||
snprintf(str, len, "-V \"%.32s ", val);
|
|
||||||
|
|
||||||
val = fgetkey("/etc/os-release", "VERSION");
|
|
||||||
if (val) {
|
|
||||||
strlcat(str, val, len);
|
|
||||||
strlcat(str, "\"", len);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strlen(str) > 0 && str[strlen(str) - 1] != '"') {
|
|
||||||
str[0] = 0;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *dhcp_compose_option(struct lyd_node *cfg, const char *ifname, struct lyd_node *id,
|
char *dhcp_compose_option(struct lyd_node *cfg, const char *ifname, struct lyd_node *id,
|
||||||
const char *val, const char *hex, char *option, size_t len,
|
const char *val, const char *hex, char *option, size_t len,
|
||||||
char *(*ip_cache_cb)(const char *, char *, size_t))
|
char *(*ip_cache_cb)(const char *, char *, size_t))
|
||||||
@@ -136,6 +109,10 @@ char *dhcp_compose_option(struct lyd_node *cfg, const char *ifname, struct lyd_n
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Skip option 60 (vendor-class) - handled separately via -V flag */
|
||||||
|
if (num == 60)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
if (val || hex) {
|
if (val || hex) {
|
||||||
switch (num) {
|
switch (num) {
|
||||||
case 81: /* fqdn */
|
case 81: /* fqdn */
|
||||||
@@ -210,6 +187,8 @@ char *dhcp_compose_options(struct lyd_node *cfg, const char *ifname, char **opti
|
|||||||
*/
|
*/
|
||||||
static void infer_options_v4(sr_session_ctx_t *session, const char *xpath)
|
static void infer_options_v4(sr_session_ctx_t *session, const char *xpath)
|
||||||
{
|
{
|
||||||
|
sr_val_t val = { .type = SR_STRING_T };
|
||||||
|
struct json_t *product_name;
|
||||||
const char *opt[] = {
|
const char *opt[] = {
|
||||||
"netmask",
|
"netmask",
|
||||||
"broadcast",
|
"broadcast",
|
||||||
@@ -223,6 +202,12 @@ static void infer_options_v4(sr_session_ctx_t *session, const char *xpath)
|
|||||||
|
|
||||||
for (i = 0; i < NELEMS(opt); i++)
|
for (i = 0; i < NELEMS(opt); i++)
|
||||||
srx_set_item(session, NULL, 0, "%s/option[id='%s']", xpath, opt[i]);
|
srx_set_item(session, NULL, 0, "%s/option[id='%s']", xpath, opt[i]);
|
||||||
|
|
||||||
|
product_name = json_object_get(confd.root, "product-name");
|
||||||
|
if (product_name) {
|
||||||
|
val.data.string_val = (char *)json_string_value(product_name);
|
||||||
|
srx_set_item(session, &val, 0, "%s/option[id='vendor-class']/value", xpath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
int dhcp_option_lookup(const struct lyd_node *id);
|
int dhcp_option_lookup(const struct lyd_node *id);
|
||||||
char *dhcp_hostname(struct lyd_node *cfg, char *str, size_t len);
|
char *dhcp_hostname(struct lyd_node *cfg, char *str, size_t len);
|
||||||
char *dhcp_fqdn(const char *val, char *str, size_t len);
|
char *dhcp_fqdn(const char *val, char *str, size_t len);
|
||||||
char *dhcp_os_name_version(char *str, size_t len);
|
|
||||||
char *dhcp_compose_option(struct lyd_node *cfg, const char *ifname, struct lyd_node *id,
|
char *dhcp_compose_option(struct lyd_node *cfg, const char *ifname, struct lyd_node *id,
|
||||||
const char *val, const char *hex, char *option, size_t len,
|
const char *val, const char *hex, char *option, size_t len,
|
||||||
char *(*ip_cache_cb)(const char *, char *, size_t));
|
char *(*ip_cache_cb)(const char *, char *, size_t));
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ module infix-dhcp-common {
|
|||||||
description "Shared types between DHCP server and client.";
|
description "Shared types between DHCP server and client.";
|
||||||
|
|
||||||
revision 2025-11-09 {
|
revision 2025-11-09 {
|
||||||
description "Add DHCPv6 options typedef.";
|
description "Add DHCPv6 options typedef and DHCPv4 vendor-class option.";
|
||||||
reference "internal";
|
reference "internal";
|
||||||
}
|
}
|
||||||
revision 2025-01-29 {
|
revision 2025-01-29 {
|
||||||
@@ -79,6 +79,10 @@ module infix-dhcp-common {
|
|||||||
value 50;
|
value 50;
|
||||||
description "Request (previously cached) address";
|
description "Request (previously cached) address";
|
||||||
}
|
}
|
||||||
|
enum vendor-class {
|
||||||
|
value 60;
|
||||||
|
description "Vendor class, e.g., product name, type/model";
|
||||||
|
}
|
||||||
enum client-id {
|
enum client-id {
|
||||||
value 61;
|
value 61;
|
||||||
description "Client ID (default MAC, and option 12)";
|
description "Client ID (default MAC, and option 12)";
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Tests verifying IETF standard interface configuration and management:
|
|||||||
- Interface enable/disable state management
|
- Interface enable/disable state management
|
||||||
- Virtual Ethernet (VETH) pair lifecycle management
|
- Virtual Ethernet (VETH) pair lifecycle management
|
||||||
- GRE tunneling configuration and data plane operation
|
- GRE tunneling configuration and data plane operation
|
||||||
|
- Tunnel TTL (Time-To-Live) and hop-limit verification
|
||||||
|
|
||||||
include::vlan_ping/Readme.adoc[]
|
include::vlan_ping/Readme.adoc[]
|
||||||
|
|
||||||
@@ -125,3 +126,7 @@ include::tunnel_basic/Readme.adoc[]
|
|||||||
<<<
|
<<<
|
||||||
|
|
||||||
include::tunnel_bridged/Readme.adoc[]
|
include::tunnel_bridged/Readme.adoc[]
|
||||||
|
|
||||||
|
<<<
|
||||||
|
|
||||||
|
include::tunnel_ttl/Readme.adoc[]
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ include::volume/Readme.adoc[]
|
|||||||
|
|
||||||
<<<
|
<<<
|
||||||
|
|
||||||
|
include::upgrade/Readme.adoc[]
|
||||||
|
|
||||||
|
<<<
|
||||||
|
|
||||||
include::firewall_basic/Readme.adoc[]
|
include::firewall_basic/Readme.adoc[]
|
||||||
|
|
||||||
<<<
|
<<<
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
:testgroup:
|
:testgroup:
|
||||||
== infix-dhcp
|
== infix-dhcp
|
||||||
|
|
||||||
Tests verifying DHCP client and server functionality in Infix:
|
Tests verifying DHCPv4/DHCPv6 client and server functionality in Infix:
|
||||||
|
|
||||||
- Basic DHCP client lease acquisition and configuration
|
- Basic DHCPv4 client lease acquisition and configuration
|
||||||
- DHCP client with default gateway assignment
|
- DHCPv4 client with default gateway assignment
|
||||||
- DHCP client with static route configuration
|
- DHCPv4 client with static route configuration
|
||||||
- Basic DHCP server operation and lease assignment
|
- DHCPv4 client hostname management and priority
|
||||||
- DHCP server with host-specific IP reservations
|
- Basic DHCPv6 client operation with address assignment
|
||||||
- DHCP server with multiple subnet configurations
|
- DHCPv6 client with prefix delegation (IA_PD)
|
||||||
|
- Basic DHCPv4 server operation and lease assignment
|
||||||
|
- DHCPv4 server with host-specific IP reservations
|
||||||
|
- DHCPv4 server with multiple subnet configurations
|
||||||
|
|
||||||
include::client_basic/Readme.adoc[]
|
include::client_basic/Readme.adoc[]
|
||||||
|
|
||||||
@@ -22,6 +25,18 @@ include::client_routes/Readme.adoc[]
|
|||||||
|
|
||||||
<<<
|
<<<
|
||||||
|
|
||||||
|
include::client_hostname/Readme.adoc[]
|
||||||
|
|
||||||
|
<<<
|
||||||
|
|
||||||
|
include::client6_basic/Readme.adoc[]
|
||||||
|
|
||||||
|
<<<
|
||||||
|
|
||||||
|
include::client6_prefix_delegation/Readme.adoc[]
|
||||||
|
|
||||||
|
<<<
|
||||||
|
|
||||||
include::server_basic/Readme.adoc[]
|
include::server_basic/Readme.adoc[]
|
||||||
|
|
||||||
<<<
|
<<<
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
test.adoc
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
=== DHCP Hostname Priority
|
||||||
|
|
||||||
|
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/infix_dhcp/client_hostname]
|
||||||
|
|
||||||
|
==== Description
|
||||||
|
|
||||||
|
Verify deterministic hostname management: a DHCP acquired hostname takes
|
||||||
|
precedence over a configured hostname. When a DHCP lease ends, or the
|
||||||
|
hostname option is removed, the system should revert to the configured
|
||||||
|
hostname.
|
||||||
|
|
||||||
|
==== Topology
|
||||||
|
|
||||||
|
image::topology.svg[DHCP Hostname Priority topology, align=center, scaledwidth=75%]
|
||||||
|
|
||||||
|
==== Sequence
|
||||||
|
|
||||||
|
. Set up topology and attach to target DUT
|
||||||
|
. Configure static system hostname
|
||||||
|
. Verify configured hostname is set
|
||||||
|
. Enable DHCP client requesting hostname option
|
||||||
|
. Verify DHCP hostname takes precedence
|
||||||
|
. Drop hostname option from client request
|
||||||
|
. Verify hostname reverts to configured value
|
||||||
|
|
||||||
|
|
||||||
Executable
+74
@@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""DHCP Hostname Priority
|
||||||
|
|
||||||
|
Verify deterministic hostname management: a DHCP acquired hostname takes
|
||||||
|
precedence over a configured hostname. When a DHCP lease ends, or the
|
||||||
|
hostname option is removed, the system should revert to the configured
|
||||||
|
hostname.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import infamy, infamy.dhcp
|
||||||
|
from infamy.util import until
|
||||||
|
|
||||||
|
|
||||||
|
def verify_hostname(node, expected):
|
||||||
|
"""Verify operational hostname matches expected value"""
|
||||||
|
data = node.get_data("/ietf-system:system")
|
||||||
|
return data["system"]["hostname"] == expected
|
||||||
|
|
||||||
|
|
||||||
|
with infamy.Test() as test:
|
||||||
|
DHCP_HOSTNAME = "dhcp-assigned"
|
||||||
|
CONF_HOSTNAME = "configured-host"
|
||||||
|
|
||||||
|
with test.step("Set up topology and attach to target DUT"):
|
||||||
|
env = infamy.Env()
|
||||||
|
client = env.attach("client", "mgmt")
|
||||||
|
_, host = env.ltop.xlate("host", "mgmt")
|
||||||
|
_, port = env.ltop.xlate("client", "mgmt")
|
||||||
|
|
||||||
|
with test.step("Configure static system hostname"):
|
||||||
|
client.put_config_dict("ietf-system", {
|
||||||
|
"system": {
|
||||||
|
"hostname": CONF_HOSTNAME
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
with test.step("Verify configured hostname is set"):
|
||||||
|
until(lambda: verify_hostname(client, CONF_HOSTNAME))
|
||||||
|
|
||||||
|
with infamy.IsolatedMacVlan(host, mode="private") as netns:
|
||||||
|
netns.addip("10.0.0.1")
|
||||||
|
with infamy.dhcp.Server(netns, ip="10.0.0.42", hostname=DHCP_HOSTNAME):
|
||||||
|
with test.step("Enable DHCP client requesting hostname option"):
|
||||||
|
client.put_config_dict("ietf-interfaces", {
|
||||||
|
"interfaces": {
|
||||||
|
"interface": [{
|
||||||
|
"name": port,
|
||||||
|
"ipv4": {
|
||||||
|
"infix-dhcp-client:dhcp": {
|
||||||
|
"option": [
|
||||||
|
{"id": "vendor-class", "value": "infamy"},
|
||||||
|
{"id": "hostname"},
|
||||||
|
{"id": "netmask"},
|
||||||
|
{"id": "router"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
with test.step("Verify DHCP hostname takes precedence"):
|
||||||
|
until(lambda: verify_hostname(client, DHCP_HOSTNAME))
|
||||||
|
|
||||||
|
with test.step("Drop hostname option from client request"):
|
||||||
|
path = f"/ietf-interfaces:interfaces/interface[name='{port}']" \
|
||||||
|
+ "/ietf-ip:ipv4/infix-dhcp-client:dhcp/option[id='hostname']"
|
||||||
|
client.delete_xpath(path)
|
||||||
|
|
||||||
|
with test.step("Verify hostname reverts to configured value"):
|
||||||
|
until(lambda: verify_hostname(client, CONF_HOSTNAME))
|
||||||
|
|
||||||
|
test.succeed()
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
graph "1x1" {
|
||||||
|
layout="neato";
|
||||||
|
overlap="false";
|
||||||
|
esep="+100";
|
||||||
|
|
||||||
|
node [shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||||
|
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||||
|
|
||||||
|
host [
|
||||||
|
label="host | { <mgmt> mgmt }",
|
||||||
|
pos="0,20!",
|
||||||
|
requires="controller",
|
||||||
|
];
|
||||||
|
|
||||||
|
client [
|
||||||
|
label="{ <mgmt> mgmt } | client",
|
||||||
|
pos="200,20!",
|
||||||
|
requires="infix",
|
||||||
|
];
|
||||||
|
|
||||||
|
host:mgmt -- client:mgmt [requires="mgmt", color=lightgrey]
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
|
||||||
|
<!-- Title: 1x1 Pages: 1 -->
|
||||||
|
<svg width="474pt" height="45pt"
|
||||||
|
viewBox="0.00 0.00 474.04 45.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 41)">
|
||||||
|
<title>1x1</title>
|
||||||
|
<polygon fill="white" stroke="transparent" points="-4,4 -4,-41 470.04,-41 470.04,4 -4,4"/>
|
||||||
|
<!-- host -->
|
||||||
|
<g id="node1" class="node">
|
||||||
|
<title>host</title>
|
||||||
|
<polygon fill="none" stroke="black" points="0,-0.5 0,-36.5 100,-36.5 100,-0.5 0,-0.5"/>
|
||||||
|
<text text-anchor="middle" x="25" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||||
|
<polyline fill="none" stroke="black" points="50,-0.5 50,-36.5 "/>
|
||||||
|
<text text-anchor="middle" x="75" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||||
|
</g>
|
||||||
|
<!-- client -->
|
||||||
|
<g id="node2" class="node">
|
||||||
|
<title>client</title>
|
||||||
|
<polygon fill="none" stroke="black" points="350.04,-0.5 350.04,-36.5 466.04,-36.5 466.04,-0.5 350.04,-0.5"/>
|
||||||
|
<text text-anchor="middle" x="375.04" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||||
|
<polyline fill="none" stroke="black" points="400.04,-0.5 400.04,-36.5 "/>
|
||||||
|
<text text-anchor="middle" x="433.04" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">client</text>
|
||||||
|
</g>
|
||||||
|
<!-- host--client -->
|
||||||
|
<g id="edge1" class="edge">
|
||||||
|
<title>host:mgmt--client:mgmt</title>
|
||||||
|
<path fill="none" stroke="lightgrey" stroke-width="2" d="M100,-18.5C100,-18.5 350.04,-18.5 350.04,-18.5"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -11,6 +11,9 @@
|
|||||||
- name: DHCP option 121 vs option 3
|
- name: DHCP option 121 vs option 3
|
||||||
case: client_routes/test.py
|
case: client_routes/test.py
|
||||||
|
|
||||||
|
- name: DHCP Hostname Priority
|
||||||
|
case: client_hostname/test.py
|
||||||
|
|
||||||
- name: DHCPv6 Basic
|
- name: DHCPv6 Basic
|
||||||
case: client6_basic/test.py
|
case: client6_basic/test.py
|
||||||
|
|
||||||
|
|||||||
@@ -228,6 +228,7 @@ if [ "$containerize" ]; then
|
|||||||
--cap-add=NET_ADMIN \
|
--cap-add=NET_ADMIN \
|
||||||
--device=/dev/net/tun \
|
--device=/dev/net/tun \
|
||||||
--env PYTHONHASHSEED=${PYTHONHASHSEED:-$(shuf -i 0-$(((1 << 32) - 1)) -n 1)} \
|
--env PYTHONHASHSEED=${PYTHONHASHSEED:-$(shuf -i 0-$(((1 << 32) - 1)) -n 1)} \
|
||||||
|
--env PYTHONPYCACHEDIR=/tmp/__pycache__ \
|
||||||
--env VIRTUAL_ENV_DISABLE_PROMPT=yes \
|
--env VIRTUAL_ENV_DISABLE_PROMPT=yes \
|
||||||
--env INFAMY_ARGS="$INFAMY_ARGS" \
|
--env INFAMY_ARGS="$INFAMY_ARGS" \
|
||||||
--env INFAMY_EXTRA_ARGS="$INFAMY_EXTRA_ARGS" \
|
--env INFAMY_EXTRA_ARGS="$INFAMY_EXTRA_ARGS" \
|
||||||
|
|||||||
+7
-3
@@ -8,11 +8,13 @@ class Server:
|
|||||||
config_file = '/tmp/udhcpd.conf'
|
config_file = '/tmp/udhcpd.conf'
|
||||||
leases_file = '/tmp/udhcpd.leases'
|
leases_file = '/tmp/udhcpd.leases'
|
||||||
|
|
||||||
def __init__(self, netns, start='192.168.0.100', end='192.168.0.110', netmask='255.255.255.0', ip=None, router=None, prefix=None, iface="iface"):
|
def __init__(self, netns, start='192.168.0.100', end='192.168.0.110',
|
||||||
|
netmask='255.255.255.0', ip=None, router=None, prefix=None,
|
||||||
|
hostname=None, iface="iface"):
|
||||||
self.process = None
|
self.process = None
|
||||||
self.netns = netns
|
self.netns = netns
|
||||||
self.iface = iface
|
self.iface = iface
|
||||||
self._create_files(start, end, netmask, ip, router, prefix)
|
self._create_files(start, end, netmask, ip, router, prefix, hostname)
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
#print(self.config_file)
|
#print(self.config_file)
|
||||||
@@ -26,7 +28,7 @@ class Server:
|
|||||||
def __exit__(self, _, __, ___):
|
def __exit__(self, _, __, ___):
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
def _create_files(self, start, end, netmask, ip, router, prefix):
|
def _create_files(self, start, end, netmask, ip, router, prefix, hostname):
|
||||||
f = open(self.leases_file, "w")
|
f = open(self.leases_file, "w")
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
@@ -47,6 +49,8 @@ option lease 864000
|
|||||||
f.write(f"option router {router}\n")
|
f.write(f"option router {router}\n")
|
||||||
if prefix and router:
|
if prefix and router:
|
||||||
f.write(f"option staticroutes {prefix} {router}\n")
|
f.write(f"option staticroutes {prefix} {router}\n")
|
||||||
|
if hostname:
|
||||||
|
f.write(f"option hostname {hostname}\n")
|
||||||
|
|
||||||
def get_pid(self):
|
def get_pid(self):
|
||||||
return self.process.pid
|
return self.process.pid
|
||||||
|
|||||||
+46
-7
@@ -28,6 +28,17 @@ class IsolatedMacVlans:
|
|||||||
NOTE: For the simple case when only one interface needs to be
|
NOTE: For the simple case when only one interface needs to be
|
||||||
mapped, see IsolatedMacVlan below.
|
mapped, see IsolatedMacVlan below.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ifmap: Dictionary mapping parent interface names to MACVLAN names
|
||||||
|
lo: Enable loopback interface in the namespace (default: True)
|
||||||
|
set_up: Automatically bring up the interfaces (default: True)
|
||||||
|
mode: MACVLAN mode to use (default: "passthru")
|
||||||
|
- "passthru": Exclusive access, single MACVLAN per parent.
|
||||||
|
Parent interface becomes promiscuous.
|
||||||
|
- "bridge": Shared access, allows multiple MACVLANs to
|
||||||
|
communicate. Required for layer-2 tests that
|
||||||
|
need full control of all frames.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
netns = IsolatedMacVlans({ "eth2": "a", "eth3": "b" })
|
netns = IsolatedMacVlans({ "eth2": "a", "eth3": "b" })
|
||||||
@@ -46,9 +57,10 @@ class IsolatedMacVlans:
|
|||||||
for ns in list(IsolatedMacVlans.Instances):
|
for ns in list(IsolatedMacVlans.Instances):
|
||||||
ns.stop()
|
ns.stop()
|
||||||
|
|
||||||
def __init__(self, ifmap, lo=True, set_up=True):
|
def __init__(self, ifmap, lo=True, set_up=True, mode="passthru"):
|
||||||
self.sleeper = None
|
self.sleeper = None
|
||||||
self.ifmap, self.lo, self.set_up = ifmap, lo, set_up
|
self.ifmap, self.lo, self.set_up = ifmap, lo, set_up
|
||||||
|
self.mode = mode
|
||||||
self.ping_timeout = env.ENV.attr("ping_timeout", 5)
|
self.ping_timeout = env.ENV.attr("ping_timeout", 5)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
@@ -64,7 +76,8 @@ class IsolatedMacVlans:
|
|||||||
"link", parent,
|
"link", parent,
|
||||||
"address", self._stable_mac(parent),
|
"address", self._stable_mac(parent),
|
||||||
"netns", str(self.sleeper.pid),
|
"netns", str(self.sleeper.pid),
|
||||||
"type", "macvlan", "mode", "passthru"], check=True)
|
"type", "macvlan", "mode", self.mode],
|
||||||
|
check=True)
|
||||||
self.runsh(f"""
|
self.runsh(f"""
|
||||||
while ! ip link show dev {ifname}; do
|
while ! ip link show dev {ifname}; do
|
||||||
sleep 0.1
|
sleep 0.1
|
||||||
@@ -287,6 +300,17 @@ class IsolatedMacVlan(IsolatedMacVlans):
|
|||||||
moves that interface to a separate namespace, isolating it from
|
moves that interface to a separate namespace, isolating it from
|
||||||
all other interfaces.
|
all other interfaces.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent: Name of the parent interface on the controller
|
||||||
|
ifname: Name of the MACVLAN interface in the namespace (default: "iface")
|
||||||
|
lo: Enable loopback interface in the namespace (default: True)
|
||||||
|
set_up: Automatically bring up the interface (default: True)
|
||||||
|
mode: MACVLAN mode to use (default: "passthru")
|
||||||
|
- "passthru": Exclusive access, single MACVLAN per parent.
|
||||||
|
- "bridge": Shared access, required for layer-2 tests that
|
||||||
|
need to communicate with other MACVLANs on the
|
||||||
|
same parent or control all frames.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
netns = IsolatedMacVlan("eth3")
|
netns = IsolatedMacVlan("eth3")
|
||||||
@@ -298,13 +322,19 @@ class IsolatedMacVlan(IsolatedMacVlans):
|
|||||||
|
|
|
|
||||||
eth0 eth1 eth2 eth3
|
eth0 eth1 eth2 eth3
|
||||||
|
|
||||||
|
Example with bridge mode:
|
||||||
|
|
||||||
|
netns = IsolatedMacVlan("eth3", mode="bridge")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, parent, ifname="iface", lo=True, set_up=True):
|
def __init__(self, parent, ifname="iface", lo=True, set_up=True, mode="passthru"):
|
||||||
self._ifname = ifname
|
self._ifname = ifname
|
||||||
return super().__init__(ifmap={ parent: ifname }, lo=lo, set_up=set_up)
|
return super().__init__(ifmap={parent: ifname}, lo=lo, set_up=set_up,
|
||||||
|
mode=mode)
|
||||||
|
|
||||||
def addip(self, addr, prefix_length=24, proto="ipv4"):
|
def addip(self, addr, prefix_length=24, proto="ipv4"):
|
||||||
return super().addip(ifname=self._ifname, addr=addr, prefix_length=prefix_length, proto=proto)
|
return super().addip(ifname=self._ifname, addr=addr,
|
||||||
|
prefix_length=prefix_length, proto=proto)
|
||||||
|
|
||||||
def must_receive(self, expr, timeout=None, ifname=None, must=True):
|
def must_receive(self, expr, timeout=None, ifname=None, must=True):
|
||||||
ifname = ifname if ifname else self._ifname
|
ifname = ifname if ifname else self._ifname
|
||||||
@@ -387,10 +417,19 @@ class TPMR(IsolatedMacVlans):
|
|||||||
|
|
||||||
This is useful to verify the correctness of fail-over behavior in
|
This is useful to verify the correctness of fail-over behavior in
|
||||||
various protocols. See ospf_bfd for a usage example.
|
various protocols. See ospf_bfd for a usage example.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
a: Name of the first parent interface on the controller
|
||||||
|
b: Name of the second parent interface on the controller
|
||||||
|
mode: MACVLAN mode to use (default: "passthru")
|
||||||
|
- "passthru": Exclusive access (default)
|
||||||
|
- "bridge": Shared access, allows communication between MACVLANs
|
||||||
|
and full control of all frames. May be required for
|
||||||
|
proper layer-2 relay functionality in some tests.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, a, b):
|
def __init__(self, a, b, mode="passthru"):
|
||||||
super().__init__(ifmap={ a: "a", b: "b" }, lo=False)
|
super().__init__(ifmap={ a: "a", b: "b" }, lo=False, mode=mode)
|
||||||
|
|
||||||
def start(self, forward=True):
|
def start(self, forward=True):
|
||||||
ret = super().start()
|
ret = super().start()
|
||||||
|
|||||||
Reference in New Issue
Block a user