Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,20 +1,56 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues

import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.CIDR_SEPARATOR
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.GrammarOptionValue
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.IP_ADDR_AND_PREFIX_LENGTH
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.IPV4_ADDR
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.IPV6_ADDR
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.IntegerTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne

/*
* [Address] Address= (and the [Network] Address= shorthand) in a .network file.
*
* man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#Address=
* parser https://github.com/systemd/systemd/blob/a8e93919c3/src/network/networkd-address.c config_parse_address
* https://github.com/systemd/systemd/blob/a8e93919c3/src/shared/conf-parser.c config_parse_in_addr_prefix
*/

/**
* Validator for `Address=`, the ADDRESS_ADDRESS slot of config_parse_address_section.
*
* config_parse_address delegates to config_parse_in_addr_prefix asking for PREFIXLEN_REFUSE. When the
* prefix length is missing that returns -ENOANO, and the caller retries with
* in_addr_prefix_from_string_auto and logs:
*
* > Address=… is specified without prefix length. Assuming the prefix length is N.
* > Please specify the prefix length explicitly.
*
* Requiring the prefix on IPv6 therefore matches systemd's own advice rather than over-reaching, and
* that behaviour is kept. What is *not* faithful is bounding the prefix: in_addr_prefix_from_string
* only rejects a length above the family's address width, so the full 0…32 and 0…128 ranges are
* legal. The previous grammar demanded /8…/32 and /64…/128, which flagged `Address=2600::1/0` — a
* line out of systemd's own test/test-network/conf/25-veth-peer.network — and `Address=…/7`.
*
* `Peer=` is the same ConfigSectionParser slot but takes the fully faithful grammar, since it has no
* comparable history; see ConfigParseAddressSectionPeerOptionValue.
*/
class NetworkAddressOptionValue() : GrammarOptionValue("config_parse_address_section", GRAMMAR) {

companion object {
val GRAMMAR = SequenceCombinator(
IP_ADDR_AND_PREFIX_LENGTH, EOF())
AlternativeCombinator(
SequenceCombinator(IPV6_ADDR, CIDR_SEPARATOR, IntegerTerminal(0, 129)),
SequenceCombinator(IPV4_ADDR, ZeroOrOne(SequenceCombinator(CIDR_SEPARATOR, IntegerTerminal(0, 33)))),
),
EOF()
)

val validators = mapOf(
Validator("config_parse_address_section", "ADDRESS_ADDRESS") to NetworkAddressOptionValue()
)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai

import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.LiteralChoiceTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.WhitespaceTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.unsignedNumber

/*
* The remaining [Address] and [NextHop] table slots of a .network file.
*
* man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#%5BAddress%5D%20Section%20Options
* parsers https://github.com/systemd/systemd/blob/a8e93919c3/src/network/networkd-address.c config_parse_address_dad, config_parse_uint32_flag
* https://github.com/systemd/systemd/blob/a8e93919c3/src/network/networkd-nexthop.c config_parse_nexthop_family, config_parse_nexthop_group
*
* These slots previously shared one grammar per section — a uint32 for every [Address] entry and a
* boolean for every [NextHop] entry — which flagged legitimate values such as AddPrefixRoute=no,
* DuplicateAddressDetection=ipv4, Id=20 and Family=ipv4.
*/

/*
* The remaining `[Address]` and `[NextHop]` table slots (#509).
*
* These previously shared one grammar per section — a uint32 for every `[Address]` slot and a boolean
* for every `[NextHop]` slot — even though the ConfigSectionParser tables give each slot its own
* parser. That flagged legitimate values: `AddPrefixRoute=no`, `HomeAddress=yes`,
* `DuplicateAddressDetection=ipv4`, `[NextHop] Id=20` and `Family=ipv4` were all reported as invalid.
* Each slot now gets the grammar its own table entry calls for.
*/

private const val ADDRESS = "config_parse_address_section"
private const val NEXTHOP = "config_parse_nexthop_section"

/**
* `[Address] HomeAddress=`, `ManageTemporaryAddress=`, `PrefixRoute=`, `AddPrefixRoute=` and
* `AutoJoin=` — table entries config_parse_uint32_flag / config_parse_uint32_invert_flag. Both read
* the value with parse_boolean() and differ only in which IFA_F_* bit they set, and in which
* direction.
*/
class ConfigParseAddressSectionFlagOptionValue : SimpleGrammarOptionValues(
ADDRESS, SequenceCombinator(BOOLEAN, EOF())
)

/**
* `[Address] DuplicateAddressDetection=` — table entry config_parse_address_dad.
*
* The man page documents only the four family names, but the parser tries parse_boolean() *first* and
* accepts a boolean with nothing worse than a warning:
*
* ```c
* r = parse_boolean(rvalue);
* if (r >= 0) {
* log_syntax(unit, LOG_WARNING, filename, line, 0,
* "For historical reasons, %s=%s means %s=%s. "
* "Please use 'both', 'ipv4', 'ipv6' or 'none' instead.", …);
* ```
*
* So the booleans are accepted here too, but marked deprecated so the editor repeats systemd's advice
* instead of reporting an error on a line networkd honours. This is a case where the C source and the
* man page disagree and the source wins.
*/
class ConfigParseAddressSectionDadOptionValue : SimpleGrammarOptionValues(
ADDRESS, SequenceCombinator(DAD, EOF())
) {
private companion object {
private const val HISTORICAL =
"For historical reasons a boolean here means the opposite of what it looks like: " +
"yes means none and no means both. Please use 'both', 'ipv4', 'ipv6' or 'none' instead."

val DAD = FlexibleLiteralChoiceTerminal(
"none", "both", "ipv4", "ipv6",
// parse_boolean() spellings, all deprecated.
"1", "yes", "y", "true", "t", "on", "0", "no", "n", "false", "f", "off",
).deprecating(
listOf("1", "yes", "y", "true", "t", "on", "0", "no", "n", "false", "f", "off")
.associateWith { HISTORICAL }
)
}
}

/** `[NextHop] Id=` — table entry config_parse_uint32. */
class ConfigParseNextHopIdOptionValue : SimpleGrammarOptionValues(
NEXTHOP, SequenceCombinator(unsignedNumber(4_294_967_296L), EOF())
)

/**
* `[NextHop] Family=` — table entry config_parse_nexthop_family, resolved through
* nexthop_address_family_table, which unlike its siblings offers only the two concrete families.
*/
class ConfigParseNextHopFamilyOptionValue : SimpleGrammarOptionValues(
NEXTHOP, SequenceCombinator(FlexibleLiteralChoiceTerminal("ipv4", "ipv6"), EOF())
)

/**
* `[NextHop] Group=` — table entry config_parse_nexthop_group: a whitespace-separated list of
* `id[:weight]`, where the id is a uint32 and the weight, when present, is 1…256.
*/
class ConfigParseNextHopGroupOptionValue : SimpleGrammarOptionValues(
NEXTHOP,
SequenceCombinator(
GROUP_MEMBER,
ZeroOrMore(SequenceCombinator(WhitespaceTerminal(), GROUP_MEMBER)),
EOF()
)
) {
private companion object {
val GROUP_MEMBER = SequenceCombinator(
unsignedNumber(4_294_967_296L),
ZeroOrOne(SequenceCombinator(LiteralChoiceTerminal(":"), unsignedNumber(257, minInclusive = 1)))
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai

import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.COLON
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.IPV6_ADDR
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.LiteralChoiceTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.RegexTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne

/*
* The IPv6 address-generation tokens: [Network] IPv6Token=, [IPv6AcceptRA] Token= and
* [DHCPPrefixDelegation] / [DHCPv6PrefixDelegation] Token=.
*
* man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#IPv6Token=
* parser https://github.com/systemd/systemd/blob/a8e93919c3/src/network/networkd-address-generation.c config_parse_address_generation_type
* secret https://github.com/systemd/systemd/blob/a8e93919c3/src/libsystemd/sd-id128/id128-util.c id128_from_string_nonzero
*
* systemd's own test/test-network/conf/25-ipv6-prefix-veth-token-prefixstable.network doubles as the
* negative-case list for this setting.
*/

/**
* Validator for the IPv6 address-generation tokens: `[Network] IPv6Token=`, `[IPv6AcceptRA] Token=`
* and `[DHCPPrefixDelegation] Token=` / `[DHCPv6PrefixDelegation] Token=` (.network).
*
* C function: config_parse_address_generation_type in src/network/networkd-address-generation.c,
* which recognises three modes:
* - `prefixstable`, optionally `:ADDRESS`, optionally `,SECRET_KEY`
* - `eui64`
* - `static:ADDRESS`, or a bare ADDRESS (the mode defaults to static)
* ADDRESS is always parsed as IPv6, and SECRET_KEY goes through id128_from_string_nonzero, i.e. 32
* hex digits or the dashed UUID spelling, and not all-zero.
*
* One divergence: for the static mode systemd also rejects an address whose low 64 bits are zero
* (`static:::`), since only those bits are used. That is a property of the parsed value rather than
* of its shape, so this grammar accepts it.
*/
class ConfigParseAddressGenerationTypeOptionValue : SimpleGrammarOptionValues(
"config_parse_address_generation_type",
SequenceCombinator(
AlternativeCombinator(
SequenceCombinator(
LiteralChoiceTerminal("prefixstable"),
ZeroOrOne(SequenceCombinator(COLON, IPV6_ADDR)),
ZeroOrOne(SequenceCombinator(LiteralChoiceTerminal(","), SECRET_KEY)),
),
LiteralChoiceTerminal("eui64"),
SequenceCombinator(LiteralChoiceTerminal("static:"), IPV6_ADDR),
IPV6_ADDR,
),
EOF()
)
) {
companion object {
/** id128_from_string_nonzero: 32 hex digits or 8-4-4-4-12, and not all zeroes. */
private val SECRET_KEY = RegexTerminal(
"""[0-9a-fA-F-]+""",
"""(?=.*[1-9a-fA-F])(?:[0-9a-fA-F]{32}|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"""
)
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,56 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai

import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.*
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ByteSizeTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator

/*
* MTUBytes= and its family-specific siblings.
*
* man https://www.freedesktop.org/software/systemd/man/latest/systemd.link.html#MTUBytes=
* https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#IPv6MTUBytes=
* parser https://github.com/systemd/systemd/blob/a8e93919c3/src/shared/conf-parser.c config_parse_mtu
* https://github.com/systemd/systemd/blob/a8e93919c3/src/basic/parse-util.c parse_mtu, parse_size
*/

/**
* Validator for Network.IPv6MTUBytes
* C Function: config_parse_mtu(AF_INET6)
* Used by Options: Network.IPv6MTUBytes
*
* Validates IPv6 MTU values. For IPv6, the minimum MTU is 1280 bytes (IPV6_MIN_MTU)
* and the maximum is UINT32_MAX (4294967295).
* Validator for `MTUBytes=` in `[Link]` (.network and .link) and `[NetDev]`, plus
* `[Network] IPv6MTUBytes=` and `[DHCPv4] RouteMTUBytes=`.
*
* config_parse_mtu hands the value to parse_mtu, which runs parse_size() with base 1024 and then
* range-checks the number of bytes it denotes:
*
* ```c
* r = parse_size(s, 1024, &u);
* …
* if (u > UINT32_MAX) return -ERANGE;
* switch (family) {
* case AF_INET: m = IPV4_MIN_MTU; break; // 68
* case AF_INET6: m = IPV6_MIN_MTU; break; // 1280
* default: m = 0;
* }
* if (u < m) return -ERANGE;
* ```
*
* The bound is therefore on the *value*, not on how it is written: `IPv6MTUBytes=1K` is 1024 bytes
* and is rejected for being under IPV6_MIN_MTU even though nothing about its spelling looks wrong.
* [ByteSizeTerminal] evaluates the suffix so the minimum applies to every form.
*
* @param minimum the family's IPV*_MIN_MTU, in bytes
*/
class ConfigParseMtuOptionValue : SimpleGrammarOptionValues(
open class ConfigParseMtuOptionValue(minimum: Long) : SimpleGrammarOptionValues(
"config_parse_mtu",
SequenceCombinator(
IntegerTerminal(1280, 4294967296),
EOF()
)
SequenceCombinator(ByteSizeTerminal(minimum, UINT32_MAX), EOF())
)

private const val UINT32_MAX = 4_294_967_295L

/** `MTUBytes=` with no family-specific minimum (ltype AF_UNSPEC). */
class ConfigParseMtuAnyOptionValue : ConfigParseMtuOptionValue(0)

/** `[DHCPv4] RouteMTUBytes=` (ltype AF_INET): at least IPV4_MIN_MTU. */
class ConfigParseMtuIpv4OptionValue : ConfigParseMtuOptionValue(68)

/** `[Network] IPv6MTUBytes=` (ltype AF_INET6): at least IPV6_MIN_MTU. */
class ConfigParseMtuIpv6OptionValue : ConfigParseMtuOptionValue(1280)
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai

import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.WhitespaceTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore

/*
* [Link] NamePolicy= in a .link file.
*
* man https://www.freedesktop.org/software/systemd/man/latest/systemd.link.html#NamePolicy=
* parser https://github.com/systemd/systemd/blob/a8e93919c3/src/udev/net/link-config.c config_parse_name_policy (DEFINE_CONFIG_PARSE_ENUMV)
* values https://github.com/systemd/systemd/blob/a8e93919c3/src/shared/netif-naming-scheme.c name_policy_table
*
* Distinct from AlternativeNamesPolicy=, which resolves against alternative_names_policy_table in the
* same file and so offers neither "kernel" nor "keep".
*/

/**
* Validator for `[Link] NamePolicy=` (.link).
*
* C function: config_parse_name_policy in src/udev/net/link-config.c, generated by
* DEFINE_CONFIG_PARSE_ENUMV — a whitespace-separated list resolved through name_policy_table
* (src/shared/netif-naming-scheme.c).
*
* Distinct from AlternativeNamesPolicy=, which uses alternative_names_policy_table and therefore does
* not offer `kernel` or `keep`; see [ConfigParseAlternativeNamesPolicyOptionValue].
*/
class ConfigParseNamePolicyOptionValue : SimpleGrammarOptionValues(
"config_parse_name_policy",
SequenceCombinator(
NAME_POLICY,
ZeroOrMore(SequenceCombinator(WhitespaceTerminal(), NAME_POLICY)),
EOF()
)
) {
companion object {
private val NAME_POLICY = FlexibleLiteralChoiceTerminal(
"kernel",
"keep",
"database",
"onboard",
"slot",
"path",
"mac",
)
}
}
Loading
Loading