From 20966d5564bd9618e021cf4dd67b74b311ff1614 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Wed, 24 Jan 2024 17:52:44 +0200 Subject: [PATCH 01/16] cmake: Add function to convert a yaml file to a config file Add a way to convert a user supplied yaml file to a configuration file. For networking, a C header file is generated which can then be included to the application. Add a cmake function that calls the generate config macros with suitable values for networking needs. Signed-off-by: Jukka Rissanen --- cmake/modules/extensions.cmake | 74 ++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/cmake/modules/extensions.cmake b/cmake/modules/extensions.cmake index 1eef042e88c02..a196254f05744 100644 --- a/cmake/modules/extensions.cmake +++ b/cmake/modules/extensions.cmake @@ -16,6 +16,7 @@ include(CheckCXXCompilerFlag) # 1.2. zephyr_library_* # 1.2.1 zephyr_interface_library_* # 1.3. generate_inc_* +# 1.3.1 generate_config_* # 1.4. board_* # 1.5. Misc. # 2. Kconfig-aware extensions @@ -739,6 +740,79 @@ function(generate_inc_file_for_target generate_inc_file_for_gen_target(${target} ${source_file} ${generated_file} ${generated_target_name} ${ARGN}) endfunction() +# 1.3.1 generate_config_* + +# These functions are needed if a configuration file is generated +# from a user supplied yaml file. +# +function(generate_config_file + source_file # The yaml source file to be converted to config data + generated_file # The generated file + yaml_to_config_script # Script that generates the config + ) + add_custom_command( + OUTPUT ${generated_file} + COMMAND + ${PYTHON_EXECUTABLE} + ${yaml_to_config_script} + ${ARGN} # Extra arguments are passed to the script + < ${source_file} + > ${generated_file} + DEPENDS ${source_file} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) +endfunction() + +function(generate_config_file_for_gen_target + target # The cmake target that depends on the generated file + source_file # The yaml source file to be converted to config data + generated_file # The generated file + gen_target # The generated file target we depend on + yaml_to_config_script # Script that generates the config + # Any additional arguments are passed on to script + ) + + generate_config_file(${source_file} ${generated_file} ${yaml_to_config_script} ${ARGN}) + + # Ensure 'generated_file' is generated before 'target' by creating a + # dependency between the two targets + + add_dependencies(${target} ${gen_target}) +endfunction() + +function(generate_config_file_for_target + target # The cmake target that depends on the generated file + source_file # The yaml source file to be converted to config data + generated_file # The generated file + yaml_to_config_script # Script that generates the config + # Any additional arguments are passed on to script + ) + + # Ensure 'generated_file' is generated before 'target' by creating a + # 'custom_target' for it and setting up a dependency between the two + # targets + + # But first create a unique name for the custom target + generate_unique_target_name_from_filename(${generated_file} generated_target_name) + + add_custom_target(${generated_target_name} DEPENDS ${generated_file}) + generate_config_file_for_gen_target(${target} ${source_file} ${generated_file} + ${generated_target_name} ${yaml_to_config_script} ${ARGN}) +endfunction() + +function(network_generate_config_file_for_target + target # The cmake target that depends on the generated file + source_file # The yaml source file to be converted to config data + ) + + set(generated_file ${ZEPHYR_BINARY_DIR}/include/generated/network_config.inc) + set(yaml_to_config_script ${ZEPHYR_BASE}/scripts/net/net-yaml-config.py) + set(yaml_schema_file ${ZEPHYR_BASE}/scripts/schemas/network-configuration-schema.yml) + + generate_config_file_for_target(${target} ${source_file} ${generated_file} + ${yaml_to_config_script} ${yaml_schema_file}) +endfunction() + # 1.4. board_* # # This section is for extensions related to Zephyr board handling. From 6f44de27f8e21297a2439166d4f0a84957718f8a Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 25 Jan 2024 11:04:28 +0200 Subject: [PATCH 02/16] schemas: Add network configuration schema yaml file This contains rules to validate network configuration in yaml format. Signed-off-by: Jukka Rissanen --- .../schemas/network-configuration-schema.yml | 224 ++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 scripts/schemas/network-configuration-schema.yml diff --git a/scripts/schemas/network-configuration-schema.yml b/scripts/schemas/network-configuration-schema.yml new file mode 100644 index 0000000000000..989909022c635 --- /dev/null +++ b/scripts/schemas/network-configuration-schema.yml @@ -0,0 +1,224 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# A JSON schema for basic validation of the network configuration yaml file. + +$id: "https://zephyrproject.org/schemas/zephyr/network" +$schema: "https://json-schema.org/draft/2020-12/schema" + +title: "Zephyr Network Configuration Schema" +description: Schema for validating Zephyr network configuration metadata files +type: object +properties: + networking: + type: object + properties: + config_format_hash: + type: string + description: Hash for the configuration data (internal) + interfaces: + description: List of network interfaces + type: array + items: + type: object + properties: + bind_to: {} + name: + description: Network interface name to bind to + type: string + # CONFIG_NET_INTERFACE_NAME_LEN + maxLength: 15 + minLength: 2 + device_name: + description: Device name to bind to + type: string + set_name: + description: New name of the network interface + type: string + # CONFIG_NET_INTERFACE_NAME_LEN + maxLength: 15 + minLength: 2 + set_default: + description: Set this interface as a default one + type: boolean + flags: + description: Network interface flags to set + type: array + items: + type: string + maxLength: 32 + ipv6: + description: IPv6 configuration + type: object + properties: + status: + description: Is IPv6 enabled for this interface + type: boolean + ipv6_addresses: + description: Static IPv6 addresses for this interface + type: array + items: + type: string + # INET6_ADDRSTRLEN + prefix len (like "/128") + maxLength: 50 + ipv6_multicast_addresses: + description: Multicast IPv6 addresses for this interface + type: array + items: + type: string + # NET_IPV6_ADDR_STR_LEN + maxLength: 40 + prefixes: + description: IPv6 prefixes for this interface + type: array + items: + type: object + properties: + address: + description: IPv6 prefix + type: string + # NET_IPV6_ADDR_STR_LEN + maxLength: 40 + len: + description: IPv6 prefix length + type: integer + lifetime: + description: IPv6 prefix lifetime (in sec) + type: integer + required: + - address + - len + - lifetime + hop_limit: + description: IPv6 hop limit + type: integer + multicast_hop_limit: + description: IPv6 multicast hop limit + type: integer + dhcpv6: + description: DHCPv6 configuration + type: object + properties: + status: + description: Is DHCPv6 enabled for this interface + type: boolean + do_request_address: + description: Set request address flag + type: boolean + do_request_prefix: + description: Set request prefix flag + type: boolean + ipv4: + description: IPv4 configuration + type: object + properties: + status: + description: Is IPv4 enabled for this interface + type: boolean + ipv4_addresses: + description: Static IPv4 addresses for this interface + type: array + items: + type: string + # INET_ADDRSTRLEN + netmask len (like "/24") + maxLength: 19 + ipv4_multicast_addresses: + description: Multicast IPv4 addresses for this interface + type: array + items: + type: string + # INET_ADDRSTRLEN + maxLength: 16 + gateway: + description: IPv4 gateway to use + type: string + # INET_ADDRSTRLEN + maxLength: 16 + time_to_live: + description: IPv4 time-to-live + type: integer + multicast_time_to_live: + description: IPv4 multicast time-to-live + type: integer + dhcpv4: + description: DHCPv4 configuration + type: object + properties: + status: + description: Is DHCPv4 enabled for this interface + type: boolean + dhcpv4_server: + type: object + properties: + status: + description: Is DHCPv4 server enabled for this interface + type: boolean + base_address: + description: Set base address for the DHCPv4 server + type: string + # INET_ADDRSTRLEN + maxLength: 16 + ipv4_autoconf: + description: IPv4 autoconfiguration + type: object + properties: + status: + description: Is IPv4 auto configuration enabled for this interface + type: boolean + vlan: + description: Virtual LAN configuration + type: object + properties: + status: + description: Is VLAN enabled for this interface + type: boolean + tag: + description: VLAN tag for this interface + type: integer + required: + - tag + ieee_802_15_4: + type: object + properties: + status: + description: Is IEEE 802.15.4 enabled + type: boolean + bind_to: {} + pan_id: + type: integer + channel: + type: integer + tx_power: + type: integer + security_key_mode: + type: integer + security_level: + type: integer + ack_required: + type: boolean + security_key: + type: array + items: + type: integer + required: + - pan_id + - channel + sntp: + type: object + properties: + status: + description: Is SNTP enabled + type: boolean + server: + description: SNTP server address + type: string + # INET6_ADDRSTRLEN + maxLength: 46 + timeout: + description: Request timeout in sec + type: integer + bind_to: {} + required: + - server +additionalProperties: false From 5574488eae382ff575e70a3536af5083bd6e1c76 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Wed, 24 Jan 2024 17:51:34 +0200 Subject: [PATCH 03/16] net: Add yaml to network configuration generator script Add a python script that converts a user supplied network configuration yaml file to a valid network configuration C header file. Signed-off-by: Jukka Rissanen --- scripts/net/net-yaml-config.py | 452 +++++++++++++++++++++++++++++++++ 1 file changed, 452 insertions(+) create mode 100755 scripts/net/net-yaml-config.py diff --git a/scripts/net/net-yaml-config.py b/scripts/net/net-yaml-config.py new file mode 100755 index 0000000000000..e5d1ff7fca553 --- /dev/null +++ b/scripts/net/net-yaml-config.py @@ -0,0 +1,452 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright The Zephyr Project Contributors +# SPDX-License-Identifier: Apache-2.0 + +import hashlib +import os +import re +import sys + +import yaml + +data = {} +schema = {} +normalized_output = "" + + +def read_yaml_file(input_data): + return yaml.safe_load(input_data) + + +# Allow both yaml file or .config file input +input_data = sys.stdin.read() + +lines = input_data.splitlines() +for line in lines: + if not line.strip(): # skip empty lines + continue + + if line.startswith("#"): + continue + if line.startswith("CONFIG_"): + # Construct string in two pieces to avoid triggering warning + # from compliance checker. + if not line.startswith("CONFIG_" + "NET_"): + continue + + (var, val) = line.split("=", 1) + if val == 'y': + val = "true" + + os.environ[var] = val + + # print("env " + var + " = " + os.getenv(var, "")) + else: + data = read_yaml_file(input_data) + break + + +# yaml env variable handler +def constructor_env_variables(loader, node): + value = loader.construct_scalar(node) + match = pattern.findall(value) # to find all env variables in line + dt = ''.join(type_tag_pattern.findall(value)) or '' + value = value.replace(dt, '') + if match: + full_value = value + for g in match: + curr_default_value = 'None' + env_var_name = g + env_var_name_with_default = g + if default_sep and isinstance(g, tuple) and len(g) > 1: + env_var_name = g[0] + env_var_name_with_default = ''.join(g) + found = False + for each in g: + if default_sep in each: + _, curr_default_value = each.split(default_sep, 1) + found = True + break + if not found: + raise ValueError(f'Could not find default value for {env_var_name}') + full_value = full_value.replace( + f'${{{env_var_name_with_default}}}', + os.environ.get(env_var_name, curr_default_value).strip('"'), + ) + if dt: + # do one more roundtrip with the dt constructor: + node.value = full_value + node.tag = dt.strip() + return loader.yaml_constructors[node.tag](loader, node) + return full_value + return value + + +# Create a yaml file from a .config variables. Treat config options as environment +# variables and substitute the variables in yaml template. +def create_yaml_file(): + if ( + "CONFIG_NET_CONFIG_MY_IPV4_NETMASK" in os.environ + and os.environ["CONFIG_NET_CONFIG_MY_IPV4_NETMASK"] != "" + ): + masklen = sum( + bin(int(x)).count('1') + for x in os.environ["CONFIG_NET_CONFIG_MY_IPV4_NETMASK"].strip('"').split('.') + ) + os.environ["IPV4_NETMASK_LEN"] = str(masklen) + + if ( + "CONFIG_NET_CONFIG_SNTP_INIT_SERVER" in os.environ + and os.environ["CONFIG_NET_CONFIG_SNTP_INIT_SERVER"] != "" + ): + os.environ["SNTP_ENABLED"] = "true" + + if "CONFIG_NET_L2_IEEE802154" in os.environ: + if ( + "CONFIG_NET_CONFIG_IEEE802154_SECURITY_KEY" in os.environ + and os.environ["CONFIG_NET_CONFIG_IEEE802154_SECURITY_KEY"] != "" + ): + IEEE802154_SECURITY_KEY = str( + [ + int(ord(x[0])) + for x in list( + os.environ["CONFIG_NET_CONFIG_IEEE802154_SECURITY_KEY"].strip('"') + ) + ] + ) + else: + IEEE802154_SECURITY_KEY = str([]) + + ieee802154 = ( + """ + ieee_802_15_4: + status: !ENV tag:yaml.org,2002:bool ${CONFIG_NET_L2_IEEE802154:false} + pan_id: !ENV tag:yaml.org,2002:int ${CONFIG_NET_CONFIG_IEEE802154_PAN_ID:0xabcd} + channel: !ENV tag:yaml.org,2002:int ${CONFIG_NET_CONFIG_IEEE802154_CHANNEL:0} + tx_power: !ENV tag:yaml.org,2002:int ${CONFIG_NET_CONFIG_IEEE802154_RADIO_TX_POWER:0} + security_key: """ + + IEEE802154_SECURITY_KEY + + """ + security_key_mode: !ENV tag:yaml.org,2002:int ${CONFIG_NET_CONFIG_IEEE802154_SECURITY_KEY_MODE:0} + security_level: !ENV tag:yaml.org,2002:int ${CONFIG_NET_CONFIG_IEEE802154_SECURITY_LEVEL:0} + ack_required: !ENV tag:yaml.org,2002:bool ${CONFIG_NET_CONFIG_IEEE802154_ACK_REQUIRED:False} + """ # noqa: E501 + ) + else: + ieee802154 = "" + + if "SNTP_ENABLED" in os.environ: + sntp = """ + sntp: + status: !ENV tag:yaml.org,2002:bool ${SNTP_ENABLED:false} + server: !ENV ${CONFIG_NET_CONFIG_SNTP_INIT_SERVER:""} + timeout: !ENV tag:yaml.org,2002:int ${CONFIG_NET_CONFIG_SNTP_INIT_TIMEOUT:0} + """ + else: + sntp = "" + + # There is only a limited number of options to set via .config file + yaml_file = ( + """ +networking: + interfaces: + - ipv6: + status: !ENV tag:yaml.org,2002:bool ${CONFIG_NET_CONFIG_NEED_IPV6:false} + ipv6_addresses: + - !ENV ${CONFIG_NET_CONFIG_MY_IPV6_ADDR:""} + dhcpv6: + status: !ENV tag:yaml.org,2002:bool ${CONFIG_NET_DHCPV6:false} + do_request_address: !ENV tag:yaml.org,2002:bool ${CONFIG_NET_CONFIG_DHCPV6_REQUEST_ADDR:true} + do_request_prefix: !ENV tag:yaml.org,2002:bool ${CONFIG_NET_CONFIG_DHCPV6_REQUEST_PREFIX:false} + ipv4: + status: !ENV tag:yaml.org,2002:bool ${CONFIG_NET_CONFIG_NEED_IPV4:false} + ipv4_addresses: + - !ENV ${CONFIG_NET_CONFIG_MY_IPV4_ADDR:""}/${IPV4_NETMASK_LEN:32} + gateway: !ENV ${CONFIG_NET_CONFIG_MY_IPV4_GW:""} + dhcpv4: + status: !ENV tag:yaml.org,2002:bool ${CONFIG_NET_DHCPV4:false} + ipv4_autoconf: + status: !ENV tag:yaml.org,2002:bool ${CONFIG_NET_IPV4_AUTO:false} + + """ # noqa: E501 + + ieee802154 + + sntp + ) + + return yaml_file + + +# This branch handles the variables read from .config, it generates a yaml +# file and then validates (if enabled) that it is correct. +if not bool(data): + # Create a yaml using the env variables + loader = yaml.SafeLoader + tag = '!ENV' + default_sep = ':' + default_value = '' + default_sep_pattern = r'(' + default_sep + '[^}]+)?' + pattern = re.compile(r'.*?\$\{([^}{' + default_sep + r']+)' + default_sep_pattern + r'\}.*?') + type_tag = 'tag:yaml.org,2002:' + type_tag_pattern = re.compile(rf'({type_tag}\w+\s)') + + loader.add_implicit_resolver(tag, pattern, first=[tag]) + loader.add_constructor(tag, constructor_env_variables) + + data = read_yaml_file(create_yaml_file()) + +# If user has supplied an argument, treat it as a schema file +if bool(data) and len(sys.argv[1:]) > 0: + # If jsonschema is installed, then validate the yaml + try: + import jsonschema + + def yaml_validate(data, schema): + if not schema: + return + + try: + from yaml import CSafeLoader as SafeLoader + except ImportError: + from yaml import SafeLoader + + with open(schema) as f: + net_schema = yaml.load(f.read(), Loader=SafeLoader) + + validator_class = jsonschema.validators.validator_for(net_schema) + validator_class.check_schema(net_schema) + validator = validator_class(net_schema) + + errors = sorted(validator.iter_errors(data), key=lambda e: e.path) + if errors: + # Build a readable message with each error and its path + lines = [] + for e in errors: + path = ".".join(map(str, list(e.path))) or "" + lines.append(f"{path}: {e.message}") + raise jsonschema.ValidationError("\n".join(lines)) + + except ImportError as e: + sys.stderr.write("can't import jsonschema; won't validate YAML (%s)", e) + + def yaml_validate(data, schema): + pass + + yaml_validate(data, sys.argv[1]) + +with open(sys.argv[1]) as schema_file: + schema = yaml.safe_load(schema_file) + +if not bool(schema): + sys.stderr.write("Schema file needs to be supplied\n") + exit(1) + +netif = data['networking']['interfaces'] +netif_count = len(netif) + + +# Note that all the bind-to fields are added +1 so that we can catch +# the case where the value is not set (0). When taken into use in C code, +# then -1 is added to the value. +def get_bind_to(dict): + for i in range(netif_count): + if dict == netif[i]: + return i + 1 + + +def print_bind_to(dict, indent): + print(indent + ".bind_to = " + str(get_bind_to(dict)) + ",") + + +def walk_dict(map, indent): + for key, value in map.items(): + if isinstance(value, list): + print(indent + "." + key + " = {") + walk_list(value, "\t" + indent) + print(indent + "},") + elif isinstance(value, dict): + if key == "bind_to": + print_bind_to(value, indent) + else: + print(indent + "." + key + " = {") + walk_dict(value, "\t" + indent) + print(indent + "},") + elif isinstance(value, str): + print(indent + "." + key + " = \"" + value + "\",") + elif isinstance(value, bool): + print(indent + "." + key + " = " + str(value).lower() + ",") + elif isinstance(value, int): + print(indent + "." + key + " = " + str(value) + ",") + + +def walk_list(lst, indent): + for i, v in enumerate(lst): + print(indent + "[" + str(i) + "] = {") + if isinstance(v, list): + walk_list(v, "\t" + indent) + elif isinstance(v, dict): + walk_dict(v, "\t" + indent) + elif isinstance(v, str): + print(indent + "\t" + ".value = \"" + v + "\",") + elif isinstance(v, bool): + print(indent + "\t" + str(v).lower() + ",") + elif isinstance(v, int): + print(indent + "\t" + str(v) + ",") + print(indent + "},") + + +def walk_dict_union(cdict, prev_key, map): + for key, value in map.items(): + if isinstance(value, list): + walk_list_union(cdict, key, value) + elif isinstance(value, dict): + walk_dict_union(cdict, key, value) + + +def walk_list_union(cdict, key, lst): + for _, v in enumerate(lst): + if isinstance(v, list): + walk_list_union(cdict, key, v) + elif isinstance(v, dict): + walk_dict_union(cdict, key, v) + # Store the length of the array so that we can fetch that + # when walking the schema file. + if key in cdict: + if cdict[key] < len(lst): + cdict[key] = len(lst) + else: + cdict[key] = len(lst) + + +def output(indent, str): + global normalized_output + + normalized_output += str + print(indent + str) + + +changed = "" + + +def walk_dict_schema(level, top_level_name, cdict, key_upper, map, indent): + global changed + str_val = "" + + for key, value in map.items(): + if key == "type": + # Use value instead of items to avoid changing too many places in C code + if key_upper == "items": + key_upper = "value" + + if value == "string": + str_val = "const char *" + key_upper + ";" + changed += indent + "bool " + "__" + key_upper + "_changed : 1;" + "\n" + elif value == "boolean": + output(indent, "bool " + key_upper + ";") + changed += indent + "bool " + "__" + key_upper + "_changed : 1;" + "\n" + elif value == "integer": + output(indent, "int " + key_upper + ";") + changed += indent + "bool " + "__" + key_upper + "_changed : 1;" + "\n" + elif value == "array": + print(changed, end="") + changed = "" + output(indent, "struct " + top_level_name + key_upper + " {") + elif value == "object": + print(changed, end="") + changed = "" + if key_upper != "value": + if level == 1: + output(indent, "struct " + key_upper + " {") + else: + output(indent, "struct " + top_level_name + key_upper + " {") + continue + + if key == "bind_to": + output(indent + "\t", "int bind_to;") + changed += indent + "\t" + "bool " + "__bind_to_changed : 1;" + "\n" + continue + + if key == "maxLength": + output(indent, "char " + key_upper + "[" + str(value) + "];") + str_val = "" + continue + + # Print the string as a pointer if it is set without maxLength + if len(str_val) > 0: + output(indent, str_val) + str_val = "" + continue + + if isinstance(value, dict): + walk_dict_schema(level + 1, top_level_name, cdict, key, value, "\t" + indent) + elif isinstance(value, list): + walk_list_schema(level + 1, top_level_name, cdict, key, value, "\t" + indent) + + if key == "items": + print(changed, end="") + changed = "" + if key_upper in combined_data: + output(indent, "} " + key_upper + "[" + str(combined_data[key_upper]) + "];") + else: + output(indent, "} " + key_upper + "[1];") + + elif key == "properties": + print(changed, end="") + changed = "" + if key_upper != "value": + # Avoid creating a variable at the top level because we have + # a separate variable created in the header file that is used in C file. + if level == 1: + output(indent, "};") + else: + output(indent, "} " + key_upper + ";") + + # Print the string as a pointer if it is set without maxLength + if len(str_val) > 0: + output(indent, str_val) + + +def walk_list_schema(level, top_level_name, cdict, key, lst, indent): + for _, v in enumerate(lst): + if isinstance(v, list): + walk_list_schema(level, top_level_name, cdict, key, v, indent) + elif isinstance(v, dict): + walk_dict_schema(level, top_level_name, cdict, key, v, indent) + + +combined_data = {} +schema_data = {} + +print("#ifndef ZEPHYR_INCLUDE_NET_CONFIG_AUTO_GENERATED_H_") +print("#define ZEPHYR_INCLUDE_NET_CONFIG_AUTO_GENERATED_H_") + +# Figure out the array sizes +for key, value in data.items(): + if isinstance(value, dict): + walk_dict_union(combined_data, key, value) + +# Create C struct definition. Prefix some of the generated C struct fields with +# by the name of the struct to make them unambiguous. +for key, value in schema.items(): + if key in ('$id', '$schema', 'title', 'description'): + print("/* " + key + ": " + str(value) + " */") + if isinstance(value, dict): + walk_dict_schema(0, "net_cfg_", schema_data, key, value, "") + +print() +print("const struct " + list(data.keys())[0] + "* net_config_get_init_config(void);") +print() +print("#define NET_CONFIG_NETWORK_INTERFACE_COUNT " + str(netif_count)) +print("#endif /* ZEPHYR_INCLUDE_NET_CONFIG_AUTO_GENERATED_H_ */") +print() + +# Create C struct values +for key, value in data.items(): + if isinstance(value, dict): + print("#if defined(" + key.upper() + "_CONFIG_ENABLE_DATA)") + print("#define " + key.upper() + "_CONFIG_DATA " + key + "_config_data") + print("static const struct " + key + " " + key + "_config_data = {") + h = hashlib.sha1(normalized_output.encode('utf-8')).hexdigest() + print(f"\t.config_format_hash = \"{h}\",") + walk_dict(value, "\t") + print("};") + print("#endif /* " + key.upper() + "_CONFIG_ENABLE_DATA */") From ba5e36028f3694dc67f3c5457bd812a1692a6e08 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 29 Aug 2024 12:59:11 +0300 Subject: [PATCH 04/16] cmake: Do initial network configuration if enabled If CONFIG_NET_CONFIG_SETTINGS is enabled, then the network configuration library will check if a yaml file network-config.yaml is found in the application directory and use it to generate network initial configuration that is then applied when the device boots. If the yaml file is not there, then the relevant Kconfig options are used to create initial network configuration. Signed-off-by: Jukka Rissanen --- cmake/modules/kernel.cmake | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cmake/modules/kernel.cmake b/cmake/modules/kernel.cmake index af9c85d80e35c..be37ef0a52a1f 100644 --- a/cmake/modules/kernel.cmake +++ b/cmake/modules/kernel.cmake @@ -256,3 +256,14 @@ if("${CMAKE_EXTRA_GENERATOR}" STREQUAL "Eclipse CDT4") include(${ZEPHYR_BASE}/cmake/ide/eclipse_cdt4_generator_amendment.cmake) eclipse_cdt4_generator_amendment(1) endif() + +if(CONFIG_NET_CONFIG_SETTINGS) + # If network configuration library is enabled, check if the yaml file + # is present and use it to generate an initial configuration. Otherwise + # use the .config file to generate the initial configuration. + if(EXISTS ${APPLICATION_SOURCE_DIR}/network-config.yaml) + network_generate_config_file_for_target(app ${APPLICATION_SOURCE_DIR}/network-config.yaml) + else() + network_generate_config_file_for_target(app ${DOTCONFIG}) + endif() +endif() From f541902b7b58f113dce1e62a913fba88a2742238 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Fri, 30 Aug 2024 09:29:59 +0300 Subject: [PATCH 05/16] cmake: Allow user to override the net init config file User can set NET_CONFIG_FILE to override the name of the default configuration file name which is network-config.yaml This way a substitute yaml config file can be used instead of the default one. Signed-off-by: Jukka Rissanen --- cmake/modules/configuration_files.cmake | 2 ++ cmake/modules/kernel.cmake | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cmake/modules/configuration_files.cmake b/cmake/modules/configuration_files.cmake index fe1ff3bf16fa4..91666c2727a94 100644 --- a/cmake/modules/configuration_files.cmake +++ b/cmake/modules/configuration_files.cmake @@ -16,6 +16,7 @@ # - EXTRA_DTC_OVERLAY_FILE List of additional devicetree overlay files # - DTS_EXTRA_CPPFLAGS List of additional devicetree preprocessor defines # - APPLICATION_CONFIG_DIR: Root folder for application configuration +# - NET_INIT_CONFIG_FILE: Name of the network init configuration file # # If any of the above variables are already set when this CMake module is # loaded, then no changes to the variable will happen. @@ -95,5 +96,6 @@ zephyr_boilerplate_watch(DTC_OVERLAY_FILE) zephyr_get(EXTRA_CONF_FILE SYSBUILD LOCAL VAR EXTRA_CONF_FILE OVERLAY_CONFIG MERGE REVERSE) zephyr_get(EXTRA_DTC_OVERLAY_FILE SYSBUILD LOCAL MERGE REVERSE) zephyr_get(DTS_EXTRA_CPPFLAGS SYSBUILD LOCAL MERGE REVERSE) +zephyr_get(NET_INIT_CONFIG_FILE) build_info(application source-dir VALUE ${APPLICATION_SOURCE_DIR}) build_info(application configuration-dir VALUE ${APPLICATION_CONFIG_DIR}) diff --git a/cmake/modules/kernel.cmake b/cmake/modules/kernel.cmake index be37ef0a52a1f..a469345d505e9 100644 --- a/cmake/modules/kernel.cmake +++ b/cmake/modules/kernel.cmake @@ -261,8 +261,11 @@ if(CONFIG_NET_CONFIG_SETTINGS) # If network configuration library is enabled, check if the yaml file # is present and use it to generate an initial configuration. Otherwise # use the .config file to generate the initial configuration. - if(EXISTS ${APPLICATION_SOURCE_DIR}/network-config.yaml) - network_generate_config_file_for_target(app ${APPLICATION_SOURCE_DIR}/network-config.yaml) + if(NOT NET_CONFIG_FILE) + set(NET_CONFIG_FILE ${APPLICATION_SOURCE_DIR}/network-config.yaml) + endif() + if(EXISTS ${NET_CONFIG_FILE}) + network_generate_config_file_for_target(app ${NET_CONFIG_FILE}) else() network_generate_config_file_for_target(app ${DOTCONFIG}) endif() From 06051654884b0187ccef56a84488ec61de91d675 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Mon, 18 Sep 2023 10:58:43 +0300 Subject: [PATCH 06/16] net: config: Example yaml configuration file Example networking yaml configuration file. This contains more complex network setup that can be used to see what can be done. Signed-off-by: Jukka Rissanen --- .../net/lib/config/configuration-example.yaml | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 subsys/net/lib/config/configuration-example.yaml diff --git a/subsys/net/lib/config/configuration-example.yaml b/subsys/net/lib/config/configuration-example.yaml new file mode 100644 index 0000000000000..e7fc2e604f338 --- /dev/null +++ b/subsys/net/lib/config/configuration-example.yaml @@ -0,0 +1,121 @@ +# This is an example for defining network configuration data that +# can be applied automatically when the device boots. +# +networking: + interfaces: + # Example of one interface selected by its name + - &main-interface + name: eth0 + set_name: my-eth0 + set_default: true + ipv6: + status: true + ipv6_addresses: + - 2001:db8:110::1 + ipv6_multicast_addresses: + - ff05::114 + - ff15::115 + prefixes: + - address: "2001:db8::" + len: 64 + lifetime: 1024 + hop_limit: 64 + multicast_hop_limit: 1 + dhcpv6: + status: true + do_request_address: true + do_request_prefix: false + ipv4: + status: true + ipv4_addresses: + - 192.0.2.10/24 + ipv4_multicast_addresses: + - 234.0.0.10 + gateway: 192.0.2.1 + time_to_live: 64 + multicast_time_to_live: 1 + dhcpv4: + status: true + ipv4_autoconf: + status: true + + # Example of another interface selected by its device + - &device-interface + device_name: ETH_DEVICE + set_name: my-eth1 + flags: + - NET_IF_NO_AUTO_START + ipv4: + status: true + ipv4_addresses: + - 192.168.1.2/24 + ipv4_multicast_addresses: + - 234.0.0.10 + gateway: 192.168.110.1 + time_to_live: 10 + multicast_time_to_live: 0 + dhcpv4: + status: true + dhcpv4_server: + status: true + base_address: 192.168.1.100 + + # Example of virtual interface tied to the first one + - name: virt0 + set_name: virt0 + bind_to: *device-interface + ipv6: + status: false + ipv6_addresses: + - 2001:db8:111::2 + ipv4: + status: false + + # Example of VLAN interface that attaches to eth0 + - &vlan-interface + set_name: vlan0 + bind_to: *main-interface + vlan: + status: true + tag: 1234 + ipv4: + status: true + dhcpv4: + status: true + + # The interface IPv4 configuration could be set to disabled + # in which case its IPv4 configuration is skipped + - name: eth1 + set_name: my-eth2 + flags: + - NET_IF_NO_AUTO_START + ipv4: + status: false + ipv4_addresses: + - 192.168.1.2/24 + ipv4_multicast_addresses: + - 234.0.0.10 + gateway: 192.168.110.1 + time_to_live: 10 + multicast_time_to_live: 0 + dhcpv4_server: + status: true + base_address: 192.168.2.1 + + ieee_802_15_4: + status: true + pan_id: 0xabcd + channel: 26 + tx_power: 1 + security_key: [0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf] + security_key_mode: 0 + security_level: 1 + ack_required: true + bind_to: *device-interface + + sntp: + status: true + server: sntp.foo.bar + timeout: 30 + bind_to: *vlan-interface From 4a5a83ea5fec118408acf6ebf9f1a94c6a5c1dac Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 29 Aug 2024 14:48:27 +0300 Subject: [PATCH 07/16] doc: net: Add yaml configuration information Add documentation about the network stack configuration using a yaml file. Signed-off-by: Jukka Rissanen --- doc/connectivity/networking/index.rst | 1 + .../networking/network_config.rst | 331 ++++++++++++++++++ 2 files changed, 332 insertions(+) create mode 100644 doc/connectivity/networking/network_config.rst diff --git a/doc/connectivity/networking/index.rst b/doc/connectivity/networking/index.rst index 18e3032978bf3..27cec6bc238bd 100644 --- a/doc/connectivity/networking/index.rst +++ b/doc/connectivity/networking/index.rst @@ -13,6 +13,7 @@ operation of the stacks and how they were implemented. overview.rst net-stack-architecture.rst net_config_guide.rst + network_config.rst networking_with_host.rst network_monitoring.rst network_tracing.rst diff --git a/doc/connectivity/networking/network_config.rst b/doc/connectivity/networking/network_config.rst new file mode 100644 index 0000000000000..14b51ccf188c9 --- /dev/null +++ b/doc/connectivity/networking/network_config.rst @@ -0,0 +1,331 @@ +.. _network_stack_configuration: + +Network Stack Configuration +########################### + +.. contents:: + :local: + :depth: 2 + +This document describes how the network config library can be used to do initial +network stack configuration at runtime when the device boots. The network config +library was mainly developed for testing purposes when a pre-defined setup is +applied to the device when it is starting up. The configuration data is static +and stored in ROM so the device is applied same initial network configuration at +boot. + +The configuration library can be used for example to enable DHCPv4 client at boot, +or setup VLAN tags etc. + +Network Configuration Options +***************************** + +The :kconfig:option:`CONFIG_NET_CONFIG_SETTINGS` enables the network configuration +library. If it is not set, then no network setup is done and the application must +setup network settings itself. + +If the above setting is enabled, then two configuration options can be used to setup +the network stack: + +* Normal Kconfig options from ``CONFIG_NET_CONFIG_*`` branch. + This is the legacy way and only suitable for simpler setups where there is only + one network interface that needs to be setup at boot. + See available Kconfig options in the network API documentation. + +* A yaml configuration file ``network-config.yaml``. + This allows user to describe the device hardware setup and what options to set + even when the device have multiple network interfaces. + +The net-init-config.yaml Syntax +******************************* + +The ``network-config.yaml`` can be placed to the application directory. When the +application is compiled, this file is used to generate configuration that is then +applied at runtime. The yaml file contents is verified using a schema located in +``scripts/schemas/network-configuration-schema.yaml`` file. + +User can use a different configuration yaml file by setting ``NET_CONFIG_FILE`` +when calling cmake or west. + +.. code-block:: console + + west build -p -b native_sim myapp -d build -- -DNET_CONFIG_FILE=my-net-config.yaml + +The yaml file contains configuration sections for each network interface in the +system, IEEE 802.15.4 configuration or SNTP configuration. + +Example: + +.. code-block:: yaml + + networking: + interfaces: + - &main-interface + name: eth0 + set_name: main-eth0 + set_default: true + flags: + - NET_IF_NO_AUTO_START + ipv6: + status: true + ipv6_addresses: + - 2001:db8:110::1 + ipv6_multicast_addresses: + - ff05::114 + - ff15::115 + prefixes: + - address: "2001:db8::" + len: 64 + lifetime: 1024 + hop_limit: 48 + multicast_hop_limit: 2 + dhcpv6: + status: true + do_request_address: true + do_request_prefix: false + ipv4: + status: true + ipv4_addresses: + - 192.0.2.10/24 + ipv4_multicast_addresses: + - 234.0.0.10 + gateway: 192.0.2.1 + time_to_live: 128 + multicast_time_to_live: 3 + dhcpv4: + status: true + ipv4_autoconf: + status: true + + - &vlan-interface + set_name: vlan0 + bind_to: *main-interface + flags: + - ^NET_IF_PROMISC + vlan: + status: true + tag: 2432 + ipv4: + status: true + dhcpv4: + status: true + + sntp: + status: true + server: sntp.example.com + timeout: 30 + bind_to: *main-interface + +In the above example, there are two network interfaces. One with name ``eth0`` which +is changed to ``main-eth0`` and which is made the default interface. It has both +IPv6 and IPv4 supported. There is also a VLAN interface that is bound to the first +one. Its name is set to ``vlan0`` and it is enabled with tag ``2432``. The VLAN +interface does not have IPv6 enabled, but IPv4 is together with DHCPv4 client support. +Also SNTP is enabled and is using ``sntp.example.com`` server address. The SNTP is +configured to use the first network interface. + +The yaml File Configuration Options +*********************************** + +These options are available for each network configuration domain. + +.. table:: The ``interface`` options + :align: left + + +-------------+-------------+-----------------------------------------------------------------+ + | Option name | Type | Description | + +=============+=============+=================================================================+ + | bind_to | reference | Bind this object to another network interface. | + | | | This is useful for example for VLANs or other types of virtual | + | | | interfaces. | + +-------------+-------------+-----------------------------------------------------------------+ + | name | string | Existing name of the network interface. | + | | | This is used to find the interface so that we can apply the | + | | | subsequent configuration to it. | + | | | Either this option or the ``device_name`` option must be given. | + +-------------+-------------+-----------------------------------------------------------------+ + | device_name | string | Name of the device of the network interface. | + | | | Either this or the ``name`` option must be set in the yaml file.| + +-------------+-------------+-----------------------------------------------------------------+ + | set_name | string | New name of the network interface. | + | | | This can be used to change the name of the interface if | + | | | the default name is not suitable. | + +-------------+-------------+-----------------------------------------------------------------+ + | set_default | bool | Set this network interface as default one which will be returned| + | | | by the :c:func:`net_if_get_default` function call. | + +-------------+-------------+-----------------------------------------------------------------+ + | flags | string list | Array of network interface flags that should be applied to this | + | | | interface. See ``net_if_flag`` documentation for descriptions of| + | | | the flags. If the flag starts with ``^`` then the flag value is | + | | | cleared. | + | | | Following flags can be set/cleared: | + | | | ``NET_IF_POINTOPOINT``, ``NET_IF_PROMISC``, | + | | | ``NET_IF_NO_AUTO_START``, ``NET_IF_FORWARD_MULTICASTS``, | + | | | ``NET_IF_IPV6_NO_ND``, ``NET_IF_IPV6_NO_MLD`` | + +-------------+-------------+-----------------------------------------------------------------+ + | ipv6 | struct | IPv6 configuration options. | + +-------------+-------------+-----------------------------------------------------------------+ + | ipv4 | struct | IPv4 configuration options. | + +-------------+-------------+-----------------------------------------------------------------+ + | vlan | struct | VLAN configuration options. | + | | | Only applicable for Ethernet based interfaces. | + +-------------+-------------+-----------------------------------------------------------------+ + +.. table:: The ``ipv6`` options + :align: left + + +--------------------------+-------------+----------------------------------------------------+ + | Option name | Type | Description | + +==========================+=============+====================================================+ + | status | bool | Is the IPv6 enabled for this interface. | + | | | If set to ``false``, then these options are no-op. | + +--------------------------+-------------+----------------------------------------------------+ + | ipv6_addresses | string list | IPv6 addresses applied to this interface. | + | | | The value can contain prefix length. | + | | | Example: ``2001:db8::1/64`` | + +--------------------------+-------------+----------------------------------------------------+ + | ipv6_multicast_addresses | string list | IPv6 multicast addresses applied to this interface.| + +--------------------------+-------------+----------------------------------------------------+ + | hop_limit | int | Hop limit for the interface. | + +--------------------------+-------------+----------------------------------------------------+ + | multicast_hop_limit | int | Multicast hop limit for the interface. | + +--------------------------+-------------+----------------------------------------------------+ + | dhcpv6 | struct | DHCPv6 client options. | + +--------------------------+-------------+----------------------------------------------------+ + | prefixes | list of | IPv6 prefixes. | + | | structs | | + +--------------------------+-------------+----------------------------------------------------+ + +.. table:: The ``dhcpv6`` options + :align: left + + +--------------------+------+-----------------------------------------------------------------+ + | Option name | Type | Description | + +====================+======+=================================================================+ + | status | bool | Is DHCPv6 client enabled for this interface. | + +--------------------+------+-----------------------------------------------------------------+ + | do_request_address | bool | Request IPv6 address. | + +--------------------+------+-----------------------------------------------------------------+ + | do_request_prefix | bool | Requeest IPv6 prefix. | + +--------------------+------+-----------------------------------------------------------------+ + +.. table:: The ``prefixes`` options + :align: left + + +-------------+--------+----------------------------------------------------------------------+ + | Option name | Type | Description | + +=============+========+======================================================================+ + | address | string | IPv6 address. | + +-------------+--------+----------------------------------------------------------------------+ + | len | int | Prefix length. | + +-------------+--------+----------------------------------------------------------------------+ + | lifetime | int | Prefix lifetime. | + +-------------+--------+----------------------------------------------------------------------+ + +.. table:: The ``ipv4`` options + :align: left + + +--------------------------+-------------+----------------------------------------------------+ + | Option name | Type | Description | + +==========================+=============+====================================================+ + | status | bool | Is the IPv4 enabled for this interface. | + | | | If set to ``false``, then these options are no-op. | + +--------------------------+-------------+----------------------------------------------------+ + | ipv4_addresses | string list | IPv4 addresses applied to this interface. | + | | | The value can contain netmask length. | + | | | Example: ``192.0.2.1/24`` | + +--------------------------+-------------+----------------------------------------------------+ + | ipv4_multicast_addresses | string list | IPv4 multicast addresses applied to this interface.| + +--------------------------+-------------+----------------------------------------------------+ + | time_to_live | int | Time-to-live value for this interface. | + +--------------------------+-------------+----------------------------------------------------+ + | multicast_time_to_live | int | Multicast time-to-live value for this interface. | + +--------------------------+-------------+----------------------------------------------------+ + | gateway | string | Gateway IPv4 address. | + +--------------------------+-------------+----------------------------------------------------+ + | ipv4_autoconf | struct | IPv4 autoconfiguration options. | + +--------------------------+-------------+----------------------------------------------------+ + | dhcpv4 | struct | DHCPv4 client options. | + +--------------------------+-------------+----------------------------------------------------+ + | dhcpv4_server | struct | DHCPv4 server options. | + +--------------------------+-------------+----------------------------------------------------+ + +.. table:: The ``ipv4_autoconf`` options + :align: left + + +--------------------+--------+---------------------------------------------------------------+ + | Option name | Type | Description | + +====================+========+===============================================================+ + | status | bool | Is IPv4 auto-conf enabled for this interface. | + +--------------------+--------+---------------------------------------------------------------+ + +.. table:: The ``dhcpv4`` options + :align: left + + +--------------------+--------+---------------------------------------------------------------+ + | Option name | Type | Description | + +====================+========+===============================================================+ + | status | bool | Is DHCPv4 client enabled for this interface. | + +--------------------+--------+---------------------------------------------------------------+ + +.. table:: The ``dhcpv4_server`` options + :align: left + + +--------------------+--------+---------------------------------------------------------------+ + | Option name | Type | Description | + +====================+========+===============================================================+ + | status | bool | Is DHCPv4 server enabled for this interface. | + +--------------------+--------+---------------------------------------------------------------+ + | base_address | string | Request IPv6 address. | + +--------------------+--------+---------------------------------------------------------------+ + +.. table:: The ``vlan`` options + :align: left + + +-------------+--------+----------------------------------------------------------------------+ + | Option name | Type | Description | + +=============+========+======================================================================+ + | status | bool | Is VLAN enabled for this interface. | + +-------------+--------+----------------------------------------------------------------------+ + | tag | int | VLAN tag applied to this interface. | + +-------------+--------+----------------------------------------------------------------------+ + +.. table:: The ``sntp`` options + :align: left + + +-------------+-------------+-----------------------------------------------------------------+ + | Option name | Type | Description | + +=============+=============+=================================================================+ + | status | bool | Is SNTP enabled. | + +-------------+-------------+-----------------------------------------------------------------+ + | server | string | SNTP server address. | + +-------------+-------------+-----------------------------------------------------------------+ + | timeout | int | SNTP server connection timeout. | + +-------------+-------------+-----------------------------------------------------------------+ + | bind_to | reference | Connect to server using this network interface. | + +-------------+-------------+-----------------------------------------------------------------+ + +.. table:: The ``ieee_802_15_4`` options + :align: left + + +-------------------+-----------+-------------------------------------------------------------+ + | Option name | Type | Description | + +===================+===========+=============================================================+ + | status | bool | Is IEEE 802.15.4 enabled. | + +-------------------+-----------+-------------------------------------------------------------+ + | bind_to | reference | Apply the options to this network interface. | + +-------------------+-------------+-----------------------------------------------------------+ + | pan_id | int | PAN identifier. | + +-------------------+-----------+-------------------------------------------------------------+ + | channel | int | Channel number. | + +-------------------+-----------+-------------------------------------------------------------+ + | tx_power | int | Transmit power. | + +-------------------+-----------+-------------------------------------------------------------+ + | ack_required | bool | Require acknowledgment. | + +-------------------+-----------+-------------------------------------------------------------+ + | security_key | int array | IEEE 802.15.4 security key. Maximum length is 16. | + +-------------------+-----------+-------------------------------------------------------------+ + | security_key_mode | int | IEEE 802.15.4 security key mode. | + +-------------------+-----------+-------------------------------------------------------------+ + | security_level | int | IEEE 802.15.4 security level. | + +-------------------+-----------+-------------------------------------------------------------+ From 01862492690f51369d2cd9d38fbb313fb6402aa6 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Fri, 26 Sep 2025 12:28:25 +0300 Subject: [PATCH 08/16] net: utils: Allow user to pass NULL as mask_len If user is not interested in the mask length, the value can be set to NULL so that it is not been returned to the caller. Signed-off-by: Jukka Rissanen --- subsys/net/ip/utils.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/subsys/net/ip/utils.c b/subsys/net/ip/utils.c index d91b060ef1bb0..65852746a25dd 100644 --- a/subsys/net/ip/utils.c +++ b/subsys/net/ip/utils.c @@ -988,7 +988,7 @@ const char *net_ipaddr_parse_mask(const char *str, size_t str_len, int parsed_mask_len = -1; bool ret = false; - if (str == NULL || str_len == 0 || addr == NULL || mask_len == NULL) { + if (str == NULL || str_len == 0 || addr == NULL) { return NULL; } @@ -1019,7 +1019,10 @@ const char *net_ipaddr_parse_mask(const char *str, size_t str_len, } str_len = mask_ptr - str; - *mask_len = (uint8_t)parsed_mask_len; + + if (mask_len != NULL) { + *mask_len = (uint8_t)parsed_mask_len; + } } #if defined(CONFIG_NET_IPV4) && defined(CONFIG_NET_IPV6) @@ -1038,7 +1041,7 @@ const char *net_ipaddr_parse_mask(const char *str, size_t str_len, return NULL; } - if (parsed_mask_len < 0) { + if (parsed_mask_len < 0 && mask_len != NULL) { if (addr->sa_family == AF_INET) { *mask_len = 32; } else if (addr->sa_family == AF_INET6) { From 761a84ce14855548a3346a581e522043e3c948b2 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Wed, 24 Jan 2024 18:08:37 +0200 Subject: [PATCH 09/16] net: config: Add support for yaml file based configuration If user has supplied a yaml file, it is used as configuration data that is applied to network configuration. In this case the CONFIG_NET_CONFIG_* Kconfig variables used for configuration are ignored. Signed-off-by: Jukka Rissanen --- include/zephyr/net/net_config.h | 1 + subsys/net/lib/config/CMakeLists.txt | 1 + subsys/net/lib/config/Kconfig | 3 + subsys/net/lib/config/ieee802154_settings.c | 21 +- subsys/net/lib/config/ieee802154_settings.h | 8 +- subsys/net/lib/config/init.c | 1094 +++++++++++++------ subsys/net/lib/config/init_clock_sntp.c | 27 +- 7 files changed, 775 insertions(+), 380 deletions(-) diff --git a/include/zephyr/net/net_config.h b/include/zephyr/net/net_config.h index 088ae5b558d4e..a389b77c61e3d 100644 --- a/include/zephyr/net/net_config.h +++ b/include/zephyr/net/net_config.h @@ -14,6 +14,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { diff --git a/subsys/net/lib/config/CMakeLists.txt b/subsys/net/lib/config/CMakeLists.txt index 732509737396f..b68300549be38 100644 --- a/subsys/net/lib/config/CMakeLists.txt +++ b/subsys/net/lib/config/CMakeLists.txt @@ -6,6 +6,7 @@ zephyr_library_compile_definitions_ifdef( ) zephyr_library_sources_ifdef(CONFIG_NET_CONFIG_SETTINGS init.c) +zephyr_library_include_directories(${ZEPHYR_BASE}/subsys/net/ip) if(CONFIG_NET_CONFIG_SETTINGS) zephyr_library_sources_ifdef( diff --git a/subsys/net/lib/config/Kconfig b/subsys/net/lib/config/Kconfig index 39eed79d35c82..0b5fc10c6764c 100644 --- a/subsys/net/lib/config/Kconfig +++ b/subsys/net/lib/config/Kconfig @@ -13,12 +13,15 @@ menuconfig NET_CONFIG_SETTINGS bool "Set network settings for applications" select NET_MGMT select NET_MGMT_EVENT + select EXPERIMENTAL help Allow IP addresses to be set in config file for networking client/server sample applications, or some link-layer dedicated settings like the channel. Beware this is not meant to be used for proper provisioning but quick sampling/testing. + The experimental status is added because this version + starts to use Yaml file based configuration. if NET_CONFIG_SETTINGS diff --git a/subsys/net/lib/config/ieee802154_settings.c b/subsys/net/lib/config/ieee802154_settings.c index 4a142bf75bdf5..0d6752fab87a1 100644 --- a/subsys/net/lib/config/ieee802154_settings.c +++ b/subsys/net/lib/config/ieee802154_settings.c @@ -17,23 +17,14 @@ LOG_MODULE_DECLARE(net_config, CONFIG_NET_CONFIG_LOG_LEVEL); #include #include -int z_net_config_ieee802154_setup(struct net_if *iface) +int z_net_config_ieee802154_setup(struct net_if *iface, + uint16_t channel, + uint16_t pan_id, + int16_t tx_power, + struct ieee802154_security_params *sec_params) { - uint16_t channel = CONFIG_NET_CONFIG_IEEE802154_CHANNEL; - uint16_t pan_id = CONFIG_NET_CONFIG_IEEE802154_PAN_ID; const struct device *const dev = iface == NULL ? DEVICE_DT_GET(DT_CHOSEN(zephyr_ieee802154)) : net_if_get_device(iface); - int16_t tx_power = CONFIG_NET_CONFIG_IEEE802154_RADIO_TX_POWER; - -#ifdef CONFIG_NET_L2_IEEE802154_SECURITY - struct ieee802154_security_params sec_params = { - .key = CONFIG_NET_CONFIG_IEEE802154_SECURITY_KEY, - .key_len = sizeof(CONFIG_NET_CONFIG_IEEE802154_SECURITY_KEY), - .key_mode = CONFIG_NET_CONFIG_IEEE802154_SECURITY_KEY_MODE, - .level = CONFIG_NET_CONFIG_IEEE802154_SECURITY_LEVEL, - }; -#endif /* CONFIG_NET_L2_IEEE802154_SECURITY */ - if (!device_is_ready(dev)) { return -ENODEV; } @@ -62,7 +53,7 @@ int z_net_config_ieee802154_setup(struct net_if *iface) #ifdef CONFIG_NET_L2_IEEE802154_SECURITY if (net_mgmt(NET_REQUEST_IEEE802154_SET_SECURITY_SETTINGS, iface, - &sec_params, sizeof(struct ieee802154_security_params))) { + sec_params, sizeof(struct ieee802154_security_params))) { return -EINVAL; } #endif /* CONFIG_NET_L2_IEEE802154_SECURITY */ diff --git a/subsys/net/lib/config/ieee802154_settings.h b/subsys/net/lib/config/ieee802154_settings.h index 5f0bb70b2490f..d235277fda0d4 100644 --- a/subsys/net/lib/config/ieee802154_settings.h +++ b/subsys/net/lib/config/ieee802154_settings.h @@ -6,10 +6,16 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include + #if defined(CONFIG_NET_L2_IEEE802154) && defined(CONFIG_NET_CONFIG_SETTINGS) struct net_if; -int z_net_config_ieee802154_setup(struct net_if *iface); +int z_net_config_ieee802154_setup(struct net_if *iface, + uint16_t channel, + uint16_t pan_id, + int16_t tx_power, + struct ieee802154_security_params *sec_params); #else #define z_net_config_ieee802154_setup(...) 0 #endif diff --git a/subsys/net/lib/config/init.c b/subsys/net/lib/config/init.c index 22500fd8f8443..34922f1237bfd 100644 --- a/subsys/net/lib/config/init.c +++ b/subsys/net/lib/config/init.c @@ -22,458 +22,643 @@ LOG_MODULE_REGISTER(net_config, CONFIG_NET_CONFIG_LOG_LEVEL); #include #include #include +#include #include #include #include - +#include +#include #include #include "ieee802154_settings.h" +#include "net_private.h" + +#include "network_config.inc" -extern int net_init_clock_via_sntp(void); +const struct networking *net_config_get_init_config(void); + +extern int net_init_clock_via_sntp(struct net_if *iface, + const char *server, + int timeout); static K_SEM_DEFINE(waiter, 0, 1); static K_SEM_DEFINE(counter, 0, UINT_MAX); -static atomic_t services_flags; #if defined(CONFIG_NET_NATIVE) static struct net_mgmt_event_callback mgmt_iface_cb; -#endif -static inline void services_notify_ready(int flags) +static void iface_up_handler(struct net_mgmt_event_callback *cb, + uint64_t mgmt_event, struct net_if *iface) { - atomic_or(&services_flags, flags); - k_sem_give(&waiter); + if (mgmt_event == NET_EVENT_IF_UP) { + NET_INFO("Interface %d (%p) coming up", + net_if_get_by_iface(iface), iface); + + k_sem_reset(&counter); + k_sem_give(&waiter); + } } -static inline bool services_are_ready(int flags) +static bool check_interface(struct net_if *iface) { - return (atomic_get(&services_flags) & flags) == flags; -} + if (net_if_is_up(iface)) { + k_sem_reset(&counter); + k_sem_give(&waiter); + return true; + } -#if defined(CONFIG_NET_NATIVE_IPV4) + NET_INFO("Waiting interface %d (%p) to be up...", + net_if_get_by_iface(iface), iface); -#if defined(CONFIG_NET_DHCPV4) + net_mgmt_init_event_callback(&mgmt_iface_cb, iface_up_handler, + NET_EVENT_IF_UP); + net_mgmt_add_event_callback(&mgmt_iface_cb); -static void setup_dhcpv4(struct net_if *iface) + return false; +} +#else +static bool check_interface(struct net_if *iface) { - NET_INFO("Running dhcpv4 client..."); + k_sem_reset(&counter); + k_sem_give(&waiter); - net_dhcpv4_start(iface); + return true; } - -static void print_dhcpv4_info(struct net_if *iface) -{ -#if CONFIG_NET_CONFIG_LOG_LEVEL >= LOG_LEVEL_INF - char hr_addr[NET_IPV4_ADDR_LEN]; #endif - ARRAY_FOR_EACH(iface->config.ip.ipv4->unicast, i) { - struct net_if_addr *if_addr = - &iface->config.ip.ipv4->unicast[i].ipv4; - if (if_addr->addr_type != NET_ADDR_DHCP || - !if_addr->is_used) { - continue; - } +int net_config_init_by_iface(struct net_if *iface, const char *app_info, + uint32_t flags, int32_t timeout) +{ +#define LOOP_DIVIDER 10 + int loop = timeout / LOOP_DIVIDER; + int count; -#if CONFIG_NET_CONFIG_LOG_LEVEL >= LOG_LEVEL_INF - NET_INFO("IPv4 address: %s", - net_addr_ntop(AF_INET, &if_addr->address.in_addr, - hr_addr, sizeof(hr_addr))); - NET_INFO("Lease time: %u seconds", - iface->config.dhcpv4.lease_time); - NET_INFO("Subnet: %s", - net_addr_ntop(AF_INET, - &iface->config.ip.ipv4->unicast[i].netmask, - hr_addr, sizeof(hr_addr))); - NET_INFO("Router: %s", - net_addr_ntop(AF_INET, &iface->config.ip.ipv4->gw, - hr_addr, sizeof(hr_addr))); -#endif - break; + if (app_info) { + NET_INFO("%s", app_info); } -} -#else -#define setup_dhcpv4(...) -#define print_dhcpv4_info(...) -#endif /* CONFIG_NET_DHCPV4 */ - -static struct net_mgmt_event_callback mgmt4_cb; + if (!iface) { + iface = net_if_get_default(); + } -static void ipv4_addr_add_handler(struct net_mgmt_event_callback *cb, - uint64_t mgmt_event, - struct net_if *iface) -{ - if (mgmt_event == NET_EVENT_IPV4_ADDR_ADD) { - print_dhcpv4_info(iface); + if (!iface) { + return -ENOENT; + } - if (!IS_ENABLED(CONFIG_NET_IPV4_ACD)) { - services_notify_ready(NET_CONFIG_NEED_IPV4); - } + if (net_if_flag_is_set(iface, NET_IF_NO_AUTO_START)) { + return -ENETDOWN; } - if (mgmt_event == NET_EVENT_IPV4_ACD_SUCCEED) { - services_notify_ready(NET_CONFIG_NEED_IPV4); + if (timeout < 0) { + count = -1; + } else if (timeout == 0) { + count = 0; + } else { + count = LOOP_DIVIDER; } -} -#if defined(CONFIG_NET_VLAN) && (CONFIG_NET_CONFIG_MY_VLAN_ID > 0) + /* First make sure that network interface is up */ + if (check_interface(iface) == false) { + k_sem_init(&counter, 1, K_SEM_MAX_LIMIT); -static void setup_vlan(struct net_if *iface) -{ - int ret = net_eth_vlan_enable(iface, CONFIG_NET_CONFIG_MY_VLAN_ID); + while (count-- > 0) { + if (!k_sem_count_get(&counter)) { + break; + } - if (ret < 0) { - NET_ERR("Network interface %d (%p): cannot set VLAN tag (%d)", - net_if_get_by_iface(iface), iface, ret); + if (k_sem_take(&waiter, K_MSEC(loop))) { + if (!k_sem_count_get(&counter)) { + break; + } + } + } + +#if defined(CONFIG_NET_NATIVE) + net_mgmt_del_event_callback(&mgmt_iface_cb); +#endif } -} -#else -#define setup_vlan(...) -#endif /* CONFIG_NET_VLAN && (CONFIG_NET_CONFIG_MY_VLAN_ID > 0) */ + /* Network interface did not come up. */ + if (timeout > 0 && count < 0) { + NET_ERR("Timeout while waiting network %s", "interface"); + return -ENETDOWN; + } -#if defined(CONFIG_NET_NATIVE_IPV4) && !defined(CONFIG_NET_DHCPV4) && \ - !defined(CONFIG_NET_CONFIG_MY_IPV4_ADDR) -#error "You need to define an IPv4 address or enable DHCPv4!" -#endif + return 0; +} -static void setup_ipv4(struct net_if *iface) +static struct net_if *get_interface(const struct networking *config, + int config_ifindex, + const struct device *dev, + const char **iface_name) { -#if CONFIG_NET_CONFIG_LOG_LEVEL >= LOG_LEVEL_INF - char hr_addr[NET_IPV4_ADDR_LEN]; -#endif - struct in_addr addr, netmask; + const struct net_cfg_interfaces *cfg; + struct net_if *iface = NULL; + const char *name; - if (IS_ENABLED(CONFIG_NET_IPV4_ACD) || IS_ENABLED(CONFIG_NET_DHCPV4)) { - net_mgmt_init_event_callback(&mgmt4_cb, ipv4_addr_add_handler, - NET_EVENT_IPV4_ADDR_ADD | - NET_EVENT_IPV4_ACD_SUCCEED); - net_mgmt_add_event_callback(&mgmt4_cb); - } + NET_ASSERT(IN_RANGE(config_ifindex, 0, ARRAY_SIZE(config->interfaces) - 1)); - if (sizeof(CONFIG_NET_CONFIG_MY_IPV4_ADDR) == 1) { - /* Empty address, skip setting ANY address in this case */ - return; - } + cfg = &config->interfaces[config_ifindex]; - if (net_addr_pton(AF_INET, CONFIG_NET_CONFIG_MY_IPV4_ADDR, &addr)) { - NET_ERR("Invalid address: %s", CONFIG_NET_CONFIG_MY_IPV4_ADDR); - return; + name = cfg->set_name; + if (name != NULL) { + iface = net_if_get_by_index(net_if_get_by_name(name)); } -#if defined(CONFIG_NET_DHCPV4) - /* In case DHCP is enabled, make the static address tentative, - * to allow DHCP address to override it. This covers a usecase - * of "there should be a static IP address for DHCP-less setups", - * but DHCP should override it (to use it, NET_IF_MAX_IPV4_ADDR - * should be set to 1). There is another usecase: "there should - * always be static IP address, and optionally, DHCP address". - * For that to work, NET_IF_MAX_IPV4_ADDR should be 2 (or more). - * (In this case, an app will need to bind to the needed addr - * explicitly.) - */ - net_if_ipv4_addr_add(iface, &addr, NET_ADDR_OVERRIDABLE, 0); -#else - net_if_ipv4_addr_add(iface, &addr, NET_ADDR_MANUAL, 0); -#endif - -#if CONFIG_NET_CONFIG_LOG_LEVEL >= LOG_LEVEL_INF - NET_INFO("IPv4 address: %s", - net_addr_ntop(AF_INET, &addr, hr_addr, sizeof(hr_addr))); -#endif + if (iface == NULL) { + name = cfg->name; - if (sizeof(CONFIG_NET_CONFIG_MY_IPV4_NETMASK) > 1) { - /* If not empty */ - if (net_addr_pton(AF_INET, CONFIG_NET_CONFIG_MY_IPV4_NETMASK, - &netmask)) { - NET_ERR("Invalid netmask: %s", - CONFIG_NET_CONFIG_MY_IPV4_NETMASK); - } else { - net_if_ipv4_set_netmask_by_addr(iface, &addr, &netmask); + if (name != NULL) { + iface = net_if_get_by_index(net_if_get_by_name(name)); } } - if (sizeof(CONFIG_NET_CONFIG_MY_IPV4_GW) > 1) { - /* If not empty */ - if (net_addr_pton(AF_INET, CONFIG_NET_CONFIG_MY_IPV4_GW, - &addr)) { - NET_ERR("Invalid gateway: %s", - CONFIG_NET_CONFIG_MY_IPV4_GW); - } else { - net_if_ipv4_set_gw(iface, &addr); + if (iface == NULL) { + /* Get the interface by device */ + const struct device *iface_dev; + + name = cfg->device_name; + + iface_dev = device_get_binding(name); + if (iface_dev) { + iface = net_if_lookup_by_dev(iface_dev); } } - if (!IS_ENABLED(CONFIG_NET_IPV4_ACD)) { - services_notify_ready(NET_CONFIG_NEED_IPV4); + /* Use the default interface if nothing is found */ + if (iface == NULL && name == NULL) { + static char ifname[CONFIG_NET_INTERFACE_NAME_LEN + 1]; + + iface = net_if_get_default(); + (void)net_if_get_name(iface, ifname, sizeof(ifname)); + name = ifname; } -} -#else -#define setup_ipv4(...) -#define setup_dhcpv4(...) -#define setup_vlan(...) -#endif /* CONFIG_NET_NATIVE_IPV4*/ + if (iface_name != NULL) { + *iface_name = name; + } -#if defined(CONFIG_NET_NATIVE_IPV6) + return iface; +} -#if defined(CONFIG_NET_DHCPV6) -static void setup_dhcpv6(struct net_if *iface) +#if defined(CONFIG_NET_IPV6) || defined(CONFIG_NET_IPV4) +static bool parse_mask(const char *str, size_t str_len, + struct sockaddr *addr, uint8_t *mask_len) { - struct net_dhcpv6_params params = { - .request_addr = IS_ENABLED(CONFIG_NET_CONFIG_DHCPV6_REQUEST_ADDR), - .request_prefix = IS_ENABLED(CONFIG_NET_CONFIG_DHCPV6_REQUEST_PREFIX), - }; + const char *result; - NET_INFO("Running dhcpv6 client..."); + result = net_ipaddr_parse_mask(str, str_len, addr, mask_len); + if (result == NULL || result[0] != '\0') { + return false; + } - net_dhcpv6_start(iface, ¶ms); + return true; } -#else /* CONFIG_NET_DHCPV6 */ -#define setup_dhcpv6(...) -#endif /* CONFIG_NET_DHCPV6 */ - -#if !defined(CONFIG_NET_CONFIG_DHCPV6_REQUEST_ADDR) && \ - !defined(CONFIG_NET_CONFIG_MY_IPV6_ADDR) -#error "You need to define an IPv6 address or enable DHCPv6!" -#endif - -static struct net_mgmt_event_callback mgmt6_cb; -static struct in6_addr laddr; +#endif /* CONFIG_NET_IPV6 || CONFIG_NET_IPV4 */ -static void ipv6_event_handler(struct net_mgmt_event_callback *cb, - uint64_t mgmt_event, struct net_if *iface) +static void ipv6_setup(struct net_if *iface, + int ifindex, + const struct net_cfg_interfaces *cfg) { - struct net_if_ipv6 *ipv6 = iface->config.ip.ipv6; - int i; +#if defined(CONFIG_NET_IPV6) + const struct networking_ipv6 *ipv6 = &cfg->ipv6; + struct net_if_mcast_addr *ifmaddr; + struct net_if_addr *ifaddr; + bool ret; - if (!ipv6) { + if (!ipv6->status) { + NET_DBG("Skipping IPv%c setup for iface %d", '6', ifindex); + net_if_flag_clear(iface, NET_IF_IPV6); return; } - if (mgmt_event == NET_EVENT_IPV6_ADDR_ADD) { - /* save the last added IP address for this interface */ - for (i = NET_IF_MAX_IPV6_ADDR - 1; i >= 0; i--) { - if (ipv6->unicast[i].is_used) { - memcpy(&laddr, - &ipv6->unicast[i].address.in6_addr, - sizeof(laddr)); - break; - } + /* First set all the static addresses and then enable DHCP */ + ARRAY_FOR_EACH(ipv6->ipv6_addresses, j) { + struct sockaddr_in6 addr = { 0 }; + uint8_t prefix_len; + + if (ipv6->ipv6_addresses[j].value == NULL || + ipv6->ipv6_addresses[j].value[0] == '\0') { + continue; } - } - if (mgmt_event == NET_EVENT_IPV6_DAD_SUCCEED) { -#if CONFIG_NET_CONFIG_LOG_LEVEL >= LOG_LEVEL_INF - char hr_addr[NET_IPV6_ADDR_LEN]; -#endif - struct net_if_addr *ifaddr; + ret = parse_mask(ipv6->ipv6_addresses[j].value, + strlen(ipv6->ipv6_addresses[j].value), + (struct sockaddr *)&addr, &prefix_len); + if (!ret) { + NET_DBG("Invalid IPv%c %s address \"%s\"", '6', "unicast", + ipv6->ipv6_addresses[j].value); + continue; + } - ifaddr = net_if_ipv6_addr_lookup(&laddr, &iface); - if (!ifaddr || - !(net_ipv6_addr_cmp(&ifaddr->address.in6_addr, &laddr) && - ifaddr->addr_state == NET_ADDR_PREFERRED)) { - /* Address is not yet properly setup */ - return; + if (net_ipv6_is_addr_unspecified(&addr.sin6_addr)) { + continue; } -#if CONFIG_NET_CONFIG_LOG_LEVEL >= LOG_LEVEL_INF - NET_INFO("IPv6 address: %s", - net_addr_ntop(AF_INET6, &laddr, hr_addr, NET_IPV6_ADDR_LEN)); + ifaddr = net_if_ipv6_addr_add( + iface, + &addr.sin6_addr, + /* If DHCPv6 is enabled, then allow address + * to be overridden. + */ + COND_CODE_1(CONFIG_NET_DHCPV6, + (ipv6->dhcpv6.status), + (false)) ? + NET_ADDR_OVERRIDABLE : NET_ADDR_MANUAL, + 0); + if (ifaddr == NULL) { + NET_DBG("Cannot %s %s %s to iface %d", "add", "address", + net_sprint_ipv6_addr(&addr.sin6_addr), + ifindex); + continue; + } - if (ifaddr->addr_type == NET_ADDR_DHCP) { - char remaining_str[] = "infinite"; - uint32_t remaining; + NET_DBG("Added %s address %s to iface %d", "unicast", + net_sprint_ipv6_addr(&addr.sin6_addr), + ifindex); + } - remaining = net_timeout_remaining(&ifaddr->lifetime, - k_uptime_get_32()); + ARRAY_FOR_EACH(ipv6->ipv6_multicast_addresses, j) { + struct sockaddr_in6 addr = { 0 }; - if (!ifaddr->is_infinite) { - snprintk(remaining_str, sizeof(remaining_str), - "%u", remaining); - } + if (ipv6->ipv6_multicast_addresses[j].value == NULL || + ipv6->ipv6_multicast_addresses[j].value[0] == '\0') { + continue; + } - NET_INFO("Lifetime: %s seconds", remaining_str); + ret = parse_mask(ipv6->ipv6_multicast_addresses[j].value, + strlen(ipv6->ipv6_multicast_addresses[j].value), + (struct sockaddr *)&addr, NULL); + if (!ret) { + NET_DBG("Invalid IPv%c %s address \"%s\"", '6', "multicast", + ipv6->ipv6_multicast_addresses[j].value); + continue; } -#endif - services_notify_ready(NET_CONFIG_NEED_IPV6); - } + if (net_ipv6_is_addr_unspecified(&addr.sin6_addr)) { + continue; + } + + ifmaddr = net_if_ipv6_maddr_add(iface, &addr.sin6_addr); + if (ifmaddr == NULL) { + NET_DBG("Cannot %s %s %s to iface %d", "add", "address", + net_sprint_ipv6_addr(&addr.sin6_addr), + ifindex); + continue; + } - if (mgmt_event == NET_EVENT_IPV6_ROUTER_ADD) { - services_notify_ready(NET_CONFIG_NEED_ROUTER); + NET_DBG("Added %s address %s to iface %d", "multicast", + net_sprint_ipv6_addr(&addr.sin6_addr), + ifindex); } -} -static void setup_ipv6(struct net_if *iface, uint32_t flags) -{ - struct net_if_addr *ifaddr; - uint64_t mask = NET_EVENT_IPV6_DAD_SUCCEED; + ARRAY_FOR_EACH(ipv6->prefixes, j) { + struct net_if_ipv6_prefix *prefix; + struct sockaddr_in6 addr = { 0 }; - if (sizeof(CONFIG_NET_CONFIG_MY_IPV6_ADDR) == 1) { - /* Empty address, skip setting ANY address in this case */ - goto exit; + if (ipv6->prefixes[j].address == NULL || + ipv6->prefixes[j].address[0] == '\0') { + continue; + } + + ret = parse_mask(ipv6->prefixes[j].address, + strlen(ipv6->prefixes[j].address), + (struct sockaddr *)&addr, NULL); + if (!ret) { + NET_DBG("Invalid IPv%c %s address \"%s\"", '6', "prefix", + ipv6->prefixes[j].address); + continue; + } + + if (net_ipv6_is_addr_unspecified(&addr.sin6_addr)) { + continue; + } + + prefix = net_if_ipv6_prefix_add(iface, + &addr.sin6_addr, + ipv6->prefixes[j].len, + ipv6->prefixes[j].lifetime); + if (prefix == NULL) { + NET_DBG("Cannot %s %s %s to iface %d", "add", "prefix", + net_sprint_ipv6_addr(&addr.sin6_addr), + ifindex); + continue; + } + + NET_DBG("Added %s address %s to iface %d", "prefix", + net_sprint_ipv6_addr(&addr.sin6_addr), + ifindex); } - if (net_addr_pton(AF_INET6, CONFIG_NET_CONFIG_MY_IPV6_ADDR, &laddr)) { - NET_ERR("Invalid address: %s", CONFIG_NET_CONFIG_MY_IPV6_ADDR); - /* some interfaces may add IP address later */ - mask |= NET_EVENT_IPV6_ADDR_ADD; + if (ipv6->hop_limit > 0) { + net_if_ipv6_set_hop_limit(iface, ipv6->hop_limit); } - if (flags & NET_CONFIG_NEED_ROUTER) { - mask |= NET_EVENT_IPV6_ROUTER_ADD; + if (ipv6->multicast_hop_limit > 0) { + net_if_ipv6_set_mcast_hop_limit(iface, ipv6->multicast_hop_limit); } - net_mgmt_init_event_callback(&mgmt6_cb, ipv6_event_handler, mask); - net_mgmt_add_event_callback(&mgmt6_cb); + if (COND_CODE_1(CONFIG_NET_DHCPV6, (ipv6->dhcpv6.status), (false))) { + struct net_dhcpv6_params params = { + .request_addr = COND_CODE_1(CONFIG_NET_DHCPV6, + (ipv6->dhcpv6.do_request_address), + (false)), + .request_prefix = COND_CODE_1(CONFIG_NET_DHCPV6, + (ipv6->dhcpv6.do_request_prefix), + (false)), + }; - ifaddr = net_if_ipv6_addr_add(iface, &laddr, NET_ADDR_MANUAL, 0); - if (!ifaddr) { - NET_ERR("Cannot add %s to interface", - CONFIG_NET_CONFIG_MY_IPV6_ADDR); + net_dhcpv6_start(iface, ¶ms); } +#endif +} -exit: +static void ipv4_setup(struct net_if *iface, + int ifindex, + const struct net_cfg_interfaces *cfg) +{ +#if defined(CONFIG_NET_IPV4) + const struct networking_ipv4 *ipv4 = &cfg->ipv4; + struct net_if_addr *ifaddr; + struct net_if_mcast_addr *ifmaddr; + bool ret; - if (!IS_ENABLED(CONFIG_NET_IPV6_DAD) || - net_if_flag_is_set(iface, NET_IF_IPV6_NO_ND)) { - services_notify_ready(NET_CONFIG_NEED_IPV6); + if (!ipv4->status) { + NET_DBG("Skipping IPv%c setup for iface %d", '4', ifindex); + net_if_flag_clear(iface, NET_IF_IPV4); + return; } - return; -} + /* First set all the static addresses and then enable DHCP */ + ARRAY_FOR_EACH(ipv4->ipv4_addresses, j) { + struct sockaddr_in addr = { 0 }; + uint8_t mask_len = 0; -#else -#define setup_ipv6(...) -#define setup_dhcpv6(...) -#endif /* CONFIG_NET_IPV6 */ + if (ipv4->ipv4_addresses[j].value == NULL || + ipv4->ipv4_addresses[j].value[0] == '\0') { + continue; + } -#if defined(CONFIG_NET_NATIVE) -static void iface_up_handler(struct net_mgmt_event_callback *cb, - uint64_t mgmt_event, struct net_if *iface) -{ - if (mgmt_event == NET_EVENT_IF_UP) { - NET_INFO("Interface %d (%p) coming up", - net_if_get_by_iface(iface), iface); + ret = parse_mask(ipv4->ipv4_addresses[j].value, + strlen(ipv4->ipv4_addresses[j].value), + (struct sockaddr *)&addr, &mask_len); + if (!ret) { + NET_DBG("Invalid IPv%c %s address \"%s\"", '4', "unicast", + ipv4->ipv4_addresses[j].value); + continue; + } - k_sem_reset(&counter); - k_sem_give(&waiter); - } -} + if (net_ipv4_is_addr_unspecified(&addr.sin_addr)) { + continue; + } -static bool check_interface(struct net_if *iface) -{ - if (net_if_is_up(iface)) { - k_sem_reset(&counter); - k_sem_give(&waiter); - return true; - } + ifaddr = net_if_ipv4_addr_add( + iface, + &addr.sin_addr, + /* If DHCPv4 is enabled, then allow address + * to be overridden. + */ + COND_CODE_1(CONFIG_NET_DHCPV4, + (ipv4->dhcpv4.status), + (false)) ? + NET_ADDR_OVERRIDABLE : NET_ADDR_MANUAL, + 0); + + if (ifaddr == NULL) { + NET_DBG("Cannot %s %s %s to iface %d", "add", "address", + net_sprint_ipv4_addr(&addr.sin_addr), + ifindex); + continue; + } - NET_INFO("Waiting interface %d (%p) to be up...", - net_if_get_by_iface(iface), iface); + /* Wait until Address Conflict Detection is ok. + * DHCPv4 server startup will fail if the address is not in + * preferred state. + */ + if (IS_ENABLED(CONFIG_NET_IPV4_ACD) && + (COND_CODE_1(CONFIG_NET_DHCPV4_SERVER, + (ipv4->dhcpv4_server.status), (false)))) { + if (WAIT_FOR(ifaddr->addr_state == NET_ADDR_PREFERRED, + USEC_PER_MSEC * MSEC_PER_SEC * 2 /* 2 sec */, + k_msleep(100)) == false) { + NET_DBG("Address %s still is not preferred", + net_sprint_ipv4_addr(&addr.sin_addr)); + } + } - net_mgmt_init_event_callback(&mgmt_iface_cb, iface_up_handler, - NET_EVENT_IF_UP); - net_mgmt_add_event_callback(&mgmt_iface_cb); + NET_DBG("Added %s address %s to iface %d", "unicast", + net_sprint_ipv4_addr(&addr.sin_addr), + ifindex); - return false; -} -#else -static bool check_interface(struct net_if *iface) -{ - k_sem_reset(&counter); - k_sem_give(&waiter); + if (mask_len > 0) { + struct in_addr netmask = { 0 }; - return true; -} -#endif + netmask.s_addr = BIT_MASK(mask_len); -int net_config_init_by_iface(struct net_if *iface, const char *app_info, - uint32_t flags, int32_t timeout) -{ -#define LOOP_DIVIDER 10 - int loop = timeout / LOOP_DIVIDER; - int count; + net_if_ipv4_set_netmask_by_addr(iface, + &addr.sin_addr, + &netmask); - if (app_info) { - NET_INFO("%s", app_info); + NET_DBG("Added %s address %s to iface %d", "netmask", + net_sprint_ipv4_addr(&netmask), + ifindex); + } } - if (!iface) { - iface = net_if_get_default(); - } + ARRAY_FOR_EACH(ipv4->ipv4_multicast_addresses, j) { + struct sockaddr_in addr = { 0 }; - if (!iface) { - return -ENOENT; + if (ipv4->ipv4_multicast_addresses[j].value == NULL || + ipv4->ipv4_multicast_addresses[j].value[0] == '\0') { + continue; + } + + ret = parse_mask(ipv4->ipv4_multicast_addresses[j].value, + strlen(ipv4->ipv4_multicast_addresses[j].value), + (struct sockaddr *)&addr, NULL); + if (!ret) { + NET_DBG("Invalid IPv%c %s address \"%s\"", '4', "multicast", + ipv4->ipv4_addresses[j].value); + continue; + } + + if (net_ipv4_is_addr_unspecified(&addr.sin_addr)) { + continue; + } + + ifmaddr = net_if_ipv4_maddr_add(iface, &addr.sin_addr); + if (ifmaddr == NULL) { + NET_DBG("Cannot %s %s %s to iface %d", "add", "address", + net_sprint_ipv4_addr(&addr.sin_addr), + ifindex); + continue; + } + + NET_DBG("Added %s address %s to iface %d", "multicast", + net_sprint_ipv4_addr(&addr.sin_addr), + ifindex); } - if (net_if_flag_is_set(iface, NET_IF_NO_AUTO_START)) { - return -ENETDOWN; + if (ipv4->time_to_live > 0) { + net_if_ipv4_set_ttl(iface, ipv4->time_to_live); } - if (timeout < 0) { - count = -1; - } else if (timeout == 0) { - count = 0; - } else { - count = LOOP_DIVIDER; + if (ipv4->multicast_time_to_live > 0) { + net_if_ipv4_set_mcast_ttl(iface, ipv4->multicast_time_to_live); } - /* First make sure that network interface is up */ - if (check_interface(iface) == false) { - k_sem_init(&counter, 1, K_SEM_MAX_LIMIT); + if (ipv4->gateway != NULL && ipv4->gateway[0] != '\0') { + struct sockaddr_in addr = { 0 }; - while (count-- > 0) { - if (!k_sem_count_get(&counter)) { - break; + ret = parse_mask(ipv4->gateway, + strlen(ipv4->gateway), + (struct sockaddr *)&addr, NULL); + if (!ret) { + NET_DBG("Invalid IPv%c %s address \"%s\"", '4', "geteway", + ipv4->gateway); + } else { + if (!net_ipv4_is_addr_unspecified(&addr.sin_addr)) { + net_if_ipv4_set_gw(iface, &addr.sin_addr); + + NET_DBG("Added %s address %s to iface %d", "gateway", + net_sprint_ipv4_addr(&addr.sin_addr), + ifindex); } + } + } - if (k_sem_take(&waiter, K_MSEC(loop))) { - if (!k_sem_count_get(&counter)) { - break; + if (COND_CODE_1(CONFIG_NET_DHCPV4, (ipv4->dhcpv4.status), (false))) { + NET_DBG("DHCPv4 client started"); + net_dhcpv4_start(iface); + } + + if (COND_CODE_1(CONFIG_NET_DHCPV4_SERVER, + (ipv4->dhcpv4_server.status), (false))) { + struct sockaddr_in addr = { 0 }; + + if (ipv4->dhcpv4_server.base_address != NULL) { + ret = parse_mask(ipv4->dhcpv4_server.base_address, + strlen(ipv4->dhcpv4_server.base_address), + (struct sockaddr *)&addr, NULL); + if (!ret) { + NET_DBG("Invalid IPv%c %s address \"%s\"", '4', "DHCPv4 base", + ipv4->dhcpv4_server.base_address); + } else { + int retval; + + retval = net_dhcpv4_server_start(iface, + COND_CODE_1(CONFIG_NET_DHCPV4_SERVER, + (&addr.sin_addr), + (&((struct in_addr){ 0 })))); + if (retval < 0) { + NET_DBG("DHCPv4 server start failed (%d)", retval); + } else { + NET_DBG("DHCPv4 server started"); } } } + } -#if defined(CONFIG_NET_NATIVE) - net_mgmt_del_event_callback(&mgmt_iface_cb); -#endif + if (COND_CODE_1(CONFIG_NET_IPV4_AUTO, (ipv4->ipv4_autoconf.status), (false))) { + NET_DBG("IPv4 autoconf started"); + net_ipv4_autoconf_start(iface); } +#endif +} - setup_vlan(iface); - setup_ipv4(iface); - setup_dhcpv4(iface); - setup_ipv6(iface, flags); - setup_dhcpv6(iface); +static void vlan_setup(const struct networking *config, + const struct net_cfg_interfaces *cfg) +{ +#if defined(CONFIG_NET_VLAN) + const struct networking_vlan *vlan = &cfg->vlan; + struct net_if *bound = NULL; + int ret, ifindex; - /* Network interface did not come up. */ - if (timeout > 0 && count < 0) { - NET_ERR("Timeout while waiting network %s", "interface"); - return -ENETDOWN; + if (!vlan->status) { + return; } - /* Loop here until we are ready to continue. As we might need - * to wait multiple events, sleep smaller amounts of data. - */ - while (!services_are_ready(flags) && count-- > 0) { - k_sem_take(&waiter, K_MSEC(loop)); + bound = get_interface(config, cfg->bind_to - 1, NULL, NULL); + if (bound == NULL) { + NET_DBG("Cannot find VLAN bound interface %d", cfg->bind_to - 1); + return; } - if (count == -1 && timeout > 0) { - NET_ERR("Timeout while waiting network %s", "setup"); - return -ETIMEDOUT; + ifindex = net_if_get_by_iface(bound); + + ret = net_eth_vlan_enable(bound, vlan->tag); + if (ret < 0) { + NET_DBG("Cannot %s %s %s to iface %d", "add", "VLAN", "tag", ifindex); + NET_DBG("Cannot enable %s for %s %d (%d)", "VLAN", "tag", vlan->tag, ret); + return; } - return 0; + NET_DBG("Added %s %s %d to iface %d", "VLAN", "tag", vlan->tag, + net_if_get_by_iface(net_eth_get_vlan_iface( + net_if_get_by_index(ifindex), + vlan->tag))); + + if (cfg->set_name != NULL) { + struct net_if *iface; + + iface = net_eth_get_vlan_iface(bound, vlan->tag); + + ret = net_if_set_name(iface, cfg->set_name); + if (ret < 0) { + NET_DBG("Cannot rename interface %d to \"%s\" (%d)", + ifindex, cfg->set_name, ret); + return; + } + + NET_DBG("Changed interface %d name to \"%s\"", ifindex, + cfg->set_name); + } +#endif } -int net_config_init(const char *app_info, uint32_t flags, - int32_t timeout) +static void virtual_iface_setup(struct net_if *iface, + int ifindex, + const struct networking *config, + const struct net_cfg_interfaces *cfg) { - return net_config_init_by_iface(NULL, app_info, flags, timeout); +#if defined(CONFIG_NET_L2_VIRTUAL) + const struct virtual_interface_api *api = net_if_get_device(iface)->api; + struct net_if *bound = NULL; + int ret; + + if (net_if_l2(iface) != &NET_L2_GET_NAME(VIRTUAL)) { + return; + } + + /* VLAN interfaces are handled separately */ + if (api->get_capabilities(iface) & VIRTUAL_INTERFACE_VLAN) { + return; + } + + if (cfg->bind_to > 0) { + bound = get_interface(config, cfg->bind_to - 1, NULL, NULL); + } + + if (bound == NULL) { + NET_DBG("Cannot %s %s %s to iface %d", "add", "virtual", "interface", + cfg->bind_to - 1); + return; + } + + ret = net_virtual_interface_attach(iface, bound); + if (ret < 0) { + if (ret != -EALREADY) { + NET_DBG("Cannot %s %s %s to iface %d (%d)", "attach", + "virtual", "interface", + net_if_get_by_iface(bound), ret); + } + + return; + } + + NET_DBG("Added %s %s %d to iface %d", "virtual", "interface", ifindex, + net_if_get_by_iface(bound)); +#endif } static void iface_find_cb(struct net_if *iface, void *user_data) @@ -487,29 +672,15 @@ static void iface_find_cb(struct net_if *iface, void *user_data) } } -int net_config_init_app(const struct device *dev, const char *app_info) +static int wait_for_interface(const struct net_cfg_interfaces *ifaces, + size_t iface_count) { struct net_if *iface = NULL; - uint32_t flags = 0U; - int ret; - if (dev) { - iface = net_if_lookup_by_dev(dev); - if (iface == NULL) { - NET_WARN("No interface for device %p, using default", - dev); - } - } + ARG_UNUSED(ifaces); + ARG_UNUSED(iface_count); - ret = z_net_config_ieee802154_setup(iface); - if (ret < 0) { - NET_ERR("Cannot setup IEEE 802.15.4 interface (%d)", ret); - } - - /* Only try to use a network interface that is auto started */ - if (iface == NULL) { - net_if_foreach(iface_find_cb, &iface); - } + net_if_foreach(iface_find_cb, &iface); if (!iface) { NET_WARN("No auto-started network interface - " @@ -517,28 +688,220 @@ int net_config_init_app(const struct device *dev, const char *app_info) return 0; } - if (IS_ENABLED(CONFIG_NET_CONFIG_NEED_IPV6)) { - flags |= NET_CONFIG_NEED_IPV6; + /* TODO: implement waiting of interface(s) */ + return 0; +} + +struct iface_str_flags { + enum net_if_flag flag; + const char * const str; +}; + +#define FLAG(val) { .flag = val, .str = STRINGIFY(val) } + +#if defined(CONFIG_NET_TEST) +enum net_if_flag get_iface_flag(const char *flag_str, bool *clear) +#else +static enum net_if_flag get_iface_flag(const char *flag_str, bool *clear) +#endif +{ + static const struct iface_str_flags flags[] = { + FLAG(NET_IF_POINTOPOINT), + FLAG(NET_IF_PROMISC), + FLAG(NET_IF_NO_AUTO_START), + FLAG(NET_IF_FORWARD_MULTICASTS), + FLAG(NET_IF_IPV6_NO_ND), + FLAG(NET_IF_IPV6_NO_MLD), + { 0, NULL } + }; + + if (flag_str == NULL || flag_str[0] == '\0') { + return NET_IF_NUM_FLAGS; + } + + if (flag_str[0] == '^') { + *clear = true; + } else { + *clear = false; } - if (IS_ENABLED(CONFIG_NET_CONFIG_NEED_IPV6_ROUTER)) { - flags |= NET_CONFIG_NEED_ROUTER; + ARRAY_FOR_EACH(flags, i) { + if (strcmp(flags[i].str, &flag_str[(*clear) ? 1 : 0]) == 0) { + return flags[i].flag; + } } - if (IS_ENABLED(CONFIG_NET_CONFIG_NEED_IPV4)) { - flags |= NET_CONFIG_NEED_IPV4; + return NET_IF_NUM_FLAGS; +} + +static int process_iface_flag(struct net_if *iface, const char *flag_str) +{ + enum net_if_flag flag; + bool clear; + + flag = get_iface_flag(flag_str, &clear); + if (flag == NET_IF_NUM_FLAGS) { + return -ENOENT; + } + + if (clear) { + NET_DBG("%s flag %s for interface %d", "Clear", + flag_str + 1, net_if_get_by_iface(iface)); + net_if_flag_clear(iface, flag); + } else { + NET_DBG("%s flag %s for interface %d", "Set", + flag_str, net_if_get_by_iface(iface)); + net_if_flag_set(iface, flag); } - /* Initialize the application automatically if needed */ - ret = net_config_init_by_iface(iface, app_info, flags, - CONFIG_NET_CONFIG_INIT_TIMEOUT * MSEC_PER_SEC); + return 0; +} + +static int generated_net_config_init_app(const struct device *dev, + const char *app_info) +{ + const struct networking *config; + int ret, ifindex; + + config = net_config_get_init_config(); + if (config == NULL) { + NET_ERR("Network configuration not found."); + return -ENOENT; + } + + ret = wait_for_interface(config->interfaces, + sizeof(config->interfaces)); if (ret < 0) { - NET_ERR("Network initialization failed (%d)", ret); + NET_WARN("Timeout while waiting network interfaces (%d)", ret); + return ret; } - if (IS_ENABLED(CONFIG_NET_CONFIG_CLOCK_SNTP_INIT) && - !IS_ENABLED(CONFIG_NET_CONFIG_SNTP_INIT_USE_CONNECTION_MANAGER)) { - net_init_clock_via_sntp(); + ARRAY_FOR_EACH(config->interfaces, i) { + const struct net_cfg_interfaces *cfg; + struct net_if *iface; + const char *name; + + cfg = &config->interfaces[i]; + + /* We first need to setup any VLAN interfaces so that other + * interfaces can use them (the interface name etc are correctly + * set so that referencing works ok). + */ + if (IS_ENABLED(CONFIG_NET_VLAN)) { + vlan_setup(config, cfg); + } + + iface = get_interface(config, i, dev, &name); + if (iface == NULL || name == NULL) { + NET_WARN("No such interface \"%s\" found.", + name == NULL ? "" : name); + continue; + } + + ifindex = net_if_get_by_iface(iface); + + NET_DBG("Configuring interface %d (%p)", ifindex, iface); + + /* Do we need to change the interface name */ + if (cfg->set_name != NULL) { + ret = net_if_set_name(iface, cfg->set_name); + if (ret < 0) { + NET_DBG("Cannot rename interface %d to \"%s\" (%d)", + ifindex, cfg->set_name, ret); + continue; + } + + NET_DBG("Changed interface %d name to \"%s\"", ifindex, + cfg->set_name); + } + + if (cfg->set_default) { + net_if_set_default(iface); + + NET_DBG("Setting interface %d as default", ifindex); + } + + ARRAY_FOR_EACH(cfg->flags, j) { + if (cfg->flags[j].value == NULL || + cfg->flags[j].value[0] == '\0') { + continue; + } + + ret = process_iface_flag(iface, cfg->flags[j].value); + if (ret < 0) { + NET_DBG("Cannot set/clear flag %s", cfg->flags[j].value); + } + } + + ipv6_setup(iface, ifindex, cfg); + ipv4_setup(iface, ifindex, cfg); + virtual_iface_setup(iface, ifindex, config, cfg); + } + + if (IS_ENABLED(CONFIG_NET_CONFIG_CLOCK_SNTP_INIT) && config->sntp.status) { +#if defined(CONFIG_SNTP) + struct net_if *iface = NULL; + + if (config->sntp.bind_to > 0) { + iface = get_interface(config, + config->sntp.bind_to - 1, + dev, + NULL); + } + + ret = net_init_clock_via_sntp(iface, config->sntp.server, + config->sntp.timeout); + if (ret < 0) { + NET_DBG("Cannot init SNTP interface %d (%d)", + net_if_get_by_iface(iface), ret); + } else { + NET_DBG("Initialized SNTP to use interface %d", + net_if_get_by_iface(iface)); + } +#endif + } + + if (IS_ENABLED(CONFIG_NET_L2_IEEE802154) && + IS_ENABLED(CONFIG_NET_CONFIG_SETTINGS) && + config->ieee_802_15_4.status) { +#ifdef CONFIG_NET_L2_IEEE802154_SECURITY + struct ieee802154_security_params sec_params = { 0 }; + struct ieee802154_security_params *generated_sec_params_ptr = &sec_params; +#else +#define generated_sec_params_ptr NULL +#endif /* CONFIG_NET_L2_IEEE802154_SECURITY */ + + struct net_if *iface = NULL; + + if (COND_CODE_1(CONFIG_NET_L2_IEEE802154, + (config->ieee_802_15_4.bind_to), (0)) > 0) { + iface = get_interface( + config, + COND_CODE_1(CONFIG_NET_L2_IEEE802154, + (config->ieee_802_15_4.bind_to - 1), (0)), + dev, + NULL); + } + +#ifdef CONFIG_NET_L2_IEEE802154_SECURITY + memcpy(sec_params.key, config->ieee_802_15_4.security_key, + MIN(sizeof(sec_params.key), + sizeof(config->ieee_802_15_4.security_key))); + sec_params.key_len = sizeof(config->ieee_802_15_4.security_key); + sec_params.key_mode = config->ieee_802_15_4.security_key_mode; + sec_params.level = config->ieee_802_15_4.security_level; +#endif + + ret = z_net_config_ieee802154_setup( + IF_ENABLED(CONFIG_NET_L2_IEEE802154, + (iface, + config->ieee_802_15_4.channel, + config->ieee_802_15_4.pan_id, + config->ieee_802_15_4.tx_power, + generated_sec_params_ptr))); + if (ret < 0) { + NET_ERR("Cannot setup IEEE 802.15.4 interface (%d)", ret); + } } /* This is activated late as it requires the network stack to be up @@ -549,17 +912,42 @@ int net_config_init_app(const struct device *dev, const char *app_info) log_backend_net_start(); } - return ret; + return 0; +} + +int net_config_init_app(const struct device *dev, const char *app_info) +{ + return generated_net_config_init_app(dev, app_info); } #if defined(CONFIG_NET_CONFIG_AUTO_INIT) static int init_app(void) { - (void)net_config_init_app(NULL, "Initializing network"); return 0; } SYS_INIT(init_app, APPLICATION, CONFIG_NET_CONFIG_INIT_PRIO); + +const struct networking *net_config_get_init_config(void) +{ +#define NETWORKING_CONFIG_ENABLE_DATA +#include "network_config.inc" + +#if defined(CONFIG_NET_DHCPV4_SERVER) +/* If we are starting DHCPv4 server, then the socket service needs to be started before + * this config lib as the server will need to use the socket service. + */ +BUILD_ASSERT(CONFIG_NET_SOCKETS_SERVICE_THREAD_PRIO < CONFIG_NET_CONFIG_INIT_PRIO); +#endif + + return &NETWORKING_CONFIG_DATA; /* defined in network_config.inc file */ +} + +#else /* CONFIG_NET_CONFIG_AUTO_INIT */ +const struct networking *net_config_get_init_config(void) +{ + return NULL; +} #endif /* CONFIG_NET_CONFIG_AUTO_INIT */ diff --git a/subsys/net/lib/config/init_clock_sntp.c b/subsys/net/lib/config/init_clock_sntp.c index e0b3063eb1db0..175630d1cc4ee 100644 --- a/subsys/net/lib/config/init_clock_sntp.c +++ b/subsys/net/lib/config/init_clock_sntp.c @@ -25,7 +25,9 @@ BUILD_ASSERT( (sizeof(CONFIG_NET_CONFIG_SNTP_INIT_SERVER) != 1), "SNTP server has to be configured, unless DHCPv4 is used to set it"); -static int sntp_init_helper(struct sntp_time *tm) +static int sntp_init_helper(struct sntp_time *tm, + const char *server, + int timeout) { #ifdef CONFIG_NET_CONFIG_SNTP_INIT_SERVER_USE_DHCPV4_OPTION struct net_if *iface = net_if_get_default(); @@ -36,7 +38,7 @@ static int sntp_init_helper(struct sntp_time *tm) sntp_addr.sin_family = AF_INET; sntp_addr.sin_addr.s_addr = iface->config.dhcpv4.ntp_addr.s_addr; return sntp_simple_addr((struct sockaddr *)&sntp_addr, sizeof(sntp_addr), - CONFIG_NET_CONFIG_SNTP_INIT_TIMEOUT, tm); + timeout, tm); } if (sizeof(CONFIG_NET_CONFIG_SNTP_INIT_SERVER) == 1) { /* Empty address, skip using SNTP via Kconfig defaults */ @@ -44,8 +46,8 @@ static int sntp_init_helper(struct sntp_time *tm) } LOG_INF("SNTP address not set by DHCPv4, using Kconfig defaults"); #endif /* NET_CONFIG_SNTP_INIT_SERVER_USE_DHCPV4_OPTION */ - return sntp_simple(CONFIG_NET_CONFIG_SNTP_INIT_SERVER, - CONFIG_NET_CONFIG_SNTP_INIT_TIMEOUT, tm); + + return sntp_simple(server, timeout, tm); } __maybe_unused static int timespec_to_rtc_time(const struct timespec *in, struct rtc_time *out) @@ -82,20 +84,23 @@ static void sntp_set_rtc(__maybe_unused const struct timespec *tspec) #endif } -int net_init_clock_via_sntp(void) +int net_init_clock_via_sntp(struct net_if *iface, + const char *server, + int timeout) { struct sntp_time ts; struct timespec tspec; - int res = sntp_init_helper(&ts); + int ret; - if (res < 0) { - LOG_ERR("Cannot set time using SNTP: %d", res); + ret = sntp_init_helper(&ts, server, timeout); + if (ret < 0) { + LOG_ERR("Cannot set time using SNTP (%d)", ret); goto end; } tspec.tv_sec = ts.seconds; tspec.tv_nsec = ((uint64_t)ts.fraction * (1000 * 1000 * 1000)) >> 32; - res = sys_clock_settime(SYS_CLOCK_REALTIME, &tspec); + ret = sys_clock_settime(SYS_CLOCK_REALTIME, &tspec); sntp_set_rtc(&tspec); @@ -105,10 +110,10 @@ int net_init_clock_via_sntp(void) #ifdef CONFIG_NET_CONFIG_SNTP_INIT_RESYNC k_work_reschedule( &sntp_resync_work_handle, - (res < 0) ? K_SECONDS(CONFIG_NET_CONFIG_SNTP_INIT_RESYNC_ON_FAILURE_INTERVAL) + (ret < 0) ? K_SECONDS(CONFIG_NET_CONFIG_SNTP_INIT_RESYNC_ON_FAILURE_INTERVAL) : K_SECONDS(CONFIG_NET_CONFIG_SNTP_INIT_RESYNC_INTERVAL)); #endif - return res; + return ret; } #ifdef CONFIG_NET_CONFIG_SNTP_INIT_RESYNC From 871f0bb6bc13eb419d76409e60ce303f5ec0eda6 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Mon, 29 Jan 2024 16:21:19 +0200 Subject: [PATCH 10/16] tests: net: config: Add tests for yaml based configuration Add simple unit tests that verify that options specified in yaml file can be used to configure network stack. Signed-off-by: Jukka Rissanen --- tests/net/lib/config/CMakeLists.txt | 9 + tests/net/lib/config/network-config.yaml | 156 ++++ tests/net/lib/config/prj.conf | 51 ++ tests/net/lib/config/src/main.c | 1049 ++++++++++++++++++++++ tests/net/lib/config/test-config.yaml | 134 +++ tests/net/lib/config/testcase.yaml | 12 + 6 files changed, 1411 insertions(+) create mode 100644 tests/net/lib/config/CMakeLists.txt create mode 100644 tests/net/lib/config/network-config.yaml create mode 100644 tests/net/lib/config/prj.conf create mode 100644 tests/net/lib/config/src/main.c create mode 100644 tests/net/lib/config/test-config.yaml create mode 100644 tests/net/lib/config/testcase.yaml diff --git a/tests/net/lib/config/CMakeLists.txt b/tests/net/lib/config/CMakeLists.txt new file mode 100644 index 0000000000000..dba2a43f052ec --- /dev/null +++ b/tests/net/lib/config/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(iface) + +target_include_directories(app PRIVATE ${ZEPHYR_BASE}/subsys/net/ip) +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/net/lib/config/network-config.yaml b/tests/net/lib/config/network-config.yaml new file mode 100644 index 0000000000000..b17079a6ceb50 --- /dev/null +++ b/tests/net/lib/config/network-config.yaml @@ -0,0 +1,156 @@ +# This is a network configuration data that is used to validate that the +# configuration can be applied automatically when the device boots. +# +# In this test, we have multiple devices and network interface. The test +# enables the network config library, it reads the configuration when the +# device boots, and then the test checks that the configuration is applied +# correctly to the network stack. +# +# Some of the network interfaces are dummy ones and some Ethernet ones. +# The Ethernet devices are needed to validate the VLAN configuration. +# The virtual interface is used to verity that it can be bound to some +# other network interface. +# +networking: + interfaces: + # First Ethernet interface in the system. We change its name in the + # testing process. + - &main-interface + name: eth0 + set_name: test-eth0 + set_default: true + ipv6: + status: true + ipv6_addresses: + - 2001:db8:110::1 + ipv6_multicast_addresses: + - ff05::114 + - ff15::115 + prefixes: + - address: "2001:db8::" + len: 64 + lifetime: 1024 + hop_limit: 48 + multicast_hop_limit: 2 + dhcpv6: + status: true + do_request_address: true + do_request_prefix: false + ipv4: + status: true + ipv4_addresses: + - 192.0.2.10/24 + ipv4_multicast_addresses: + - 234.0.0.10 + gateway: 192.0.2.1 + time_to_live: 128 + multicast_time_to_live: 3 + dhcpv4: + status: true + ipv4_autoconf: + status: true + + # For this interface we do not know its name, but we know what device + # it is bound to. + - &device-interface + device_name: eth_2 + set_name: test-eth1 + flags: + - NET_IF_NO_AUTO_START + - NET_IF_PROMISC + ipv4: + status: true + ipv4_addresses: + - 192.0.2.22/24 + gateway: 192.0.2.2 + time_to_live: 10 + multicast_time_to_live: 1 + dhcpv4: + status: false + dhcpv4_server: + status: true + base_address: 192.0.2.32 + + # This virtual interface bound to the first one + - name: virt0 + bind_to: *main-interface + flags: + - NET_IF_IPV6_NO_MLD + - NET_IF_NO_AUTO_START + - ^NET_IF_PROMISC + ipv6: + status: true + ipv6_addresses: + - 2001:db8:111::2 + ipv4: + status: false + + # This VLAN interface that attaches to second Ethernet interface + - &vlan-interface + set_name: vlan0 + bind_to: *device-interface + vlan: + status: true + tag: 2432 + ipv4: + status: true + dhcpv4: + status: true + + # This 2nd VLAN interface is attached to one of the Ethernet interfaces + - set_name: vlan1 + bind_to: *main-interface + vlan: + status: true + tag: 1234 + ipv4: + status: true + dhcpv4: + status: true + + # This virtual interface bound to the VLAN interface + - name: virt1 + set_name: virt-over-vlan + bind_to: *vlan-interface + ipv6: + status: true + ipv6_addresses: + - 2001:db8:abcd::11 + + # This interface IPv4 configuration is set to disabled + # in which case its IPv4 configuration is skipped + - name: dummy0 + ipv6: + status: true + ipv4: + status: false + ipv4_addresses: + - 192.2.0.2 + gateway: 192.2.0.1 + time_to_live: 10 + multicast_time_to_live: 2 + + # This interface IPv4 and IPv6 configuration are both disabled. + # Basically the interface is non functional for IP connectivity. + - name: dummy1 + ipv6: + status: false + ipv4: + status: false + + ieee_802_15_4: + status: true + pan_id: 0xabcd + channel: 26 + tx_power: 1 + security_key: [0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf] + security_key_mode: 0 + security_level: 1 + ack_required: true + bind_to: *device-interface + + sntp: + server: sntp.foo.bar + timeout: 30 + bind_to: *vlan-interface diff --git a/tests/net/lib/config/prj.conf b/tests/net/lib/config/prj.conf new file mode 100644 index 0000000000000..b391aa0a323b7 --- /dev/null +++ b/tests/net/lib/config/prj.conf @@ -0,0 +1,51 @@ +CONFIG_NETWORKING=y +CONFIG_NET_TEST=y +CONFIG_ZTEST=y +CONFIG_NET_LOG=y +CONFIG_LOG=y +CONFIG_MAIN_STACK_SIZE=2048 + +CONFIG_NET_IPV6=y +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6_DAD=n +CONFIG_NET_IPV6_MLD=n +CONFIG_NET_IPV6_MAX_NEIGHBORS=8 +CONFIG_NET_IPV6_ND=n +CONFIG_NET_MAX_NEXTHOPS=8 +CONFIG_NET_IPV4_AUTO=n +CONFIG_NET_IPV4_ACD=n +CONFIG_NET_DHCPV4=y +CONFIG_NET_DHCPV4_SERVER=y +CONFIG_NET_DHCPV4_SERVER_ADDR_COUNT=32 +CONFIG_NET_DHCPV6=y + +CONFIG_NET_UDP=y +CONFIG_NET_TCP=y + +CONFIG_NET_MAX_CONTEXTS=4 + +CONFIG_NET_L2_DUMMY=y +CONFIG_NET_L2_ETHERNET=y +CONFIG_NET_L2_ETHERNET_MGMT=y +CONFIG_NET_L2_VIRTUAL=y +CONFIG_NET_INTERFACE_NAME_LEN=15 + +CONFIG_NET_VLAN=y +CONFIG_NET_VLAN_COUNT=2 + +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y + +CONFIG_NET_PKT_TX_COUNT=10 +CONFIG_NET_PKT_RX_COUNT=10 +CONFIG_NET_BUF_RX_COUNT=10 +CONFIG_NET_BUF_TX_COUNT=10 + +CONFIG_NET_IF_MAX_IPV6_COUNT=8 +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=6 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4 +CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=2 +CONFIG_NET_IF_MAX_IPV4_COUNT=8 + +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_AUTO_INIT=y diff --git a/tests/net/lib/config/src/main.c b/tests/net/lib/config/src/main.c new file mode 100644 index 0000000000000..4fe78cfa691c9 --- /dev/null +++ b/tests/net/lib/config/src/main.c @@ -0,0 +1,1049 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define NET_LOG_LEVEL CONFIG_NET_CONFIG_LOG_LEVEL + +#include +LOG_MODULE_REGISTER(net_test, NET_LOG_LEVEL); + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define NET_LOG_ENABLED 1 +#include "net_private.h" + +#include "network_config.inc" + +#if defined(CONFIG_NET_CONFIG_LOG_LEVEL_DBG) +#define DBG(fmt, ...) printk(fmt, ##__VA_ARGS__) +#else +#define DBG(fmt, ...) +#endif + +/* The MTU value here is just an arbitrary number for testing purposes */ +#define VIRTUAL_TEST_MTU 1024 + +static int dummy_if_count; +static int eth_if_count; +static int vlan_if_count; +static int virtual_if_count; + +/* We should have enough interfaces as set in test-config.yaml. */ +static struct net_if *iface1; /* eth */ +static struct net_if *iface2; /* eth */ +static struct net_if *iface3; /* vlan */ +static struct net_if *iface4; /* vlan */ +static struct net_if *iface5; /* dummy */ +static struct net_if *iface6; /* dummy */ +static struct net_if *iface7; /* virtual */ +static struct net_if *iface8; /* virtual */ + +static bool test_started; + +struct net_if_test { + uint8_t mac_addr[sizeof(struct net_eth_addr)]; + struct net_linkaddr ll_addr; +}; + +struct eth_test_context { + struct net_if *iface; + uint8_t mac_address[6]; +}; + +struct virtual_test_context { + struct net_if *iface; + struct net_if *attached_to; + bool status; + bool init_done; +}; + +static uint8_t *net_iface_get_mac(const struct device *dev) +{ + struct net_if_test *data = dev->data; + + if (data->mac_addr[2] == 0x00) { + /* 00-00-5E-00-53-xx Documentation RFC 7042 */ + data->mac_addr[0] = 0x00; + data->mac_addr[1] = 0x00; + data->mac_addr[2] = 0x5E; + data->mac_addr[3] = 0x00; + data->mac_addr[4] = 0x53; + data->mac_addr[5] = sys_rand32_get(); + } + + data->ll_addr.len = 6U; + + memcpy(data->ll_addr.addr, data->mac_addr, data->ll_addr.len); + + return data->mac_addr; +} + +static void dummy_iface_init(struct net_if *iface) +{ + uint8_t *mac = net_iface_get_mac(net_if_get_device(iface)); + + net_if_set_link_addr(iface, mac, sizeof(struct net_eth_addr), + NET_LINK_ETHERNET); +} + +static int dev_init(const struct device *dev) +{ + return 0; +} + +static int sender_iface(const struct device *dev, struct net_pkt *pkt) +{ + if (!pkt->buffer) { + DBG("No data to send!\n"); + return -ENODATA; + } + + if (test_started) { + DBG("Sending at iface %d %p\n", + net_if_get_by_iface(net_pkt_iface(pkt)), + net_pkt_iface(pkt)); + } + + return 0; +} + +static void eth_iface_init(struct net_if *iface) +{ + const struct device *dev = net_if_get_device(iface); + struct eth_test_context *ctx = dev->data; + + ctx->iface = iface; + + net_if_set_link_addr(iface, ctx->mac_address, + sizeof(ctx->mac_address), + NET_LINK_ETHERNET); + + ethernet_init(iface); +} + +static int eth_send(const struct device *dev, struct net_pkt *pkt) +{ + ARG_UNUSED(dev); + ARG_UNUSED(pkt); + + return 0; +} + +static enum ethernet_hw_caps eth_get_capabilities(const struct device *dev) +{ + return ETHERNET_PROMISC_MODE | ETHERNET_HW_VLAN; +} + +static int eth_set_config(const struct device *dev, + enum ethernet_config_type type, + const struct ethernet_config *config) +{ + struct eth_test_context *ctx = dev->data; + + ARG_UNUSED(ctx); + + switch (type) { + default: + return -EINVAL; + } + + return 0; +} + +static int eth_dev_init(const struct device *dev) +{ + struct eth_test_context *ctx = dev->data; + + ARG_UNUSED(ctx); + + return 0; +} + +static void virtual_test_iface_init(struct net_if *iface) +{ + struct virtual_test_context *ctx = net_if_get_device(iface)->data; + char name[sizeof("VirtualTest-+##########")]; + static int count; + + if (ctx->init_done) { + return; + } + + ctx->iface = iface; + net_if_flag_set(iface, NET_IF_NO_AUTO_START); + + snprintk(name, sizeof(name), "VirtualTest-%d", count + 1); + count++; + net_virtual_set_name(iface, name); + (void)net_virtual_set_flags(iface, NET_L2_POINT_TO_POINT); + + ctx->init_done = true; +} + +static enum virtual_interface_caps +virtual_test_get_capabilities(struct net_if *iface) +{ + ARG_UNUSED(iface); + + return (enum virtual_interface_caps)0; +} + +static int virtual_test_interface_start(const struct device *dev) +{ + struct virtual_test_context *ctx = dev->data; + + if (ctx->status) { + return -EALREADY; + } + + ctx->status = true; + + DBG("Starting iface %d", net_if_get_by_iface(ctx->iface)); + + /* You can implement here any special action that is needed + * when the network interface is coming up. + */ + + return 0; +} + +static int virtual_test_interface_stop(const struct device *dev) +{ + struct virtual_test_context *ctx = dev->data; + + if (!ctx->status) { + return -EALREADY; + } + + ctx->status = false; + + DBG("Stopping iface %d", net_if_get_by_iface(ctx->iface)); + + /* You can implement here any special action that is needed + * when the network interface is going down. + */ + + return 0; +} + +static int virtual_test_interface_send(struct net_if *iface, + struct net_pkt *pkt) +{ + struct virtual_test_context *ctx = net_if_get_device(iface)->data; + + if (ctx->attached_to == NULL) { + return -ENOENT; + } + + return net_send_data(pkt); +} + +static enum net_verdict virtual_test_interface_recv(struct net_if *iface, + struct net_pkt *pkt) +{ + ARG_UNUSED(iface); + ARG_UNUSED(pkt); + + return NET_CONTINUE; +} + +static int virtual_test_interface_attach(struct net_if *virtual_iface, + struct net_if *iface) +{ + struct virtual_test_context *ctx = net_if_get_device(virtual_iface)->data; + + LOG_INF("This interface %d/%p attached to %d/%p", + net_if_get_by_iface(virtual_iface), virtual_iface, + net_if_get_by_iface(iface), iface); + + ctx->attached_to = iface; + + return 0; +} + +static const struct virtual_interface_api virtual_test_iface_api = { + .iface_api.init = virtual_test_iface_init, + + .get_capabilities = virtual_test_get_capabilities, + .start = virtual_test_interface_start, + .stop = virtual_test_interface_stop, + .send = virtual_test_interface_send, + .recv = virtual_test_interface_recv, + .attach = virtual_test_interface_attach, +}; + +struct net_if_test net_eth_iface1_data; +struct net_if_test net_eth_iface2_data; +struct net_if_test net_vlan_iface3_data; +struct net_if_test net_vlan_iface4_data; +struct net_if_test net_dummy_iface6_data; +struct net_if_test net_dummy_iface7_data; +struct virtual_test_context virtual_test_iface5_data; +struct virtual_test_context virtual_test_iface8_data; + +static struct ethernet_api eth_api_funcs = { + .iface_api.init = eth_iface_init, + .get_capabilities = eth_get_capabilities, + .set_config = eth_set_config, + .send = eth_send, +}; + +static struct dummy_api dummy_iface_api = { + .iface_api.init = dummy_iface_init, + .send = sender_iface, +}; + +ETH_NET_DEVICE_INIT(eth_1, "eth_1", eth_dev_init, NULL, + &net_eth_iface1_data, NULL, CONFIG_ETH_INIT_PRIORITY, + ð_api_funcs, NET_ETH_MTU); + +ETH_NET_DEVICE_INIT(eth_2, "eth_2", eth_dev_init, NULL, + &net_eth_iface2_data, NULL, CONFIG_ETH_INIT_PRIORITY, + ð_api_funcs, NET_ETH_MTU); + +NET_DEVICE_INIT_INSTANCE(net_iface6_test, "dummy_6", iface6, dev_init, NULL, + &net_dummy_iface6_data, NULL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, + &dummy_iface_api, DUMMY_L2, DUMMY_L2_CTX_TYPE, 127); + +NET_DEVICE_INIT_INSTANCE(net_iface7_test, "dummy_7", iface7, NULL, NULL, + &net_dummy_iface7_data, NULL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, + &dummy_iface_api, DUMMY_L2, DUMMY_L2_CTX_TYPE, 127); + +NET_VIRTUAL_INTERFACE_INIT(virtual_iface5_test, "virtual_5", NULL, NULL, + &virtual_test_iface5_data, NULL, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, + &virtual_test_iface_api, VIRTUAL_TEST_MTU); + +NET_VIRTUAL_INTERFACE_INIT(virtual_iface8_test, "virtual_8", NULL, NULL, + &virtual_test_iface8_data, NULL, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, + &virtual_test_iface_api, VIRTUAL_TEST_MTU); + + +#if NET_LOG_LEVEL >= LOG_LEVEL_DBG +static const char *iface2str(struct net_if *iface) +{ + if (net_if_l2(iface) == &NET_L2_GET_NAME(ETHERNET)) { + return "Ethernet"; + } + + if (net_if_l2(iface) == &NET_L2_GET_NAME(DUMMY)) { + return "Dummy"; + } + + if (net_if_l2(iface) == &NET_L2_GET_NAME(VIRTUAL)) { + return "Virtual"; + } + + return ""; +} +#endif + +static void iface_cb(struct net_if *iface, void *user_data) +{ + char name[CONFIG_NET_INTERFACE_NAME_LEN]; + + (void)net_if_get_name(iface, name, sizeof(name)); + + DBG("Interface %p %s (%s) [%d]\n", iface, name, iface2str(iface), + net_if_get_by_iface(iface)); + + if (net_if_l2(iface) == &NET_L2_GET_NAME(ETHERNET)) { + const struct ethernet_api *api = + net_if_get_device(iface)->api; + + /* As native_sim board will introduce another Ethernet + * interface, make sure that we only use our own in this test. + */ + if (api->get_capabilities != eth_api_funcs.get_capabilities) { + return; + } + + switch (eth_if_count) { + case 0: + iface1 = iface; + break; + case 1: + iface2 = iface; + break; + } + + eth_if_count++; + + } else if (net_if_l2(iface) == &NET_L2_GET_NAME(DUMMY)) { + switch (dummy_if_count) { + case 0: + iface5 = iface; + break; + case 1: + iface6 = iface; + break; + } + + dummy_if_count++; + + } else if (net_if_l2(iface) == &NET_L2_GET_NAME(VIRTUAL)) { + const struct virtual_interface_api *api = + net_if_get_device(iface)->api; + char name[CONFIG_NET_INTERFACE_NAME_LEN]; + int ret; + + if (api->get_capabilities(iface) & VIRTUAL_INTERFACE_VLAN) { + switch (vlan_if_count) { + case 0: + iface3 = iface; + break; + case 1: + iface4 = iface; + break; + } + + vlan_if_count++; + } else { + switch (virtual_if_count) { + case 0: + iface7 = iface; + snprintk(name, sizeof(name), "virt%d", virtual_if_count); + ret = net_if_set_name(iface, name); + zassert_equal(ret, 0, "Unexpected value (%d) returned", ret); + break; + case 1: + iface8 = iface; + snprintk(name, sizeof(name), "virt%d", virtual_if_count); + ret = net_if_set_name(iface, name); + zassert_equal(ret, 0, "Unexpected value (%d) returned", ret); + break; + } + + virtual_if_count++; + } + } else { + zassert_true(false, "Invalid network interface type found"); + } +} + +static void *iface_setup(void) +{ + int idx; + + net_if_foreach(iface_cb, NULL); + + idx = net_if_get_by_iface(iface1); + zassert_true(idx > 0); + + idx = net_if_get_by_iface(iface2); + zassert_true(idx > 0); + + idx = net_if_get_by_iface(iface3); + zassert_true(idx > 0); + + idx = net_if_get_by_iface(iface4); + zassert_true(idx > 0); + + idx = net_if_get_by_iface(iface5); + zassert_true(idx > 0); + + idx = net_if_get_by_iface(iface6); + zassert_true(idx > 0); + + idx = net_if_get_by_iface(iface7); + zassert_true(idx > 0); + + idx = net_if_get_by_iface(iface8); + zassert_true(idx > 0); + + DBG("Ethernet interfaces:\n" + "\t[%d] iface1 %p, [%d] iface2 %p\n", + net_if_get_by_iface(iface1), iface1, + net_if_get_by_iface(iface2), iface2); + + DBG("VLAN interfaces:\n" + "\t[%d] iface3 %p, [%d] iface4 %p\n", + net_if_get_by_iface(iface3), iface3, + net_if_get_by_iface(iface4), iface4); + +#define EXPECTED_VLAN_IFACE_COUNT (CONFIG_NET_VLAN_COUNT) + zassert_equal(vlan_if_count, EXPECTED_VLAN_IFACE_COUNT, + "Invalid number of Ethernet interface found, expected %d got %d", + EXPECTED_VLAN_IFACE_COUNT, vlan_if_count); + + zassert_not_null(iface1, "Ethernet interface 1"); + zassert_not_null(iface2, "Ethernet interface 2"); + zassert_not_null(iface3, "VLAN interface 3"); + zassert_not_null(iface4, "VLAN interface 4"); + + DBG("Dummy interfaces:\n" + "\t[%d] iface5 %p, [%d] iface6 %p\n", + net_if_get_by_iface(iface5), iface5, + net_if_get_by_iface(iface6), iface6); + + zassert_equal(dummy_if_count, 2, + "Invalid number of Dummy interface found, expected %d got %d", + 2, eth_if_count); + + zassert_not_null(iface5, "Dummy interface 6"); + zassert_not_null(iface6, "Dummy interface 7"); + + DBG("Virtual interfaces:\n" + "\t[%d] iface7 %p, [%d] iface8 %p\n", + net_if_get_by_iface(iface7), iface7, + net_if_get_by_iface(iface8), iface8); + + zassert_equal(virtual_if_count, 2, + "Invalid number of virtual interface found, expected %d got %d", + 2, virtual_if_count); + + test_started = true; + + return NULL; +} + +static void iface_teardown(void *dummy) +{ + ARG_UNUSED(dummy); + + net_if_down(iface1); + net_if_down(iface2); + net_if_down(iface3); + net_if_down(iface4); + net_if_down(iface5); + net_if_down(iface6); + net_if_down(iface7); + net_if_down(iface8); +} + +static int setup_net_config_test(void) +{ + (void)iface_setup(); + return 0; +} + +/* We must setup the network interfaces just before the config library + * is initializing itself. If we use ztest setup function, then the config + * library has already ran, and things happens too late and will fail. + */ +#define TEST_NET_CONFIG_INIT_PRIO 85 +SYS_INIT(setup_net_config_test, APPLICATION, TEST_NET_CONFIG_INIT_PRIO); + +/* Fail the compilation if the network config library is initialized + * before this code. + */ +BUILD_ASSERT(TEST_NET_CONFIG_INIT_PRIO < CONFIG_NET_CONFIG_INIT_PRIO); + +static int get_ifindex(const struct net_cfg_interfaces *cfg) +{ + int ifindex = -1; + + /* Both name and device cannot be given at the same time + * as then we would not know what device to get. If both + * are missing, then the bind-to field will tell which + * interface to use. + */ + zassert_false(cfg->name == NULL && + cfg->device_name == NULL && + cfg->bind_to == 0, + "Cannot find the interface."); + + if (cfg->set_name[0] != '\0') { + ifindex = net_if_get_by_name(cfg->set_name); + } else { + if (cfg->name[0] != '\0') { + if (ifindex < 0) { + ifindex = net_if_get_by_name(cfg->name); + } + } + } + + if (cfg->device_name != NULL) { + const struct device *dev; + struct net_if *iface; + + if (ifindex < 0) { + dev = device_get_binding(cfg->device_name); + zassert_not_null(dev, "Device %s not found.", cfg->device_name); + + iface = net_if_lookup_by_dev(dev); + zassert_not_null(iface, "Cannot find interface."); + + ifindex = net_if_get_by_iface(iface); + } + } + + if ((cfg->bind_to - 1) > 0) { + if (ifindex < 0) { + ifindex = cfg->bind_to - 1; + } + } + + zassert_true(ifindex > 0, "Invalid network interface %d\n" + "name '%s', new_name '%s', dev '%s', bind-to %d", + ifindex, cfg->name ? cfg->name : "?", + cfg->set_name ? cfg->set_name : "?", + cfg->device_name ? cfg->device_name : "?", + cfg->bind_to - 1); + + return ifindex; +} + +ZTEST(net_config, test_interface_00_names) +{ + const struct networking *config; + int ifindex; + + config = net_config_get_init_config(); + zassert_not_null(config, "Network configuration not found."); + + zassert_true(NET_CONFIG_NETWORK_INTERFACE_COUNT > 0); + + for (int i = 0; i < NET_CONFIG_NETWORK_INTERFACE_COUNT; i++) { + ifindex = get_ifindex(&config->interfaces[i]); + } +} + +static bool parse_mask(const char *str, size_t str_len, + struct sockaddr *addr, uint8_t *mask_len) +{ + const char *result; + + result = net_ipaddr_parse_mask(str, str_len, addr, mask_len); + if (result == NULL || result[0] != '\0') { + return false; + } + + return true; +} + +#if defined(CONFIG_NET_IPV4) +static void check_ipv4(const struct net_cfg_interfaces *cfg) +{ + struct net_if *iface; + struct in_addr addr; + int ifindex; + int ret; + + ARRAY_FOR_EACH(cfg->ipv4.ipv4_addresses, i) { + struct net_if_addr *ifaddr; + struct net_if *iface_addr; + struct sockaddr_in saddr; + uint8_t netmask_len = 0; + + ifindex = get_ifindex(cfg); + zassert_true(ifindex > 0, "No interface found for cfg %p", cfg); + + iface = net_if_get_by_index(ifindex); + zassert_not_null(iface); + + if (cfg->ipv4.ipv4_addresses[i].value[0] == '\0') { + continue; + } + + zassert_true(parse_mask(cfg->ipv4.ipv4_addresses[i].value, + strlen(cfg->ipv4.ipv4_addresses[i].value), + (struct sockaddr *)&saddr, &netmask_len), + "Cannot parse the address \"%s\"", + cfg->ipv4.ipv4_addresses[i].value); + + memcpy(&addr, &saddr.sin_addr, sizeof(addr)); + + if (net_ipv4_is_addr_unspecified(&addr)) { + continue; + } + + ifaddr = net_if_ipv4_addr_lookup(&addr, &iface_addr); + zassert_not_null(ifaddr, "Address %s not found.", + net_sprint_ipv4_addr(&addr)); + zassert_equal(iface, iface_addr, "Invalid network interface. " + "Got %p (%d) expected %p (%d).", + iface_addr, net_if_get_by_iface(iface_addr), + iface, net_if_get_by_iface(iface)); + + if (netmask_len > 0) { + struct in_addr gen_mask; + + addr = net_if_ipv4_get_netmask_by_addr(iface, &addr); + gen_mask.s_addr = BIT_MASK(netmask_len); + + zassert_equal(gen_mask.s_addr, addr.s_addr, + "Netmask invalid, expecting %s got %s", + net_sprint_ipv4_addr(&gen_mask), + net_sprint_ipv4_addr(&addr)); + } + } + + ARRAY_FOR_EACH(cfg->ipv4.ipv4_multicast_addresses, i) { + struct net_if_mcast_addr *ifmaddr; + struct net_if *iface_addr = NULL; + + if (cfg->ipv4.ipv4_multicast_addresses[i].value[0] == '\0') { + continue; + } + + zassert_true(parse_mask(cfg->ipv4.ipv4_multicast_addresses[i].value, + strlen(cfg->ipv4.ipv4_multicast_addresses[i].value), + (struct sockaddr *)&addr, NULL), + "Cannot parse the address \"%s\"", + cfg->ipv4.ipv4_multicast_addresses[i].value); + + ret = net_addr_pton(AF_INET, cfg->ipv4.ipv4_multicast_addresses[i].value, + &addr); + zassert_equal(ret, 0, "Cannot convert multicast address \"%s\"", + cfg->ipv4.ipv4_multicast_addresses[i].value); + + if (net_ipv4_is_addr_unspecified(&addr)) { + continue; + } + + ifmaddr = net_if_ipv4_maddr_lookup(&addr, &iface_addr); + zassert_not_null(ifmaddr, "Multicast address %s not found.", + net_sprint_ipv4_addr(&addr)); + zassert_equal(iface, iface_addr, "Invalid network interface. " + "Got %p (%d) expected %p (%d).", + iface_addr, net_if_get_by_iface(iface_addr), + iface, net_if_get_by_iface(iface)); + } + + if (cfg->ipv4.gateway[0] != '\0') { + ret = net_addr_pton(AF_INET, cfg->ipv4.gateway, &addr); + zassert_equal(ret, 0, "Cannot convert gateway address \"%s\"", + cfg->ipv4.gateway); + + if (!net_ipv4_is_addr_unspecified(&addr)) { + zassert_mem_equal(&iface->config.ip.ipv4->gw, + &addr, + sizeof(struct in_addr), + "Mismatch gateway address. " + "Expecting %s got %s.", + net_sprint_ipv4_addr(&addr), + net_sprint_ipv4_addr(&iface->config.ip.ipv4->gw)); + } + } + + /* We cannot verify default values of TTL and multicast TTL */ + if (cfg->ipv4.time_to_live > 0) { + zassert_equal(net_if_ipv4_get_ttl(iface), cfg->ipv4.time_to_live, + "TTL mismatch, expecting %d got %d", + cfg->ipv4.time_to_live, net_if_ipv4_get_ttl(iface)); + } + + if (cfg->ipv4.multicast_time_to_live > 0) { + zassert_equal(net_if_ipv4_get_mcast_ttl(iface), + cfg->ipv4.multicast_time_to_live, + "Multicast TTL mismatch, expecting %d got %d", + cfg->ipv4.multicast_time_to_live, + net_if_ipv4_get_mcast_ttl(iface)); + } + + if (cfg->ipv4.dhcpv4.status) { +#if defined(CONFIG_NET_DHCPV4) + zassert_true((iface->config.dhcpv4.state == NET_DHCPV4_INIT) || + (iface->config.dhcpv4.state == NET_DHCPV4_SELECTING), + "DHCPv4 not in correct state, expecting '%s' or '%s' got '%s'", + net_dhcpv4_state_name(NET_DHCPV4_INIT), + net_dhcpv4_state_name(NET_DHCPV4_SELECTING), + net_dhcpv4_state_name(iface->config.dhcpv4.state)); +#endif + } + +#if defined(CONFIG_NET_IPV4_AUTO) + if (cfg->ipv4.ipv4_autoconf.status) { + zassert_equal(iface->config.ipv4auto.state, + NET_IPV4_AUTOCONF_ASSIGNED, + "IPv4 autoconf not in correct state, expecting '%d' got '%d'", + NET_IPV4_AUTOCONF_ASSIGNED, + iface->config.ipv4auto.state); + } +#endif +} +#else +static inline void check_ipv4(const struct networking *cfg, int index) +{ + ARG_UNUSED(cfg); + ARG_UNUSED(index); +} +#endif + +#if defined(CONFIG_NET_IPV6) +static void check_ipv6(const struct net_cfg_interfaces *cfg) +{ + struct net_if *iface; + int ifindex; + + ARRAY_FOR_EACH(cfg->ipv6.ipv6_addresses, i) { + struct net_if_addr *ifaddr; + struct net_if *iface_addr = NULL; + struct sockaddr_in6 saddr; + uint8_t prefix_len = 0; + + ifindex = get_ifindex(cfg); + zassert_true(ifindex > 0); + + iface = net_if_get_by_index(ifindex); + zassert_not_null(iface); + + if (cfg->ipv6.ipv6_addresses[i].value[0] == '\0') { + continue; + } + + zassert_true(parse_mask(cfg->ipv6.ipv6_addresses[i].value, + strlen(cfg->ipv6.ipv6_addresses[i].value), + (struct sockaddr *)&saddr, &prefix_len), + "Cannot parse the address \"%s\"", + cfg->ipv6.ipv6_addresses[i].value); + + if (net_ipv6_is_addr_unspecified(&saddr.sin6_addr)) { + continue; + } + + ifaddr = net_if_ipv6_addr_lookup(&saddr.sin6_addr, &iface_addr); + zassert_not_null(ifaddr, "Address %s not found.", + net_sprint_ipv6_addr(&saddr.sin6_addr)); + zassert_equal(iface, iface_addr, "Invalid network interface. " + "Got %p (%d) expected %p (%d).", + iface_addr, net_if_get_by_iface(iface_addr), + iface, net_if_get_by_iface(iface)); + + } + + ARRAY_FOR_EACH(cfg->ipv6.ipv6_multicast_addresses, i) { + struct net_if_mcast_addr *ifmaddr; + struct net_if *iface_addr; + struct sockaddr_in6 saddr; + + if (cfg->ipv6.ipv6_multicast_addresses[i].value[0] == '\0') { + continue; + } + + zassert_true(parse_mask(cfg->ipv6.ipv6_multicast_addresses[i].value, + strlen(cfg->ipv6.ipv6_multicast_addresses[i].value), + (struct sockaddr *)&saddr, NULL), + "Cannot parse the address \"%s\"", + cfg->ipv6.ipv6_multicast_addresses[i].value); + + if (net_ipv6_is_addr_unspecified(&saddr.sin6_addr)) { + continue; + } + + ifmaddr = net_if_ipv6_maddr_lookup(&saddr.sin6_addr, &iface_addr); + zassert_not_null(ifmaddr, "Multicast address %s not found.", + net_sprint_ipv6_addr(&cfg->ipv6.ipv6_multicast_addresses[i])); + zassert_equal(iface, iface_addr, "Invalid network interface. " + "Got %p (%d) expected %p (%d).", + iface_addr, net_if_get_by_iface(iface_addr), + iface, net_if_get_by_iface(iface)); + } + + ARRAY_FOR_EACH(cfg->ipv6.prefixes, i) { + struct net_if_ipv6_prefix *prefix; + struct sockaddr_in6 saddr; + + if (cfg->ipv6.prefixes[i].address[0] == '\0') { + continue; + } + + zassert_true(parse_mask(cfg->ipv6.prefixes[i].address, + strlen(cfg->ipv6.prefixes[i].address), + (struct sockaddr *)&saddr, NULL), + "Cannot parse the address \"%s\"", + cfg->ipv6.prefixes[i].address); + + if (net_ipv6_is_addr_unspecified(&saddr.sin6_addr)) { + continue; + } + + prefix = net_if_ipv6_prefix_lookup(iface, &saddr.sin6_addr, + cfg->ipv6.prefixes[i].len); + zassert_not_null(prefix, "Prefix %s/%d not found.", + net_sprint_ipv6_addr(&saddr.sin6_addr), + cfg->ipv6.prefixes[i].len); + zassert_equal(prefix->len, cfg->ipv6.prefixes[i].len, + "Prefix len differs, expected %u got %u", + cfg->ipv6.prefixes[i].len, + prefix->len); + if (cfg->ipv6.prefixes[i].lifetime == 0xffffffff) { + zassert_true(prefix->is_infinite, + "Prefix lifetime not infinite"); + } + } + + /* We cannot verify default values of hop limit and multicast hop limit */ + if (cfg->ipv6.hop_limit > 0) { + zassert_equal(net_if_ipv6_get_hop_limit(iface), cfg->ipv6.hop_limit, + "hop limit mismatch, expecting %d got %d", + cfg->ipv6.hop_limit, net_if_ipv6_get_hop_limit(iface)); + } + + if (cfg->ipv6.multicast_hop_limit > 0) { + zassert_equal(net_if_ipv6_get_mcast_hop_limit(iface), + cfg->ipv6.multicast_hop_limit, + "Multicast hop limit mismatch, expecting %d got %d", + cfg->ipv6.multicast_hop_limit, + net_if_ipv6_get_mcast_hop_limit(iface)); + } + + if (cfg->ipv6.dhcpv6.status) { +#if defined(CONFIG_NET_DHCPV6) + zassert_true((iface->config.dhcpv6.state == NET_DHCPV6_INIT) || + (iface->config.dhcpv6.state == NET_DHCPV6_SOLICITING), + "DHCPv6 not in correct state, expecting '%s' or '%s' got '%s'", + net_dhcpv6_state_name(NET_DHCPV6_INIT), + net_dhcpv6_state_name(NET_DHCPV6_SOLICITING), + net_dhcpv6_state_name(iface->config.dhcpv6.state)); +#endif + } +} +#else +static inline void check_ipv6(const struct net_cfg_interfaces *cfg) +{ +} +#endif + +ZTEST(net_config, test_interface_01_ipv4_addresses) +{ + const struct networking *config; + int ifindex = 0; + + config = net_config_get_init_config(); + zassert_not_null(config, "Network configuration not found."); + + zassert_true(NET_CONFIG_NETWORK_INTERFACE_COUNT > 0); + + for (int i = 0; i < NET_CONFIG_NETWORK_INTERFACE_COUNT; i++) { + ifindex = get_ifindex(&config->interfaces[i]); + +#define IF_ENABLED_IPV4(cfg, i) \ + COND_CODE_1(CONFIG_NET_IPV4, \ + ((cfg)->interfaces[i].ipv4.status), \ + (false)) + + if (IF_ENABLED_IPV4(config, i)) { + check_ipv4(&config->interfaces[i]); + } + } +} + +ZTEST(net_config, test_interface_02_ipv6_addresses) +{ + const struct networking *config; + int ifindex = 0; + + config = net_config_get_init_config(); + zassert_not_null(config, "Network configuration not found."); + + zassert_true(NET_CONFIG_NETWORK_INTERFACE_COUNT > 0); + + for (int i = 0; i < NET_CONFIG_NETWORK_INTERFACE_COUNT; i++) { + ifindex = get_ifindex(&config->interfaces[i]); + +#define IF_ENABLED_IPV6(cfg, i) \ + COND_CODE_1(CONFIG_NET_IPV6, \ + ((cfg)->interfaces[i].ipv6.status), \ + (false)) + + if (IF_ENABLED_IPV6(config, i)) { + check_ipv6(&config->interfaces[i]); + } + } +} + +ZTEST(net_config, test_interface_03_vlan) +{ + const struct networking *config; + int ifindex = 0; + int vlan_count = 0; + + config = net_config_get_init_config(); + zassert_not_null(config, "Network configuration not found."); + + ARRAY_FOR_EACH(config->interfaces, i) { + const struct net_cfg_interfaces *cfg; + const struct net_cfg_vlan *vlan; + struct net_if *iface; + uint16_t tag; + + cfg = &config->interfaces[i]; + ifindex = get_ifindex(cfg); + + vlan = &cfg->vlan; + if (!vlan->status) { + continue; + } + + iface = net_eth_get_vlan_iface(NULL, vlan->tag); + zassert_equal(net_if_get_by_index(ifindex), iface, + "Could not get the VLAN interface (%d)", + ifindex); + + tag = net_eth_get_vlan_tag(net_if_get_by_index(ifindex)); + zassert_equal(tag, vlan->tag, + "Tag 0x%04x (%d) not set to iface %d (got 0x%04x (%d))", + vlan->tag, vlan->tag, ifindex, tag, tag); + + vlan_count++; + } + + zassert_equal(vlan_count, CONFIG_NET_VLAN_COUNT, + "Invalid VLAN count, expecting %d got %d", + CONFIG_NET_VLAN_COUNT, vlan_count); +} + +/* From subsys/net/lib/config/init.c */ +extern enum net_if_flag get_iface_flag(const char *flag_str, bool *clear); + +ZTEST(net_config, test_interface_04_flags) +{ + const struct networking *config; + int ifindex = 0; + + config = net_config_get_init_config(); + zassert_not_null(config, "Network configuration not found."); + + ARRAY_FOR_EACH(config->interfaces, i) { + const struct net_cfg_interfaces *cfg; + + cfg = &config->interfaces[i]; + ifindex = get_ifindex(cfg); + + ARRAY_FOR_EACH(cfg->flags, j) { + enum net_if_flag flag; + bool status; + bool clear; + + if (cfg->flags[j].value[0] == '\0') { + continue; + } + + flag = get_iface_flag(cfg->flags[j].value, &clear); + zassert_not_equal(flag, NET_IF_NUM_FLAGS, + "Unknown flag %s", + cfg->flags[j].value); + + status = net_if_flag_is_set(net_if_get_by_index(ifindex), + flag); + if (clear) { + zassert_equal(status, false, "Flag %s (%d) was set", + cfg->flags[j].value, flag); + } else { + zassert_equal(status, true, "Flag %s (%d) was not set", + cfg->flags[j].value, flag); + } + } + } +} + +ZTEST_SUITE(net_config, NULL, NULL, NULL, NULL, iface_teardown); diff --git a/tests/net/lib/config/test-config.yaml b/tests/net/lib/config/test-config.yaml new file mode 100644 index 0000000000000..98b9cd7dde0c2 --- /dev/null +++ b/tests/net/lib/config/test-config.yaml @@ -0,0 +1,134 @@ +# This is a network configuration data that is used to validate that the +# configuration can be applied automatically when the device boots. +# +# In this test, we have multiple devices and network interface. The test +# enables the network config library, it reads the configuration when the +# device boots, and then the test checks that the configuration is applied +# correctly to the network stack. +# +# Some of the network interfaces are dummy ones and some Ethernet ones. +# The Ethernet devices are needed to validate the VLAN configuration. +# The virtual interface is used to verity that it can be bound to some +# other network interface. +# +net: + network-interfaces: + # First Ethernet interface in the system. We change its name in the + # testing process. + - &main-interface + name: eth0 + set-name: test-eth0 + set-default: true + IPv6: + status: enabled + addresses: + - 2001:db8:110::1 + multicast-addresses: + - ff05::114 + - ff15::115 + prefixes: + - address: "2001:db8::" + len: 64 + lifetime: 1024 + hop-limit: 48 + multicast-hop-limit: 2 + DHCPv6: + status: enabled + do-request-address: true + do-request-prefix: false + IPv4: + status: enabled + addresses: + - 192.0.2.10/24 + multicast-addresses: + - 234.0.0.10 + gateway: 192.0.2.1 + time-to-live: 128 + multicast-time-to-live: 3 + DHCPv4: enabled + IPv4-autoconf: enabled + + # For this interface we do not know its name, but we know what device + # it is bound to. + - &device-interface + device: eth_2 + set-name: test-eth1 + flags: + - no-auto-start + IPv4: + status: enabled + addresses: + - "192.0.2.22/24" + gateway: 192.0.2.2 + time-to-live: 10 + multicast-time-to-live: 1 + DHCPv4: disabled + DHCPv4-server: + status: enabled + base-address: 192.0.2.128 + + # This virtual interface bound to the first one + - name: virt0 + bind-to: *main-interface + IPv6: + status: enabled + addresses: + - 2001:db8:111::2 + IPv4: + status: disabled + + # This VLAN interface that attaches to second Ethernet interface + - &vlan-interface + set-name: vlan0 + bind-to: *device-interface + VLAN: + status: enabled + tag: 2432 + IPv4: + status: enabled + DHCPv4: enabled + + # This 2nd VLAN interface is attached to one of the Ethernet interfaces + - set-name: vlan1 + bind-to: *main-interface + VLAN: + status: enabled + tag: 1234 + IPv4: + status: enabled + DHCPv4: enabled + + # This virtual interface bound to the VLAN interface + - name: virt1 + set-name: virt-over-vlan + bind-to: *vlan-interface + IPv6: + status: enabled + addresses: + - 2001:db8:abcd::11 + + # This interface IPv4 configuration is set to disabled + # in which case its IPv4 configuration is skipped + - name: dummy0 + IPv6: + status: enabled + IPv4: + status: disabled + addresses: + - 192.2.0.2 + gateway: 192.2.0.1 + time-to-live: 10 + multicast-time-to-live: 2 + + # This interface IPv4 and IPv6 configuration are both disabled. + # Basically the interface is non functional for IP connectivity. + - name: dummy1 + IPv6: + status: disabled + IPv4: + status: disabled + + SNTP: + server: sntp.foo.bar + timeout: 30 + bind-to: *vlan-interface diff --git a/tests/net/lib/config/testcase.yaml b/tests/net/lib/config/testcase.yaml new file mode 100644 index 0000000000000..ef9621c5fc036 --- /dev/null +++ b/tests/net/lib/config/testcase.yaml @@ -0,0 +1,12 @@ +common: + min_ram: 16 + depends_on: netif + # eventfd API does not work with native_posix so exclude it here + platform_exclude: + - native_sim + - native_sim/native/64 +tests: + net.config: + tags: + - net + - iface From 5de847f3de90860d489e5e6f84e154a0e8cc1a43 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Tue, 30 Sep 2025 15:34:04 +0300 Subject: [PATCH 11/16] net: config: Add set/get functions Add set and get functions that return/set either the default settings or if user has changed them, the user adjusted value. Make these two functions to use settings API. Signed-off-by: Jukka Rissanen --- include/zephyr/net/net_config.h | 37 + subsys/net/lib/config/Kconfig | 5 + subsys/net/lib/config/init.c | 1115 ++++++++++++++++++++++++++++++- 3 files changed, 1129 insertions(+), 28 deletions(-) diff --git a/include/zephyr/net/net_config.h b/include/zephyr/net/net_config.h index a389b77c61e3d..8a0b043bcca21 100644 --- a/include/zephyr/net/net_config.h +++ b/include/zephyr/net/net_config.h @@ -16,6 +16,12 @@ #include #include +#if defined(CONFIG_NET_CONFIG_SETTINGS) +#include +#else +struct networking; +#endif + #ifdef __cplusplus extern "C" { #endif @@ -99,6 +105,37 @@ int net_config_init_by_iface(struct net_if *iface, const char *app_info, */ int net_config_init_app(const struct device *dev, const char *app_info); +/* @brief Get network initialization configuration. + * + * @details This network configuration consists of initial read-only + * configuration and read-write configuration when the + * configuration is changed at runtime. + * + * @param cfg Caller supplied pointer to struct net_init_config where + * the configuration will be stored. + */ +int net_config_get(struct networking *cfg); + +/* @brief Set network initialization configuration. + * + * @details The user supplied configuration is saved to permanent + * storage. How this works: + * - If the config option in struct networking is different + * than the default one, then change the option and enable the _changed flag. + * - If the config option in struct networking is the same + * as the default one. + * + * @param cfg Caller supplied pointer to struct networking where + * the configuration will be read. + */ +int net_config_set(const struct networking *cfg); + +/* @brief Clear all network configuration. + * + * @details This will reset all runtime configuration back to defaults. + */ +int net_config_clear(void); + /** * @} */ diff --git a/subsys/net/lib/config/Kconfig b/subsys/net/lib/config/Kconfig index 0b5fc10c6764c..c62d945ec44e1 100644 --- a/subsys/net/lib/config/Kconfig +++ b/subsys/net/lib/config/Kconfig @@ -25,6 +25,11 @@ menuconfig NET_CONFIG_SETTINGS if NET_CONFIG_SETTINGS +config HEAP_MEM_POOL_ADD_SIZE_NET_CONFIG + int "Amount of heap needed for storing user configuration data" + default 1024 + depends on SETTINGS + config NET_CONFIG_AUTO_INIT bool "Init networking support automatically during device startup" default y if !(USB_DEVICE_NETWORK || USBD_CDC_ECM_CLASS || USBD_CDC_NCM_CLASS) diff --git a/subsys/net/lib/config/init.c b/subsys/net/lib/config/init.c index 34922f1237bfd..47ce216439833 100644 --- a/subsys/net/lib/config/init.c +++ b/subsys/net/lib/config/init.c @@ -15,6 +15,8 @@ LOG_MODULE_REGISTER(net_config, CONFIG_NET_CONFIG_LOG_LEVEL); #include #include +#include + #include #include #include @@ -37,6 +39,9 @@ LOG_MODULE_REGISTER(net_config, CONFIG_NET_CONFIG_LOG_LEVEL); const struct networking *net_config_get_init_config(void); +#define STORAGE_PARTITION storage_partition +#define STORAGE_PARTITION_ID FIXED_PARTITION_ID(STORAGE_PARTITION) + extern int net_init_clock_via_sntp(struct net_if *iface, const char *server, int timeout); @@ -44,6 +49,113 @@ extern int net_init_clock_via_sntp(struct net_if *iface, static K_SEM_DEFINE(waiter, 0, 1); static K_SEM_DEFINE(counter, 0, UINT_MAX); +#define SETTINGS_SUBTREE_NET_CONFIG "net/config" + +#if defined(CONFIG_SETTINGS) +static struct networking net_init_config_user; + +/* Make sure that various IP address fields are large enough to hold + * an address and other possible data. + */ +#define CHECK_FIELD(field, size) \ + BUILD_ASSERT(sizeof(((struct networking *)0)->interfaces[0].field) >= (size), \ + "Field " STRINGIFY(field) " is too small, expecting " STRINGIFY(size) " bytes") + +CHECK_FIELD(ipv4.ipv4_addresses[0].value, INET_ADDRSTRLEN + sizeof("/32") - 1); +CHECK_FIELD(ipv4.ipv4_multicast_addresses[0].value, INET_ADDRSTRLEN); +CHECK_FIELD(ipv4.gateway, INET_ADDRSTRLEN); +CHECK_FIELD(ipv4.dhcpv4_server.base_address, INET_ADDRSTRLEN); +CHECK_FIELD(ipv6.ipv6_addresses[0].value, INET6_ADDRSTRLEN + sizeof("/128") - 1); +CHECK_FIELD(ipv6.ipv6_multicast_addresses[0].value, NET_IPV6_ADDR_STR_LEN); +CHECK_FIELD(ipv6.prefixes[0].address, NET_IPV6_ADDR_STR_LEN); +BUILD_ASSERT(sizeof(((struct networking *)0)->sntp.server) >= INET6_ADDRSTRLEN); + +static bool settings_loaded; + +static int net_config_settings_set(const char *name, size_t len, + settings_read_cb read_cb, void *cb_arg); +static int net_config_settings_commit(void); +static int net_config_settings_export(int (*cb)(const char *name, + const void *value, size_t val_len)); +static int net_config_settings_get(const char *name, char *val, int val_len_max); + +static struct settings_handler net_config_settings_handler = { + .name = SETTINGS_SUBTREE_NET_CONFIG, + .h_get = net_config_settings_get, + .h_set = net_config_settings_set, + .h_commit = net_config_settings_commit, + .h_export = net_config_settings_export +}; + +static int net_config_settings_set(const char *name, size_t len, + settings_read_cb read_cb, void *cb_arg) +{ + const char *next; + int ret; + + if (settings_name_steq(name, "user", &next) && next == NULL) { + if (len != sizeof(struct networking)) { + NET_ERR("Error net_init_config too large %zu (expecting %zu)", + len, sizeof(struct networking)); + return -EINVAL; + } + + ret = read_cb(cb_arg, &net_init_config_user, + sizeof(net_init_config_user)); + if (ret == 0) { + /* The key is deleted */ + memset(&net_init_config_user, 0, sizeof(net_init_config_user)); + return 0; + } else if (ret > 0) { + return 0; + } + + NET_ERR("Error code list read failure: %d", ret); + + return ret; + } + + return -ENOENT; +} + +static int net_config_settings_commit(void) +{ + NET_DBG("loading all settings under <%s> handler is done", + SETTINGS_SUBTREE_NET_CONFIG); + return 0; +} + +static int net_config_settings_get(const char *name, char *val, int val_len_max) +{ + const char *next; + + if (settings_name_steq(name, "user", &next) && !next) { + if (val_len_max != sizeof(struct networking)) { + NET_ERR("Error networking struct too large %zu (expecting %zu)", + (size_t)val_len_max, sizeof(struct networking)); + return -EINVAL; + } + + memcpy((struct networking *)val, &net_init_config_user, val_len_max); + + return val_len_max; + } + + return -ENOENT; +} + +static int net_config_settings_export(int (*cb)(const char *name, + const void *value, size_t val_len)) +{ + NET_DBG("export keys under <%s> handler", SETTINGS_SUBTREE_NET_CONFIG); + + (void)cb(SETTINGS_SUBTREE_NET_CONFIG "/user", &net_init_config_user, + sizeof(net_init_config_user)); + + return 0; +} +#endif /* CONFIG_SETTINGS */ + #if defined(CONFIG_NET_NATIVE) static struct net_mgmt_event_callback mgmt_iface_cb; @@ -161,14 +273,14 @@ static struct net_if *get_interface(const struct networking *config, cfg = &config->interfaces[config_ifindex]; name = cfg->set_name; - if (name != NULL) { + if (name[0] != '\0') { iface = net_if_get_by_index(net_if_get_by_name(name)); } if (iface == NULL) { name = cfg->name; - if (name != NULL) { + if (name[0] != '\0') { iface = net_if_get_by_index(net_if_get_by_name(name)); } } @@ -186,7 +298,7 @@ static struct net_if *get_interface(const struct networking *config, } /* Use the default interface if nothing is found */ - if (iface == NULL && name == NULL) { + if (iface == NULL) { static char ifname[CONFIG_NET_INTERFACE_NAME_LEN + 1]; iface = net_if_get_default(); @@ -221,7 +333,7 @@ static void ipv6_setup(struct net_if *iface, const struct net_cfg_interfaces *cfg) { #if defined(CONFIG_NET_IPV6) - const struct networking_ipv6 *ipv6 = &cfg->ipv6; + const struct net_cfg_ipv6 *ipv6 = &cfg->ipv6; struct net_if_mcast_addr *ifmaddr; struct net_if_addr *ifaddr; bool ret; @@ -237,8 +349,7 @@ static void ipv6_setup(struct net_if *iface, struct sockaddr_in6 addr = { 0 }; uint8_t prefix_len; - if (ipv6->ipv6_addresses[j].value == NULL || - ipv6->ipv6_addresses[j].value[0] == '\0') { + if (ipv6->ipv6_addresses[j].value[0] == '\0') { continue; } @@ -281,8 +392,7 @@ static void ipv6_setup(struct net_if *iface, ARRAY_FOR_EACH(ipv6->ipv6_multicast_addresses, j) { struct sockaddr_in6 addr = { 0 }; - if (ipv6->ipv6_multicast_addresses[j].value == NULL || - ipv6->ipv6_multicast_addresses[j].value[0] == '\0') { + if (ipv6->ipv6_multicast_addresses[j].value[0] == '\0') { continue; } @@ -316,8 +426,7 @@ static void ipv6_setup(struct net_if *iface, struct net_if_ipv6_prefix *prefix; struct sockaddr_in6 addr = { 0 }; - if (ipv6->prefixes[j].address == NULL || - ipv6->prefixes[j].address[0] == '\0') { + if (ipv6->prefixes[j].address[0] == '\0') { continue; } @@ -378,7 +487,7 @@ static void ipv4_setup(struct net_if *iface, const struct net_cfg_interfaces *cfg) { #if defined(CONFIG_NET_IPV4) - const struct networking_ipv4 *ipv4 = &cfg->ipv4; + const struct net_cfg_ipv4 *ipv4 = &cfg->ipv4; struct net_if_addr *ifaddr; struct net_if_mcast_addr *ifmaddr; bool ret; @@ -394,8 +503,7 @@ static void ipv4_setup(struct net_if *iface, struct sockaddr_in addr = { 0 }; uint8_t mask_len = 0; - if (ipv4->ipv4_addresses[j].value == NULL || - ipv4->ipv4_addresses[j].value[0] == '\0') { + if (ipv4->ipv4_addresses[j].value[0] == '\0') { continue; } @@ -468,8 +576,7 @@ static void ipv4_setup(struct net_if *iface, ARRAY_FOR_EACH(ipv4->ipv4_multicast_addresses, j) { struct sockaddr_in addr = { 0 }; - if (ipv4->ipv4_multicast_addresses[j].value == NULL || - ipv4->ipv4_multicast_addresses[j].value[0] == '\0') { + if (ipv4->ipv4_multicast_addresses[j].value[0] == '\0') { continue; } @@ -507,7 +614,7 @@ static void ipv4_setup(struct net_if *iface, net_if_ipv4_set_mcast_ttl(iface, ipv4->multicast_time_to_live); } - if (ipv4->gateway != NULL && ipv4->gateway[0] != '\0') { + if (ipv4->gateway[0] != '\0') { struct sockaddr_in addr = { 0 }; ret = parse_mask(ipv4->gateway, @@ -536,7 +643,7 @@ static void ipv4_setup(struct net_if *iface, (ipv4->dhcpv4_server.status), (false))) { struct sockaddr_in addr = { 0 }; - if (ipv4->dhcpv4_server.base_address != NULL) { + if (ipv4->dhcpv4_server.base_address[0] != '\0') { ret = parse_mask(ipv4->dhcpv4_server.base_address, strlen(ipv4->dhcpv4_server.base_address), (struct sockaddr *)&addr, NULL); @@ -570,7 +677,7 @@ static void vlan_setup(const struct networking *config, const struct net_cfg_interfaces *cfg) { #if defined(CONFIG_NET_VLAN) - const struct networking_vlan *vlan = &cfg->vlan; + const struct net_cfg_vlan *vlan = &cfg->vlan; struct net_if *bound = NULL; int ret, ifindex; @@ -598,7 +705,7 @@ static void vlan_setup(const struct networking *config, net_if_get_by_index(ifindex), vlan->tag))); - if (cfg->set_name != NULL) { + if (cfg->set_name[0] != '\0') { struct net_if *iface; iface = net_eth_get_vlan_iface(bound, vlan->tag); @@ -757,23 +864,946 @@ static int process_iface_flag(struct net_if *iface, const char *flag_str) return 0; } -static int generated_net_config_init_app(const struct device *dev, - const char *app_info) +#define UPDATE_IFACE_VAL(x) \ + if (iface_cfg->__##x##_changed) { \ + cfg->interfaces[i].x = \ + net_init_config_user.interfaces[i].x; \ + cfg->interfaces[i].__##x##_changed = true; \ + } else { \ + cfg->interfaces[i].x = \ + config->interfaces[i].x; \ + cfg->interfaces[i].__##x##_changed = false; \ + } + +#define UPDATE_IFACE_STR_VAL(x) \ + if (iface_cfg->__##x##_changed) { \ + strcpy(cfg->interfaces[i].x, \ + net_init_config_user.interfaces[i].x); \ + cfg->interfaces[i].__##x##_changed = true; \ + } else { \ + strcpy(cfg->interfaces[i].x, \ + config->interfaces[i].x); \ + cfg->interfaces[i].__##x##_changed = false; \ + } + +#define UPDATE_FLAGS_VAL(x) \ + if (iface_cfg->flags[j].__##x##_changed) { \ + strcpy(cfg->interfaces[i].flags[j].x, \ + net_init_config_user.interfaces[i].flags[j].x); \ + cfg->interfaces[i].flags[j].__##x##_changed = true; \ + } else { \ + strcpy(cfg->interfaces[i].flags[j].x, \ + config->interfaces[i].flags[j].x); \ + cfg->interfaces[i].flags[j].__##x##_changed = false; \ + } + +#define UPDATE_IPV6_VAL(x) \ + if (iface_cfg->ipv6.__##x##_changed) { \ + cfg->interfaces[i].ipv6.x = \ + net_init_config_user.interfaces[i].ipv6.x; \ + cfg->interfaces[i].ipv6.__##x##_changed = true; \ + } else { \ + cfg->interfaces[i].ipv6.x = \ + config->interfaces[i].ipv6.x; \ + cfg->interfaces[i].ipv6.__##x##_changed = false; \ + } + +#define UPDATE_IPV6_ADDR_VAL(x) \ + if (iface_cfg->ipv6.ipv6_addresses[j].__##x##_changed) { \ + strcpy(cfg->interfaces[i].ipv6.ipv6_addresses[j].x, \ + net_init_config_user.interfaces[i].ipv6.ipv6_addresses[j].x); \ + cfg->interfaces[i].ipv6.ipv6_addresses[j].__##x##_changed = true; \ + } else { \ + strcpy(cfg->interfaces[i].ipv6.ipv6_addresses[j].x, \ + config->interfaces[i].ipv6.ipv6_addresses[j].x); \ + cfg->interfaces[i].ipv6.ipv6_addresses[j].__##x##_changed = false; \ + } + +#define UPDATE_IPV6_MADDR_VAL(x) \ + if (iface_cfg->ipv6.ipv6_multicast_addresses[j].__##x##_changed) { \ + strcpy(cfg->interfaces[i].ipv6.ipv6_multicast_addresses[j].x, \ + net_init_config_user.interfaces[i].ipv6.ipv6_multicast_addresses[j].x); \ + cfg->interfaces[i].ipv6.ipv6_multicast_addresses[j]. \ + __##x##_changed = true; \ + } else { \ + strcpy(cfg->interfaces[i].ipv6.ipv6_multicast_addresses[j].x, \ + config->interfaces[i].ipv6.ipv6_multicast_addresses[j].x); \ + cfg->interfaces[i].ipv6.ipv6_multicast_addresses[j].\ + __##x##_changed = false; \ + } + +#define UPDATE_IPV6_PREFIX_VAL(x) \ + if (iface_cfg->ipv6.prefixes[j].__##x##_changed) { \ + cfg->interfaces[i].ipv6.prefixes[j].x = \ + net_init_config_user.interfaces[i].ipv6.prefixes[j].x; \ + cfg->interfaces[i].ipv6.prefixes[j].__##x##_changed = true; \ + } else { \ + cfg->interfaces[i].ipv6.prefixes[j].x = \ + config->interfaces[i].ipv6.prefixes[j].x; \ + cfg->interfaces[i].ipv6.prefixes[j].__##x##_changed = false; \ + } + +#define UPDATE_IPV6_PREFIX_STR_VAL(x) \ + if (iface_cfg->ipv6.prefixes[j].__##x##_changed) { \ + strcpy(cfg->interfaces[i].ipv6.prefixes[j].x, \ + net_init_config_user.interfaces[i].ipv6.prefixes[j].x); \ + cfg->interfaces[i].ipv6.prefixes[j].__##x##_changed = true; \ + } else { \ + strcpy(cfg->interfaces[i].ipv6.prefixes[j].x, \ + config->interfaces[i].ipv6.prefixes[j].x); \ + cfg->interfaces[i].ipv6.prefixes[j].__##x##_changed = false; \ + } + +#define UPDATE_IPV6_DHCPV6_VAL(x) \ + if (iface_cfg->ipv6.dhcpv6.__##x##_changed) { \ + cfg->interfaces[i].ipv6.dhcpv6.x = \ + net_init_config_user.interfaces[i].ipv6.dhcpv6.x; \ + cfg->interfaces[i].ipv6.dhcpv6.__##x##_changed = true; \ + } else { \ + cfg->interfaces[i].ipv6.dhcpv6.x = \ + config->interfaces[i].ipv6.dhcpv6.x; \ + cfg->interfaces[i].ipv6.dhcpv6.__##x##_changed = false; \ + } + +#define UPDATE_IPV4_AUTOCONF_VAL(x) \ + if (iface_cfg->ipv4.ipv4_autoconf.__##x##_changed) { \ + cfg->interfaces[i].ipv4.ipv4_autoconf.x = \ + net_init_config_user.interfaces[i].ipv4.ipv4_autoconf.x; \ + cfg->interfaces[i].ipv4.ipv4_autoconf.__##x##_changed = true; \ + } else { \ + cfg->interfaces[i].ipv4.ipv4_autoconf.x = \ + config->interfaces[i].ipv4.ipv4_autoconf.x; \ + cfg->interfaces[i].ipv4.ipv4_autoconf.__##x##_changed = false; \ + } + +#define UPDATE_IPV4_DHCPV4_VAL(x) \ + if (iface_cfg->ipv4.dhcpv4.__##x##_changed) { \ + cfg->interfaces[i].ipv4.dhcpv4.x = \ + net_init_config_user.interfaces[i].ipv4.dhcpv4.x; \ + cfg->interfaces[i].ipv4.dhcpv4.__##x##_changed = true; \ + } else { \ + cfg->interfaces[i].ipv4.dhcpv4.x = \ + config->interfaces[i].ipv4.dhcpv4.x; \ + cfg->interfaces[i].ipv4.dhcpv4.__##x##_changed = false; \ + } + +#define UPDATE_IPV4_VAL(x) \ + if (iface_cfg->ipv4.__##x##_changed) { \ + cfg->interfaces[i].ipv4.x = \ + net_init_config_user.interfaces[i].ipv4.x; \ + cfg->interfaces[i].ipv4.__##x##_changed = true; \ + } else { \ + cfg->interfaces[i].ipv4.x = \ + config->interfaces[i].ipv4.x; \ + cfg->interfaces[i].ipv4.__##x##_changed = false; \ + } + +#define UPDATE_IPV4_STR_VAL(x) \ + if (iface_cfg->ipv4.__##x##_changed) { \ + strcpy(cfg->interfaces[i].ipv4.x, \ + net_init_config_user.interfaces[i].ipv4.x); \ + cfg->interfaces[i].ipv4.__##x##_changed = true; \ + } else { \ + strcpy(cfg->interfaces[i].ipv4.x, \ + config->interfaces[i].ipv4.x); \ + cfg->interfaces[i].ipv4.__##x##_changed = false; \ + } + +#define UPDATE_IPV4_ADDR_VAL(x) \ + if (iface_cfg->ipv4.ipv4_addresses[j].__##x##_changed) { \ + strcpy(cfg->interfaces[i].ipv4.ipv4_addresses[j].x, \ + net_init_config_user.interfaces[i].ipv4.ipv4_addresses[j].x); \ + cfg->interfaces[i].ipv4.ipv4_addresses[j].__##x##_changed = true; \ + } else { \ + strcpy(cfg->interfaces[i].ipv4.ipv4_addresses[j].x, \ + config->interfaces[i].ipv4.ipv4_addresses[j].x); \ + cfg->interfaces[i].ipv4.ipv4_addresses[j].__##x##_changed = false; \ + } + +#define UPDATE_IPV4_MADDR_VAL(x) \ + if (iface_cfg->ipv4.ipv4_multicast_addresses[j].__##x##_changed) { \ + strcpy(cfg->interfaces[i].ipv4.ipv4_multicast_addresses[j].x, \ + net_init_config_user.interfaces[i].ipv4.ipv4_multicast_addresses[j].x); \ + cfg->interfaces[i].ipv4.ipv4_multicast_addresses[j]. \ + __##x##_changed = true; \ + } else { \ + strcpy(cfg->interfaces[i].ipv4.ipv4_multicast_addresses[j].x, \ + config->interfaces[i].ipv4.ipv4_multicast_addresses[j].x); \ + cfg->interfaces[i].ipv4.ipv4_multicast_addresses[j]. \ + __##x##_changed = false; \ + } + +#define UPDATE_IPV4_DHCPV4_SERVER_VAL(x) \ + if (iface_cfg->ipv4.dhcpv4_server.__##x##_changed) { \ + cfg->interfaces[i].ipv4.dhcpv4_server.x = \ + net_init_config_user.interfaces[i].ipv4.dhcpv4_server.x; \ + cfg->interfaces[i].ipv4.dhcpv4_server.__##x##_changed = true; \ + } else { \ + cfg->interfaces[i].ipv4.dhcpv4_server.x = \ + config->interfaces[i].ipv4.dhcpv4_server.x; \ + cfg->interfaces[i].ipv4.dhcpv4_server.__##x##_changed = false; \ + } + +#define UPDATE_IPV4_DHCPV4_SERVER_STR_VAL(x) \ + if (iface_cfg->ipv4.dhcpv4_server.__##x##_changed) { \ + strcpy(cfg->interfaces[i].ipv4.dhcpv4_server.x, \ + net_init_config_user.interfaces[i].ipv4.dhcpv4_server.x); \ + cfg->interfaces[i].ipv4.dhcpv4_server.__##x##_changed = true; \ + } else { \ + strcpy(cfg->interfaces[i].ipv4.dhcpv4_server.x, \ + config->interfaces[i].ipv4.dhcpv4_server.x); \ + cfg->interfaces[i].ipv4.dhcpv4_server.__##x##_changed = false; \ + } + +#define UPDATE_IFACE_VLAN_VAL(x) \ + if (iface_cfg->vlan.__##x##_changed) { \ + cfg->interfaces[i].vlan.x = \ + net_init_config_user.interfaces[i].vlan.x; \ + cfg->interfaces[i].vlan.__##x##_changed = true; \ + } else { \ + cfg->interfaces[i].vlan.x = \ + config->interfaces[i].vlan.x; \ + cfg->interfaces[i].vlan.__##x##_changed = false; \ + } + +#define UPDATE_IEEE802154_VAL(x) \ + if (net_init_config_user.ieee_802_15_4.__##x##_changed) { \ + cfg->ieee_802_15_4.x = net_init_config_user.ieee_802_15_4.x; \ + cfg->ieee_802_15_4.__##x##_changed = true; \ + } else { \ + cfg->ieee_802_15_4.x = config->ieee_802_15_4.x; \ + cfg->ieee_802_15_4.__##x##_changed = false; \ + } + +#define UPDATE_IEEE802154_SECURITY_KEY_VAL(x) \ + if (net_init_config_user.ieee_802_15_4.security_key[0].__##x##_changed) { \ + cfg->ieee_802_15_4.security_key[0].x = \ + net_init_config_user.ieee_802_15_4.security_key[0].x; \ + cfg->ieee_802_15_4.security_key[0].__##x##_changed = true; \ + } else { \ + cfg->ieee_802_15_4.security_key[0].x = \ + config->ieee_802_15_4.security_key[0].x; \ + cfg->ieee_802_15_4.security_key[0].__##x##_changed = false; \ + } + +#define UPDATE_SNTP_VAL(x) \ + if (net_init_config_user.sntp.__##x##_changed) { \ + cfg->sntp.x = net_init_config_user.sntp.x; \ + cfg->sntp.__##x##_changed = true; \ + } else { \ + cfg->sntp.x = config->sntp.x; \ + cfg->sntp.__##x##_changed = false; \ + } + +#define UPDATE_SNTP_STR_VAL(x) \ + if (net_init_config_user.sntp.__##x##_changed) { \ + strcpy(cfg->sntp.x, net_init_config_user.sntp.x); \ + cfg->sntp.__##x##_changed = true; \ + } else { \ + strcpy(cfg->sntp.x, config->sntp.x); \ + cfg->sntp.__##x##_changed = false; \ + } + +int net_config_get(struct networking *cfg) { +#if defined(CONFIG_SETTINGS) && defined(CONFIG_SETTINGS_RUNTIME) const struct networking *config; - int ret, ifindex; + int ret; + + if (cfg == NULL) { + return -EINVAL; + } + + config = net_config_get_init_config(); + if (config == NULL) { + NET_ERR("Default network configuration not found."); + return -ENOENT; + } + + if (!settings_loaded) { + settings_load(); + + ret = settings_runtime_get(SETTINGS_SUBTREE_NET_CONFIG "/user", + &net_init_config_user, + sizeof(net_init_config_user)); + if (ret < 0) { + NET_ERR("Cannot get user network configuration (%d)", ret); + return ret; + } + + settings_loaded = true; + } + + /* Make a union of the default config and the user modified one and + * return results in cfg pointer. + */ + ARRAY_FOR_EACH(net_init_config_user.interfaces, i) { + struct net_cfg_interfaces *iface_cfg; + + iface_cfg = &net_init_config_user.interfaces[i]; + + UPDATE_IFACE_VAL(bind_to); + UPDATE_IFACE_STR_VAL(name); + UPDATE_IFACE_VAL(device_name); + UPDATE_IFACE_STR_VAL(set_name); + UPDATE_IFACE_VAL(set_default); + + ARRAY_FOR_EACH(iface_cfg->flags, j) { + UPDATE_FLAGS_VAL(value); + } + + /* IPv6 config */ + if (IS_ENABLED(CONFIG_NET_IPV6)) { + UPDATE_IPV6_VAL(status); + UPDATE_IPV6_VAL(hop_limit); + UPDATE_IPV6_VAL(multicast_hop_limit); + + ARRAY_FOR_EACH(iface_cfg->ipv6.ipv6_addresses, j) { + UPDATE_IPV6_ADDR_VAL(value); + } + + ARRAY_FOR_EACH(iface_cfg->ipv6.ipv6_multicast_addresses, j) { + UPDATE_IPV6_MADDR_VAL(value); + } + + ARRAY_FOR_EACH(iface_cfg->ipv6.prefixes, j) { + UPDATE_IPV6_PREFIX_STR_VAL(address); + UPDATE_IPV6_PREFIX_VAL(len); + UPDATE_IPV6_PREFIX_VAL(lifetime); + } + + if (IS_ENABLED(CONFIG_NET_DHCPV6)) { + UPDATE_IPV6_DHCPV6_VAL(status); + UPDATE_IPV6_DHCPV6_VAL(do_request_address); + UPDATE_IPV6_DHCPV6_VAL(do_request_prefix); + } + } + + /* IPv4 config */ + if (IS_ENABLED(CONFIG_NET_IPV4)) { + UPDATE_IPV4_VAL(status); + UPDATE_IPV4_VAL(time_to_live); + UPDATE_IPV4_VAL(multicast_time_to_live); + UPDATE_IPV4_STR_VAL(gateway); + + if (IS_ENABLED(CONFIG_NET_DHCPV4)) { + UPDATE_IPV4_DHCPV4_VAL(status); + } + + if (IS_ENABLED(CONFIG_NET_IPV4_AUTO)) { + UPDATE_IPV4_AUTOCONF_VAL(status); + } + + ARRAY_FOR_EACH(iface_cfg->ipv4.ipv4_addresses, j) { + UPDATE_IPV4_ADDR_VAL(value); + } + + ARRAY_FOR_EACH(iface_cfg->ipv4.ipv4_multicast_addresses, j) { + UPDATE_IPV4_MADDR_VAL(value); + } + + if (IS_ENABLED(CONFIG_NET_DHCPV4_SERVER)) { + UPDATE_IPV4_DHCPV4_SERVER_VAL(status); + UPDATE_IPV4_DHCPV4_SERVER_STR_VAL(base_address); + } + } + + /* VLAN config */ + if (IS_ENABLED(CONFIG_NET_VLAN)) { + UPDATE_IFACE_VLAN_VAL(status); + UPDATE_IFACE_VLAN_VAL(tag); + } + } + + /* IEEE 802.15.4 config */ + if (IS_ENABLED(CONFIG_NET_L2_IEEE802154)) { + UPDATE_IEEE802154_VAL(status); + UPDATE_IEEE802154_VAL(bind_to); + UPDATE_IEEE802154_VAL(pan_id); + UPDATE_IEEE802154_VAL(channel); + UPDATE_IEEE802154_VAL(tx_power); + UPDATE_IEEE802154_VAL(security_key_mode); + UPDATE_IEEE802154_VAL(security_level); + UPDATE_IEEE802154_VAL(ack_required); + UPDATE_IEEE802154_SECURITY_KEY_VAL(value); + } + + /* SNTP config */ + if (IS_ENABLED(CONFIG_NET_CONFIG_CLOCK_SNTP_INIT)) { + UPDATE_SNTP_VAL(status); + UPDATE_SNTP_VAL(bind_to); + UPDATE_SNTP_STR_VAL(server); + UPDATE_SNTP_VAL(timeout); + } + + return 0; +#else + const struct networking *config; + + config = net_config_get_init_config(); + if (config == NULL) { + NET_ERR("Default network configuration not found."); + return -ENOENT; + } + + memcpy(cfg, config, sizeof(*config)); + + return 0; +#endif /* CONFIG_SETTINGS */ +} + +#define SET_IFACE_VAL(x) \ + if (net_init_config_user.interfaces[i].__##x##_changed == true) { \ + if (cfg->interfaces[i].x != net_init_config_user.interfaces[i].x) { \ + net_init_config_user.interfaces[i].x = cfg->interfaces[i].x; \ + } \ + } else if (cfg->interfaces[i].__##x##_changed && \ + cfg->interfaces[i].x != config->interfaces[i].x) { \ + net_init_config_user.interfaces[i].x = cfg->interfaces[i].x; \ + net_init_config_user.interfaces[i].__##x##_changed = true; \ + } + +#define SET_IFACE_STR_VAL(x) \ + if (net_init_config_user.interfaces[i].__##x##_changed == true) { \ + if (strcmp(cfg->interfaces[i].x, \ + net_init_config_user.interfaces[i].x) != 0) { \ + strcpy(net_init_config_user.interfaces[i].x, \ + cfg->interfaces[i].x); \ + } \ + } else if (cfg->interfaces[i].__##x##_changed && \ + strcmp(cfg->interfaces[i].x, \ + config->interfaces[i].x) != 0) { \ + strcpy(net_init_config_user.interfaces[i].x, \ + cfg->interfaces[i].x); \ + net_init_config_user.interfaces[i].__##x##_changed = true; \ + } + +#define SET_FLAGS_VAL(x) \ + if (net_init_config_user.interfaces[i].flags[j].__##x##_changed == true) { \ + if (strcmp(cfg->interfaces[i].flags[j].x, \ + net_init_config_user.interfaces[i].flags[j].x) != 0) { \ + strcpy(net_init_config_user.interfaces[i].flags[j].x, \ + cfg->interfaces[i].flags[j].x); \ + } \ + } else if (cfg->interfaces[i].flags[j].__##x##_changed && \ + strcmp(cfg->interfaces[i].flags[j].x, \ + config->interfaces[i].flags[j].x) != 0) { \ + strcpy(net_init_config_user.interfaces[i].flags[j].x, \ + cfg->interfaces[i].flags[j].x); \ + net_init_config_user.interfaces[i].flags[j].__##x##_changed = true; \ + } + +#define SET_IPV6_VAL(x) \ + if (net_init_config_user.interfaces[i].ipv6.__##x##_changed == true) { \ + if (cfg->interfaces[i].ipv6.x != net_init_config_user.interfaces[i].ipv6.x) { \ + net_init_config_user.interfaces[i].ipv6.x = \ + cfg->interfaces[i].ipv6.x; \ + } \ + } else if (cfg->interfaces[i].ipv6.__##x##_changed && \ + cfg->interfaces[i].ipv6.x != config->interfaces[i].ipv6.x) { \ + net_init_config_user.interfaces[i].ipv6.x = \ + cfg->interfaces[i].ipv6.x; \ + net_init_config_user.interfaces[i].ipv6.__##x##_changed = true; \ + } + +#define SET_IPV6_ADDR_VAL(x) \ + if (net_init_config_user.interfaces[i].ipv6.ipv6_addresses[j]. \ + __##x##_changed == true) { \ + if (strcmp(cfg->interfaces[i].ipv6.ipv6_addresses[j].x, \ + net_init_config_user.interfaces[i]. \ + ipv6.ipv6_addresses[j].x) != 0) { \ + strcpy(net_init_config_user.interfaces[i].ipv6.ipv6_addresses[j].x, \ + cfg->interfaces[i].ipv6.ipv6_addresses[j].x); \ + } \ + } else if (cfg->interfaces[i].ipv6.ipv6_addresses[j].__##x##_changed && \ + strcmp(cfg->interfaces[i].ipv6.ipv6_addresses[j].x, \ + config->interfaces[i].ipv6.ipv6_addresses[j].x) != 0) { \ + strcpy(net_init_config_user.interfaces[i].ipv6.ipv6_addresses[j].x, \ + cfg->interfaces[i].ipv6.ipv6_addresses[j].x); \ + net_init_config_user.interfaces[i].ipv6.ipv6_addresses[j].__##x##_changed = true; \ + } + +#define SET_IPV6_MADDR_VAL(x) \ + if (net_init_config_user.interfaces[i].ipv6.ipv6_multicast_addresses[j]. \ + __##x##_changed == true) { \ + if (strcmp(cfg->interfaces[i].ipv6.ipv6_multicast_addresses[j].x, \ + net_init_config_user.interfaces[i].ipv6.ipv6_multicast_addresses[j].x) != 0) { \ + strcpy(net_init_config_user.interfaces[i].ipv6. \ + ipv6_multicast_addresses[j].x, \ + cfg->interfaces[i].ipv6.ipv6_multicast_addresses[j].x); \ + } \ + } else if (cfg->interfaces[i].ipv6.ipv6_multicast_addresses[j].__##x##_changed && \ + strcmp(cfg->interfaces[i].ipv6.ipv6_multicast_addresses[j].x, \ + config->interfaces[i].ipv6.ipv6_multicast_addresses[j].x) != 0) { \ + strcpy(net_init_config_user.interfaces[i].ipv6. \ + ipv6_multicast_addresses[j].x, \ + cfg->interfaces[i].ipv6.ipv6_multicast_addresses[j].x); \ + net_init_config_user.interfaces[i].ipv6.ipv6_multicast_addresses[j]. \ + __##x##_changed = true; \ + } + +#define SET_IPV6_PREFIX_VAL(x) \ + if (net_init_config_user.interfaces[i].ipv6.prefixes[j]. \ + __##x##_changed == true) { \ + if (cfg->interfaces[i].ipv6.prefixes[j].x != \ + net_init_config_user.interfaces[i].ipv6.prefixes[j].x) { \ + net_init_config_user.interfaces[i].ipv6.prefixes[j].x = \ + cfg->interfaces[i].ipv6.prefixes[j].x; \ + } \ + } else if (cfg->interfaces[i].ipv6.prefixes[j].__##x##_changed && \ + cfg->interfaces[i].ipv6.prefixes[j].x != \ + config->interfaces[i].ipv6.prefixes[j].x) { \ + net_init_config_user.interfaces[i].ipv6.prefixes[j].x = \ + cfg->interfaces[i].ipv6.prefixes[j].x; \ + net_init_config_user.interfaces[i].ipv6.prefixes[j]. \ + __##x##_changed = true; \ + } + +#define SET_IPV6_PREFIX_STR_VAL(x) \ + if (net_init_config_user.interfaces[i].ipv6.prefixes[j]. \ + __##x##_changed == true) { \ + if (strcmp(cfg->interfaces[i].ipv6.prefixes[j].x, \ + net_init_config_user.interfaces[i].ipv6.prefixes[j].x) != 0) { \ + strcpy(net_init_config_user.interfaces[i].ipv6.prefixes[j].x, \ + cfg->interfaces[i].ipv6.prefixes[j].x); \ + } \ + } else if (cfg->interfaces[i].ipv6.prefixes[j].__##x##_changed && \ + strcmp(cfg->interfaces[i].ipv6.prefixes[j].x, \ + config->interfaces[i].ipv6.prefixes[j].x) != 0) { \ + strcpy(net_init_config_user.interfaces[i].ipv6.prefixes[j].x, \ + cfg->interfaces[i].ipv6.prefixes[j].x); \ + net_init_config_user.interfaces[i].ipv6.prefixes[j]. \ + __##x##_changed = true; \ + } + +#define SET_IPV6_DHCPV6_VAL(x) \ + if (net_init_config_user.interfaces[i].ipv6.dhcpv6. \ + __##x##_changed == true) { \ + if (cfg->interfaces[i].ipv6.dhcpv6.x != \ + net_init_config_user.interfaces[i].ipv6.dhcpv6.x) { \ + net_init_config_user.interfaces[i].ipv6.dhcpv6.x = \ + cfg->interfaces[i].ipv6.dhcpv6.x; \ + } \ + } else if (cfg->interfaces[i].ipv6.dhcpv6.__##x##_changed && \ + cfg->interfaces[i].ipv6.dhcpv6.x != \ + config->interfaces[i].ipv6.dhcpv6.x) { \ + net_init_config_user.interfaces[i].ipv6.dhcpv6.x = \ + cfg->interfaces[i].ipv6.dhcpv6.x; \ + net_init_config_user.interfaces[i].ipv6.dhcpv6. \ + __##x##_changed = true; \ + } + +#define SET_IPV4_DHCPV4_VAL(x) \ + if (net_init_config_user.interfaces[i].ipv4.dhcpv4. \ + __##x##_changed == true) { \ + if (cfg->interfaces[i].ipv4.dhcpv4.x != \ + net_init_config_user.interfaces[i].ipv4.dhcpv4.x) { \ + net_init_config_user.interfaces[i].ipv4.dhcpv4.x = \ + cfg->interfaces[i].ipv4.dhcpv4.x; \ + } \ + } else if (cfg->interfaces[i].ipv4.dhcpv4.__##x##_changed && \ + cfg->interfaces[i].ipv4.dhcpv4.x != \ + config->interfaces[i].ipv4.dhcpv4.x) { \ + net_init_config_user.interfaces[i].ipv4.dhcpv4.x = \ + cfg->interfaces[i].ipv4.dhcpv4.x; \ + net_init_config_user.interfaces[i].ipv4.dhcpv4. \ + __##x##_changed = true; \ + } + +#define SET_IPV4_AUTOCONF_VAL(x) \ + if (net_init_config_user.interfaces[i].ipv4.ipv4_autoconf. \ + __##x##_changed == true) { \ + if (cfg->interfaces[i].ipv4.ipv4_autoconf.x != \ + net_init_config_user.interfaces[i].ipv4.ipv4_autoconf.x) { \ + net_init_config_user.interfaces[i].ipv4.ipv4_autoconf.x = \ + cfg->interfaces[i].ipv4.ipv4_autoconf.x; \ + } \ + } else if (cfg->interfaces[i].ipv4.ipv4_autoconf.__##x##_changed && \ + cfg->interfaces[i].ipv4.ipv4_autoconf.x != \ + config->interfaces[i].ipv4.ipv4_autoconf.x) { \ + net_init_config_user.interfaces[i].ipv4.ipv4_autoconf.x = \ + cfg->interfaces[i].ipv4.ipv4_autoconf.x; \ + net_init_config_user.interfaces[i].ipv4.ipv4_autoconf. \ + __##x##_changed = true; \ + } + +#define SET_IPV4_VAL(x) \ + if (net_init_config_user.interfaces[i].ipv4.__##x##_changed == true) { \ + if (cfg->interfaces[i].ipv4.x != \ + net_init_config_user.interfaces[i].ipv4.x) { \ + net_init_config_user.interfaces[i].ipv4.x = \ + cfg->interfaces[i].ipv4.x; \ + } \ + } else if (cfg->interfaces[i].ipv4.__##x##_changed && \ + cfg->interfaces[i].ipv4.x != \ + config->interfaces[i].ipv4.x) { \ + net_init_config_user.interfaces[i].ipv4.x = \ + cfg->interfaces[i].ipv4.x; \ + net_init_config_user.interfaces[i].ipv4.__##x##_changed = true; \ + } + +#define SET_IPV4_STR_VAL(x) \ + if (net_init_config_user.interfaces[i].ipv4.__##x##_changed == true) { \ + if (strcmp(cfg->interfaces[i].ipv4.x, \ + net_init_config_user.interfaces[i].ipv4.x) != 0) { \ + strcpy(net_init_config_user.interfaces[i].ipv4.x, \ + cfg->interfaces[i].ipv4.x); \ + } \ + } else if (cfg->interfaces[i].ipv4.__##x##_changed && \ + strcmp(cfg->interfaces[i].ipv4.x, \ + config->interfaces[i].ipv4.x) != 0) { \ + strcpy(net_init_config_user.interfaces[i].ipv4.x, \ + cfg->interfaces[i].ipv4.x); \ + net_init_config_user.interfaces[i].ipv4.__##x##_changed = true; \ + } + +#define SET_IPV4_ADDR_VAL(x) \ + if (net_init_config_user.interfaces[i].ipv4.ipv4_addresses[j]. \ + __##x##_changed == true) { \ + if (strcmp(cfg->interfaces[i].ipv4.ipv4_addresses[j].x, \ + net_init_config_user.interfaces[i]. \ + ipv4.ipv4_addresses[j].x) != 0) { \ + strcpy(net_init_config_user.interfaces[i]. \ + ipv4.ipv4_addresses[j].x, \ + cfg->interfaces[i].ipv4.ipv4_addresses[j].x); \ + } \ + } else if (cfg->interfaces[i].ipv4.ipv4_addresses[j].__##x##_changed && \ + strcmp(cfg->interfaces[i].ipv4.ipv4_addresses[j].x, \ + config->interfaces[i].ipv4.ipv4_addresses[j].x) != 0) { \ + strcpy(net_init_config_user.interfaces[i].ipv4.ipv4_addresses[j].x, \ + cfg->interfaces[i].ipv4.ipv4_addresses[j].x); \ + net_init_config_user.interfaces[i].ipv4.ipv4_addresses[j]. \ + __##x##_changed = true; \ + } + +#define SET_IPV4_MADDR_VAL(x) \ + if (net_init_config_user.interfaces[i].ipv4.ipv4_multicast_addresses[j]. \ + __##x##_changed == true) { \ + if (strcmp(cfg->interfaces[i].ipv4.ipv4_multicast_addresses[j].x, \ + net_init_config_user.interfaces[i].ipv4. \ + ipv4_multicast_addresses[j].x) != 0) { \ + strcpy(net_init_config_user.interfaces[i].ipv4. \ + ipv4_multicast_addresses[j].x, \ + cfg->interfaces[i].ipv4.ipv4_multicast_addresses[j].x); \ + } \ + } else if (cfg->interfaces[i].ipv4.ipv4_multicast_addresses[j].__##x##_changed && \ + strcmp(cfg->interfaces[i].ipv4.ipv4_multicast_addresses[j].x, \ + config->interfaces[i].ipv4. \ + ipv4_multicast_addresses[j].x) != 0) { \ + strcpy(net_init_config_user.interfaces[i]. \ + ipv4.ipv4_multicast_addresses[j].x, \ + cfg->interfaces[i].ipv4.ipv4_multicast_addresses[j].x); \ + net_init_config_user.interfaces[i].ipv4.ipv4_multicast_addresses[j]. \ + __##x##_changed = true; \ + } + +#define SET_IPV4_DHCPV4_SERVER_VAL(x) \ + if (net_init_config_user.interfaces[i].ipv4.dhcpv4_server. \ + __##x##_changed == true) { \ + if (cfg->interfaces[i].ipv4.dhcpv4_server.x != \ + net_init_config_user.interfaces[i].ipv4.dhcpv4_server.x) { \ + net_init_config_user.interfaces[i].ipv4.dhcpv4_server.x = \ + cfg->interfaces[i].ipv4.dhcpv4_server.x; \ + } \ + } else if (cfg->interfaces[i].ipv4.dhcpv4_server.__##x##_changed && \ + cfg->interfaces[i].ipv4.dhcpv4_server.x != \ + config->interfaces[i].ipv4.dhcpv4_server.x) { \ + net_init_config_user.interfaces[i].ipv4.dhcpv4_server.x = \ + cfg->interfaces[i].ipv4.dhcpv4_server.x; \ + net_init_config_user.interfaces[i].ipv4.dhcpv4_server. \ + __##x##_changed = true; \ + } + +#define SET_IPV4_DHCPV4_SERVER_STR_VAL(x) \ + if (net_init_config_user.interfaces[i].ipv4.dhcpv4_server. \ + __##x##_changed == true) { \ + if (strcmp(cfg->interfaces[i].ipv4.dhcpv4_server.x, \ + net_init_config_user.interfaces[i].ipv4.dhcpv4_server.x) != 0) { \ + strcpy(net_init_config_user.interfaces[i].ipv4.dhcpv4_server.x, \ + cfg->interfaces[i].ipv4.dhcpv4_server.x); \ + } \ + } else if (cfg->interfaces[i].ipv4.dhcpv4_server.__##x##_changed && \ + strcmp(cfg->interfaces[i].ipv4.dhcpv4_server.x, \ + config->interfaces[i].ipv4.dhcpv4_server.x) != 0) { \ + strcpy(net_init_config_user.interfaces[i].ipv4.dhcpv4_server.x, \ + cfg->interfaces[i].ipv4.dhcpv4_server.x); \ + net_init_config_user.interfaces[i].ipv4.dhcpv4_server. \ + __##x##_changed = true; \ + } + +#define SET_IFACE_VLAN_VAL(x) \ + if (net_init_config_user.interfaces[i].vlan.__##x##_changed == true) { \ + if (cfg->interfaces[i].vlan.x != net_init_config_user. \ + interfaces[i].vlan.x) { \ + net_init_config_user.interfaces[i].vlan.x = \ + cfg->interfaces[i].vlan.x; \ + } \ + } else if (cfg->interfaces[i].vlan.__##x##_changed && \ + cfg->interfaces[i].vlan.x != config->interfaces[i].vlan.x) { \ + net_init_config_user.interfaces[i].vlan.x = \ + cfg->interfaces[i].vlan.x; \ + net_init_config_user.interfaces[i].vlan.__##x##_changed = true; \ + } + +#define SET_IEEE802154_VAL(x) \ + if (net_init_config_user.ieee_802_15_4.__##x##_changed == true) { \ + if (cfg->ieee_802_15_4.x != net_init_config_user.ieee_802_15_4.x) { \ + net_init_config_user.ieee_802_15_4.x = cfg->ieee_802_15_4.x; \ + } \ + } else if (cfg->ieee_802_15_4.__##x##_changed && \ + cfg->ieee_802_15_4.x != config->ieee_802_15_4.x) { \ + net_init_config_user.ieee_802_15_4.x = cfg->ieee_802_15_4.x; \ + net_init_config_user.ieee_802_15_4.__##x##_changed = true; \ + } + +#define SET_IEEE802154_SECURITY_KEY_VAL(x) \ + if (net_init_config_user.ieee_802_15_4.security_key[0].__##x##_changed == true) { \ + if (cfg->ieee_802_15_4.security_key[0].x != \ + net_init_config_user.ieee_802_15_4.security_key[0].x) { \ + net_init_config_user.ieee_802_15_4.security_key[0].x = \ + cfg->ieee_802_15_4.security_key[0].x; \ + } \ + } else if (cfg->ieee_802_15_4.security_key[0].__##x##_changed && \ + cfg->ieee_802_15_4.security_key[0].x != \ + config->ieee_802_15_4.security_key[0].x) { \ + net_init_config_user.ieee_802_15_4.security_key[0].x = \ + cfg->ieee_802_15_4.security_key[0].x; \ + net_init_config_user.ieee_802_15_4.security_key[0].__##x##_changed = true; \ + } + +#define SET_SNTP_VAL(x) \ + if (net_init_config_user.sntp.__##x##_changed == true) { \ + if (cfg->sntp.x != net_init_config_user.sntp.x) { \ + net_init_config_user.sntp.x = cfg->sntp.x; \ + } \ + } else if (cfg->sntp.__##x##_changed && \ + cfg->sntp.x != config->sntp.x) { \ + net_init_config_user.sntp.x = cfg->sntp.x; \ + net_init_config_user.sntp.__##x##_changed = true; \ + } + +#define SET_SNTP_STR_VAL(x) \ + if (net_init_config_user.sntp.__##x##_changed == true) { \ + if (strcmp(cfg->sntp.x, net_init_config_user.sntp.x) != 0) { \ + strcpy(net_init_config_user.sntp.x, cfg->sntp.x); \ + } \ + } else if (cfg->sntp.__##x##_changed && \ + strcmp(cfg->sntp.x, config->sntp.x) != 0) { \ + strcpy(net_init_config_user.sntp.x, cfg->sntp.x); \ + net_init_config_user.sntp.__##x##_changed = true; \ + } + +int net_config_set(const struct networking *cfg) +{ +#if defined(CONFIG_SETTINGS) && defined(CONFIG_SETTINGS_RUNTIME) + const struct networking *config; + int ret; + + if (cfg == NULL) { + return -EINVAL; + } config = net_config_get_init_config(); if (config == NULL) { - NET_ERR("Network configuration not found."); + NET_ERR("Default network configuration not found."); return -ENOENT; } + /* Save the user modified config */ + ARRAY_FOR_EACH(config->interfaces, i) { + const struct net_cfg_interfaces *iface_cfg; + + iface_cfg = &config->interfaces[i]; + + SET_IFACE_VAL(bind_to); + SET_IFACE_STR_VAL(name); + SET_IFACE_VAL(device_name); + SET_IFACE_STR_VAL(set_name); + SET_IFACE_VAL(set_default); + + ARRAY_FOR_EACH(iface_cfg->flags, j) { + SET_FLAGS_VAL(value); + } + + /* IPv6 config */ + if (IS_ENABLED(CONFIG_NET_IPV6)) { + SET_IPV6_VAL(status); + SET_IPV6_VAL(hop_limit); + SET_IPV6_VAL(multicast_hop_limit); + + ARRAY_FOR_EACH(iface_cfg->ipv6.ipv6_addresses, j) { + SET_IPV6_ADDR_VAL(value); + } + + ARRAY_FOR_EACH(iface_cfg->ipv6.ipv6_multicast_addresses, j) { + SET_IPV6_MADDR_VAL(value); + } + + ARRAY_FOR_EACH(iface_cfg->ipv6.prefixes, j) { + SET_IPV6_PREFIX_STR_VAL(address); + SET_IPV6_PREFIX_VAL(len); + SET_IPV6_PREFIX_VAL(lifetime); + } + + if (IS_ENABLED(CONFIG_NET_DHCPV6)) { + SET_IPV6_DHCPV6_VAL(status); + SET_IPV6_DHCPV6_VAL(do_request_address); + SET_IPV6_DHCPV6_VAL(do_request_prefix); + } + } + + /* IPv4 config */ + if (IS_ENABLED(CONFIG_NET_IPV4)) { + SET_IPV4_VAL(status); + SET_IPV4_VAL(time_to_live); + SET_IPV4_VAL(multicast_time_to_live); + SET_IPV4_STR_VAL(gateway); + + if (IS_ENABLED(CONFIG_NET_DHCPV4)) { + SET_IPV4_DHCPV4_VAL(status); + } + + if (IS_ENABLED(CONFIG_NET_IPV4_AUTO)) { + SET_IPV4_AUTOCONF_VAL(status); + } + + ARRAY_FOR_EACH(iface_cfg->ipv4.ipv4_addresses, j) { + SET_IPV4_ADDR_VAL(value); + } + + ARRAY_FOR_EACH(iface_cfg->ipv4.ipv4_multicast_addresses, j) { + SET_IPV4_MADDR_VAL(value); + } + + if (IS_ENABLED(CONFIG_NET_DHCPV4_SERVER)) { + SET_IPV4_DHCPV4_SERVER_VAL(status); + SET_IPV4_DHCPV4_SERVER_STR_VAL(base_address); + } + } + + /* VLAN config */ + if (IS_ENABLED(CONFIG_NET_VLAN)) { + SET_IFACE_VLAN_VAL(status); + SET_IFACE_VLAN_VAL(tag); + } + } + + /* IEEE 802.15.4 config */ + if (IS_ENABLED(CONFIG_NET_L2_IEEE802154)) { + SET_IEEE802154_VAL(status); + SET_IEEE802154_VAL(bind_to); + SET_IEEE802154_VAL(pan_id); + SET_IEEE802154_VAL(channel); + SET_IEEE802154_VAL(tx_power); + SET_IEEE802154_VAL(security_key_mode); + SET_IEEE802154_VAL(security_level); + SET_IEEE802154_VAL(ack_required); + SET_IEEE802154_SECURITY_KEY_VAL(value); + } + + /* SNTP config */ + if (IS_ENABLED(CONFIG_NET_CONFIG_CLOCK_SNTP_INIT)) { + SET_SNTP_VAL(status); + SET_SNTP_VAL(bind_to); + SET_SNTP_STR_VAL(server); + SET_SNTP_VAL(timeout); + } + + ret = settings_runtime_set(SETTINGS_SUBTREE_NET_CONFIG "/user", + &net_init_config_user, sizeof(net_init_config_user)); + if (ret < 0) { + NET_ERR("Cannot save user network configuration (%d)", ret); + return ret; + } + + ret = settings_runtime_commit(SETTINGS_SUBTREE_NET_CONFIG); + if (ret < 0) { + NET_ERR("Cannot commit user network configuration (%d)", ret); + return ret; + } + + settings_save(); + + NET_DBG("Saved user network configuration"); + + return 0; +#else + return -ENOTSUP; +#endif +} + +int net_config_clear(void) +{ +#if defined(CONFIG_SETTINGS) && defined(CONFIG_SETTINGS_RUNTIME) + int ret; + + ret = settings_delete(SETTINGS_SUBTREE_NET_CONFIG "/user"); + if (ret < 0) { + NET_ERR("Cannot clear user network configuration (%d)", ret); + return ret; + } + + memset(&net_init_config_user, 0, sizeof(net_init_config_user)); + + settings_loaded = false; + + ret = settings_runtime_commit(SETTINGS_SUBTREE_NET_CONFIG); + if (ret < 0) { + NET_ERR("Cannot commit user network configuration (%d)", ret); + return ret; + } + + settings_save(); + + NET_DBG("Cleared user network configuration"); + + return 0; +#else + return -ENOTSUP; +#endif +} + +static int generated_net_config_init_app(const struct device *dev, + const char *app_info) +{ + struct networking *user_config; + const struct networking *config; + int ret, ifindex; + + /* Because the configuration struct might be large, we allocate it + * from heap so that we don't overflow the stack. One option could be + * to use a static variable, but the space for that would be reserved + * all the time even if not used after the initialization. + * With k_calloc() we allocate only when needed. + */ + if (IS_ENABLED(CONFIG_SETTINGS)) { + user_config = k_calloc(1, sizeof(*user_config)); + if (user_config == NULL) { + NET_ERR("Cannot allocate memory for user network configuration"); + return -ENOMEM; + } + + config = user_config; + + ret = net_config_get(user_config); + if (ret < 0) { + NET_ERR("Network configuration error (%d)", ret); + ret = -ENOENT; + goto out; + } + } else { + config = net_config_get_init_config(); + if (config == NULL) { + NET_ERR("Default network configuration not found."); + return -ENOENT; + } + } + ret = wait_for_interface(config->interfaces, sizeof(config->interfaces)); if (ret < 0) { NET_WARN("Timeout while waiting network interfaces (%d)", ret); - return ret; + goto out; } ARRAY_FOR_EACH(config->interfaces, i) { @@ -803,7 +1833,7 @@ static int generated_net_config_init_app(const struct device *dev, NET_DBG("Configuring interface %d (%p)", ifindex, iface); /* Do we need to change the interface name */ - if (cfg->set_name != NULL) { + if (cfg->set_name[0] != '\0') { ret = net_if_set_name(iface, cfg->set_name); if (ret < 0) { NET_DBG("Cannot rename interface %d to \"%s\" (%d)", @@ -822,8 +1852,7 @@ static int generated_net_config_init_app(const struct device *dev, } ARRAY_FOR_EACH(cfg->flags, j) { - if (cfg->flags[j].value == NULL || - cfg->flags[j].value[0] == '\0') { + if (cfg->flags[j].value[0] == '\0') { continue; } @@ -912,11 +1941,41 @@ static int generated_net_config_init_app(const struct device *dev, log_backend_net_start(); } - return 0; +out: + if (IS_ENABLED(CONFIG_SETTINGS)) { + k_free(user_config); + } + + return ret; } int net_config_init_app(const struct device *dev, const char *app_info) { +#if defined(CONFIG_SETTINGS) + int ret; + + ret = settings_subsys_init(); + if (ret != 0) { + NET_ERR("settings subsys initialization fail (%d)", ret); + return ret; + } + + settings_loaded = false; + + ret = settings_register(&net_config_settings_handler); + if (ret == 0) { + ret = settings_load_subtree(SETTINGS_SUBTREE_NET_CONFIG); + if (ret != 0) { + NET_ERR("Settings load failed: %d", ret); + } else { + NET_DBG("Settings %s loaded", SETTINGS_SUBTREE_NET_CONFIG); + settings_loaded = true; + } + } else { + NET_ERR("Settings register failed: %d", ret); + } +#endif /* CONFIG_SETTINGS */ + return generated_net_config_init_app(dev, app_info); } From 538669e84e2557cc6a322fbbd3ae00b08efc64dc Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Tue, 14 Oct 2025 10:09:33 +0300 Subject: [PATCH 12/16] net: config: Verify that the config format hash is not changed The hash is used to detect whether the yaml schema file is changed. If the data format in the settings is different that what is found in the ROM, then the settings db must be discarded because the data is now incompatible. Signed-off-by: Jukka Rissanen --- subsys/net/lib/config/init.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/subsys/net/lib/config/init.c b/subsys/net/lib/config/init.c index 47ce216439833..738a1ada1195b 100644 --- a/subsys/net/lib/config/init.c +++ b/subsys/net/lib/config/init.c @@ -1134,6 +1134,18 @@ int net_config_get(struct networking *cfg) settings_loaded = true; } + /* Verify that the yaml schema file is not changed between what is in + * use and what was used when the user modified the configuration. + */ + if (net_init_config_user.config_format_hash != NULL && + strncmp(config->config_format_hash, + net_init_config_user.config_format_hash, + sizeof(config->config_format_hash) - 1) != 0) { + NET_ERR("User network configuration format hash mismatch"); + settings_loaded = false; + return -EINVAL; + } + /* Make a union of the default config and the user modified one and * return results in cfg pointer. */ From b9ce64a7cb8d6bed115447f665d8c32c42ec5a85 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 16 Oct 2025 10:27:14 +0300 Subject: [PATCH 13/16] tests: net: config: Add settings db testing Make sure we run the tests with settings db enabled. Signed-off-by: Jukka Rissanen --- tests/net/lib/config/testcase.yaml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/net/lib/config/testcase.yaml b/tests/net/lib/config/testcase.yaml index ef9621c5fc036..62524b0636035 100644 --- a/tests/net/lib/config/testcase.yaml +++ b/tests/net/lib/config/testcase.yaml @@ -5,8 +5,15 @@ common: platform_exclude: - native_sim - native_sim/native/64 + tags: + - net + - iface tests: - net.config: - tags: - - net - - iface + net.config: {} + net.config.settings: + extra_configs: + # The yaml configuration contains multiple network interface + # so we need larger heap to store the information. + - CONFIG_HEAP_MEM_POOL_ADD_SIZE_NET_CONFIG=4096 + - CONFIG_SETTINGS=y + - CONFIG_SETTINGS_RUNTIME=y From f073ae89a307543694d02e4897eb12b1997fa273 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 2 Oct 2025 16:57:21 +0300 Subject: [PATCH 14/16] net: shell: config: Add a way to see / modify configuration Add "net config" command to view the current configuration and modify or remove the configuration data. Signed-off-by: Jukka Rissanen --- subsys/net/lib/config/Kconfig | 7 + subsys/net/lib/shell/CMakeLists.txt | 1 + subsys/net/lib/shell/Kconfig | 5 + subsys/net/lib/shell/config.c | 697 ++++++++++++++++++++++++++++ 4 files changed, 710 insertions(+) create mode 100644 subsys/net/lib/shell/config.c diff --git a/subsys/net/lib/config/Kconfig b/subsys/net/lib/config/Kconfig index c62d945ec44e1..9f29ba8bb5c67 100644 --- a/subsys/net/lib/config/Kconfig +++ b/subsys/net/lib/config/Kconfig @@ -30,6 +30,13 @@ config HEAP_MEM_POOL_ADD_SIZE_NET_CONFIG default 1024 depends on SETTINGS +config NET_CONFIG_SETTINGS_SHELL_ACCESS + bool "Configure options via shell" + select SETTINGS + help + If this is set, then user is able to use "net config" shell to + update the default options. This requires settings db. + config NET_CONFIG_AUTO_INIT bool "Init networking support automatically during device startup" default y if !(USB_DEVICE_NETWORK || USBD_CDC_ECM_CLASS || USBD_CDC_NCM_CLASS) diff --git a/subsys/net/lib/shell/CMakeLists.txt b/subsys/net/lib/shell/CMakeLists.txt index a027ff434ef4e..8af74a21cf6d9 100644 --- a/subsys/net/lib/shell/CMakeLists.txt +++ b/subsys/net/lib/shell/CMakeLists.txt @@ -9,6 +9,7 @@ zephyr_library_link_libraries_ifdef(CONFIG_MBEDTLS mbedTLS) zephyr_library_sources_ifdef(CONFIG_NET_SHELL_PKT_ALLOC_SUPPORTED allocs.c) zephyr_library_sources_ifdef(CONFIG_NET_SHELL_ETHERNET_SUPPORTED arp.c) zephyr_library_sources_ifdef(CONFIG_NET_SHELL_CAPTURE_SUPPORTED capture.c) +zephyr_library_sources_ifdef(CONFIG_NET_SHELL_CONFIG_SETTINGS_SUPPORTED config.c) zephyr_library_sources(conn.c) zephyr_library_sources_ifdef(CONFIG_NET_SHELL_DHCPV4_SUPPORTED dhcpv4.c) zephyr_library_sources_ifdef(CONFIG_NET_SHELL_DHCPV6_SUPPORTED dhcpv6.c) diff --git a/subsys/net/lib/shell/Kconfig b/subsys/net/lib/shell/Kconfig index c050afd1ff612..3fa4fd7bf72f6 100644 --- a/subsys/net/lib/shell/Kconfig +++ b/subsys/net/lib/shell/Kconfig @@ -35,6 +35,11 @@ config NET_SHELL_CAPTURE_SUPPORTED default y depends on NET_SHELL_SHOW_DISABLED_COMMANDS || NET_CAPTURE +config NET_SHELL_CONFIG_SETTINGS_SUPPORTED + bool "Network stack configuration" + default y + depends on NET_SHELL_SHOW_DISABLED_COMMANDS || NET_CONFIG_SETTINGS + config NET_SHELL_DHCPV4_SUPPORTED bool "DHCPv4 start / stop" default y diff --git a/subsys/net/lib/shell/config.c b/subsys/net/lib/shell/config.c new file mode 100644 index 0000000000000..9b386cae32221 --- /dev/null +++ b/subsys/net/lib/shell/config.c @@ -0,0 +1,697 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_DECLARE(net_shell); + +#include + +#include "net_shell_private.h" + +#include + +#if defined(CONFIG_NET_CONFIG_SETTINGS_SHELL_ACCESS) +static struct networking config; +#endif + +#define CHANGED(my, t, x) ((my)->__##x##_changed ? "+ " : ((t)->__##x##_changed ? "* " : "")) + +static int cmd_net_config(const struct shell *sh, size_t argc, char *argv[]) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + /* Print current configuration */ + +#if defined(CONFIG_NET_CONFIG_SETTINGS_SHELL_ACCESS) + static struct networking current_config; + struct networking *cfg; + int ret; + + cfg = ¤t_config; + + ret = net_config_get(cfg); + if (ret < 0) { + PR_ERROR("Failed to %s network configuration (%d)", "get", ret); + return ret; + } + + ARRAY_FOR_EACH(cfg->interfaces, i) { + struct net_cfg_interfaces *iface_cfg, *my_cfg; + + PR("Network interface: %d\n", i + 1); + + iface_cfg = &cfg->interfaces[i]; + my_cfg = &config.interfaces[i]; + + if (iface_cfg->bind_to > 0) { + PR("\t%sbind_to %d\n", CHANGED(my_cfg, iface_cfg, bind_to), + iface_cfg->bind_to); + } + + if (iface_cfg->name[0] != '\0') { + PR("\t%sname %s\n", CHANGED(my_cfg, iface_cfg, name), + iface_cfg->name); + } + + if (iface_cfg->device_name != NULL) { + PR("\t%sdevice_name %s\n", + CHANGED(my_cfg, iface_cfg, device_name), + iface_cfg->device_name); + } + + if (iface_cfg->set_name[0] != '\0') { + PR("\t%sset_name %s\n", + CHANGED(my_cfg, iface_cfg, set_name), + iface_cfg->set_name); + } + + PR("\t%sset_default %s\n", CHANGED(my_cfg, iface_cfg, set_default), + iface_cfg->set_default ? "yes" : "no"); + + if (iface_cfg->flags[0].value[0] != '\0') { + PR("\t%sflags %s\n", + CHANGED(&my_cfg->flags[0], &iface_cfg->flags[0], value), + iface_cfg->flags[0].value); + } + + if (IS_ENABLED(MY_CFG_NET_IPV6)) { + PR("\tIPv6 configuration\n"); + + if (!iface_cfg->ipv6.status) { + PR("\t\t%sipv6.status %s\n", + CHANGED(&my_cfg->ipv6, &iface_cfg->ipv6, status), + "disabled"); + goto skip_ipv6; + } + + PR("\t\t%sipv6.status %s\n", + CHANGED(&my_cfg->ipv6, &iface_cfg->ipv6, status), + "enabled"); + PR("\t\t%sipv6.hop_limit %d\n", + CHANGED(&my_cfg->ipv6, &iface_cfg->ipv6, hop_limit), + iface_cfg->ipv6.hop_limit); + PR("\t\t%sipv6.multicast_hop_limit %d\n", + CHANGED(&my_cfg->ipv6, &iface_cfg->ipv6, multicast_hop_limit), + iface_cfg->ipv6.multicast_hop_limit); + + ARRAY_FOR_EACH(iface_cfg->ipv6.ipv6_addresses, j) { + + if (iface_cfg->ipv6.ipv6_addresses[j].value[0] == '\0') { + continue; + } + + PR("\t\t%s-j %d ipv6.ipv6_addresses %s\n", + CHANGED(&my_cfg->ipv6.ipv6_addresses[j], + &iface_cfg->ipv6.ipv6_addresses[j], value), j, + iface_cfg->ipv6.ipv6_addresses[j].value); + } + + ARRAY_FOR_EACH(iface_cfg->ipv6.ipv6_multicast_addresses, j) { + + if (iface_cfg->ipv6.ipv6_multicast_addresses[j].value[0] == '\0') { + continue; + } + + PR("\t\t%s-j %d ipv6.ipv6_multicast_addresses %s\n", + CHANGED(&my_cfg->ipv6.ipv6_multicast_addresses[j], + &iface_cfg->ipv6.ipv6_multicast_addresses[j], + value), j, + iface_cfg->ipv6.ipv6_multicast_addresses[j].value); + } + + ARRAY_FOR_EACH(iface_cfg->ipv6.prefixes, j) { + if (iface_cfg->ipv6.prefixes[j].address[0] == '\0') { + continue; + } + + PR("\t\t%s-j %d ipv6.prefixes.address %s\n" + "\t\t%s-j %d ipv6.prefixes.len %d\n" + "\t\t%s-j %d ipv6.prefixes.lifetime %d\n", + CHANGED(&my_cfg->ipv6.prefixes[j], + &iface_cfg->ipv6.prefixes[j], address), j, + iface_cfg->ipv6.prefixes[j].address, + CHANGED(&my_cfg->ipv6.prefixes[j], + &iface_cfg->ipv6.prefixes[j], len), j, + iface_cfg->ipv6.prefixes[j].len, + CHANGED(&my_cfg->ipv6.prefixes[j], + &iface_cfg->ipv6.prefixes[j], lifetime), j, + iface_cfg->ipv6.prefixes[j].lifetime); + } + + if (IS_ENABLED(CONFIG_NET_DHCPV6)) { + PR("\t\t%sipv6.dhcpv6 %s\n", + CHANGED(&my_cfg->ipv6.dhcpv6, &iface_cfg->ipv6.dhcpv6, status), + iface_cfg->ipv6.dhcpv6.status ? "enabled" : "disabled"); + + if (iface_cfg->ipv6.dhcpv6.status) { + PR("\t\t\t%sipv6.do_request_address %s\n", + CHANGED(&my_cfg->ipv6.dhcpv6, &iface_cfg->ipv6.dhcpv6, + do_request_address), + iface_cfg->ipv6.dhcpv6.do_request_address ? + "yes" : "no"); + PR("\t\t\t%sipv6.do_request_prefix %s\n", + CHANGED(&my_cfg->ipv6.dhcpv6, &iface_cfg->ipv6.dhcpv6, + do_request_prefix), + iface_cfg->ipv6.dhcpv6.do_request_prefix ? + "yes" : "no"); + } + } + } + +skip_ipv6: + if (IS_ENABLED(CONFIG_NET_IPV4) && iface_cfg->ipv4.status) { + PR("\tIPv4 configuration\n"); + + if (!iface_cfg->ipv4.status) { + PR("\t\t%sipv4.status %s\n", + CHANGED(&my_cfg->ipv4, &iface_cfg->ipv4, status), + "disabled"); + goto skip_ipv4; + } + + PR("\t\t%sipv4.status %s\n", + CHANGED(&my_cfg->ipv4, &iface_cfg->ipv4, status), + "enabled"); + PR("\t\t%sipv4.time_to_live %d\n", + CHANGED(&my_cfg->ipv4, &iface_cfg->ipv4, time_to_live), + iface_cfg->ipv4.time_to_live); + PR("\t\t%sipv4.multicast_time_to_live %d\n", + CHANGED(&my_cfg->ipv4, &iface_cfg->ipv4, multicast_time_to_live), + iface_cfg->ipv4.multicast_time_to_live); + + if (iface_cfg->ipv4.gateway[0] != '\0') { + PR("\t\t%sipv4.gateway %s\n", + CHANGED(&my_cfg->ipv4, &iface_cfg->ipv4, gateway), + iface_cfg->ipv4.gateway); + } + + if (IS_ENABLED(CONFIG_NET_DHCPV4_SERVER)) { + PR("\t\t%sipv4.dhcpv4_server.status %s\n", + CHANGED(&my_cfg->ipv4.dhcpv4_server, + &iface_cfg->ipv4.dhcpv4_server, status), + iface_cfg->ipv4.dhcpv4_server.status ? "enabled" : "disabled"); + PR("\t\t%sipv4.dhcpv4_server.base_address %s\n", + CHANGED(&my_cfg->ipv4.dhcpv4_server, + &iface_cfg->ipv4.dhcpv4_server, + base_address), + iface_cfg->ipv4.dhcpv4_server.base_address); + } + + if (IS_ENABLED(CONFIG_NET_DHCPV4)) { + PR("\t\t%sipv4.dhcpv4.status %s\n", + CHANGED(&my_cfg->ipv4.dhcpv4, + &iface_cfg->ipv4.dhcpv4, status), + iface_cfg->ipv4.dhcpv4.status ? "enabled" : "disabled"); + } + + if (IS_ENABLED(CONFIG_NET_IPV4_AUTO)) { + PR("\t\t%sipv4.ipv4_autoconf.status %s\n", + CHANGED(&my_cfg->ipv4.ipv4_autoconf, + &iface_cfg->ipv4.ipv4_autoconf, + status), + iface_cfg->ipv4.ipv4_autoconf.status ? "enabled" : "disabled"); + } + + ARRAY_FOR_EACH(iface_cfg->ipv4.ipv4_addresses, j) { + + if (iface_cfg->ipv4.ipv4_addresses[j].value[0] == '\0') { + continue; + } + + PR("\t\t%s-j %d ipv4.ipv4_addresses %s\n", + CHANGED(&my_cfg->ipv4.ipv4_addresses[j], + &iface_cfg->ipv4.ipv4_addresses[j], value), j, + iface_cfg->ipv4.ipv4_addresses[j].value); + } + + ARRAY_FOR_EACH(iface_cfg->ipv4.ipv4_multicast_addresses, j) { + + if (iface_cfg->ipv4.ipv4_multicast_addresses[j].value[0] == '\0') { + continue; + } + + PR("\t\t%s-j %d ipv4.ipv4_multicast_addresses %s\n", + CHANGED(&my_cfg->ipv4.ipv4_multicast_addresses[j], + &iface_cfg->ipv4.ipv4_multicast_addresses[j], + value), j, + iface_cfg->ipv4.ipv4_multicast_addresses[j].value); + } + } + +skip_ipv4: + if (IS_ENABLED(CONFIG_NET_VLAN)) { + PR("\tVLAN configuration\n"); + + if (!iface_cfg->vlan.status) { + PR("\t\t%sstatus %s\n", + CHANGED(&my_cfg->vlan, &iface_cfg->vlan, status), + "disabled"); + goto skip_vlan; + } + + PR("\t\t%sstatus %s\n", + CHANGED(&my_cfg->vlan, &iface_cfg->vlan, status), + "enabled"); + PR("\t\t%stag %d\n", + CHANGED(&my_cfg->vlan, &iface_cfg->vlan, tag), + iface_cfg->vlan.tag); + } +skip_vlan: + } + + if (IS_ENABLED(CONFIG_NET_L2_IEEE802154)) { + PR("IEEE 802.15.4 configuration\n"); + + if (!cfg->ieee_802_15_4.status) { + PR("\t%sstatus %s\n", + CHANGED(&config.ieee_802_15_4, &cfg->ieee_802_15_4, status), + "disabled"); + goto skip_ieee802154; + } + + PR("\t%sstatus %s\n", + CHANGED(&config.ieee_802_15_4, &cfg->ieee_802_15_4, status), + "enabled"); + + if (cfg->ieee_802_15_4.bind_to > 0) { + PR("\t%sbind_to %d\n", + CHANGED(&config.ieee_802_15_4, &cfg->ieee_802_15_4, bind_to), + cfg->ieee_802_15_4.bind_to); + } + + PR("\t%span_id 0x%04X\n", + CHANGED(&config.ieee_802_15_4, &cfg->ieee_802_15_4, pan_id), + cfg->ieee_802_15_4.pan_id); + PR("\t%schannel %d\n", + CHANGED(&config.ieee_802_15_4, &cfg->ieee_802_15_4, channel), + cfg->ieee_802_15_4.channel); + PR("\t%stx_power %d dBm\n", + CHANGED(&config.ieee_802_15_4, &cfg->ieee_802_15_4, tx_power), + cfg->ieee_802_15_4.tx_power); + PR("\t%sack_required %s\n", + CHANGED(&config.ieee_802_15_4, &cfg->ieee_802_15_4, ack_required), + cfg->ieee_802_15_4.ack_required ? "yes" : "no"); + PR("\t%ssecurity_key_mode %d\n", + CHANGED(&config.ieee_802_15_4, &cfg->ieee_802_15_4, security_key_mode), + cfg->ieee_802_15_4.security_key_mode); + PR("\t%ssecurity_level %d\n", + CHANGED(&config.ieee_802_15_4, &cfg->ieee_802_15_4, security_level), + cfg->ieee_802_15_4.security_level); + PR("\t%ssecurity_key %d\n", + CHANGED(&config.ieee_802_15_4.security_key[0], + &cfg->ieee_802_15_4.security_key[0], value), + cfg->ieee_802_15_4.security_key[0].value); + } + +skip_ieee802154: + + if (IS_ENABLED(CONFIG_NET_CONFIG_CLOCK_SNTP_INIT)) { + PR("SNTP configuration\n"); + + if (!cfg->sntp.status) { + PR("\t%sstatus %s\n", + CHANGED(&config.sntp, &cfg->sntp, status), + "disabled"); + goto skip_sntp; + } + + PR("\t%sstatus %s\n", + CHANGED(&config.sntp, &cfg->sntp, status), + "enabled"); + + if (cfg->sntp.bind_to > 0) { + PR("\t%sbind_to %d\n", + CHANGED(&config.sntp, &cfg->sntp, bind_to), + cfg->sntp.bind_to); + } + + if (cfg->sntp.server[0] != '\0') { + PR("\t%sserver %s\n", + CHANGED(&config.sntp, &cfg->sntp, server), + cfg->sntp.server); + + PR("\t%stimeout %d ms\n", + CHANGED(&config.sntp, &cfg->sntp, timeout), + cfg->sntp.timeout); + } + } + +skip_sntp: +#else + PR_INFO("Set %s to enable %s support.\n", + "CONFIG_NET_CONFIG_SETTINGS_SHELL_ACCESS", + "network stack settings configuration"); +#endif + return 0; +} + +static int cmd_net_config_remove(const struct shell *sh, size_t argc, char *argv[]) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + +#if defined(CONFIG_NET_CONFIG_SETTINGS_SHELL_ACCESS) + int ret; + + memset(&config, 0, sizeof(config)); + + ret = net_config_clear(); + if (ret < 0) { + PR_ERROR("Failed to %s network configuration (%d)", "remove", ret); + return ret; + } + + PR("User configured network settings removed.\n"); +#else + PR_INFO("Set %s to enable %s support.\n", + "CONFIG_NET_CONFIG_SETTINGS_SHELL_ACCESS", + "network stack settings configuration"); +#endif + + return 0; +} + +#define CHECK_BASE_OPTION(opt, val, cfg) \ + if (strcmp(option, #opt) == 0) { \ + (cfg)->opt = val; \ + (cfg)->__##opt##_changed = true; \ + goto option_found; \ + } + +#define CHECK_BASE_STR_OPTION(opt, val, cfg) \ + if (strcmp(option, #opt) == 0) { \ + strcpy((cfg)->opt, val); \ + (cfg)->__##opt##_changed = true; \ + goto option_found; \ + } + +#define CHECK_BASE_INT_OPTION(opt, val, cfg) \ + if (strcmp(option, #opt) == 0) { \ + err = 0; \ + (cfg)->opt = shell_strtol(val, 10, &err); \ + if (err != 0) { \ + PR_WARNING("Parse error: %s\n", val); \ + return -ENOEXEC; \ + } \ + \ + (cfg)->__##opt##_changed = true; \ + goto option_found; \ + } + +#define CHECK_BASE_BOOL_OPTION(opt, val, cfg) \ + if (strcmp(option, #opt) == 0) { \ + if (strcmp(val, "yes") == 0 || \ + strcmp(val, "enabled") == 0 || \ + strcmp(val, "1") == 0 || \ + strcmp(val, "true") == 0) { \ + (cfg)->opt = true; \ + } else if (strcmp(val, "no") == 0 || \ + strcmp(val, "disabled") == 0 || \ + strcmp(val, "0") == 0 || \ + strcmp(val, "false") == 0) { \ + (cfg)->opt = false; \ + } else { \ + PR_WARNING("Invalid boolean value: %s\n", \ + val); \ + return -ENOEXEC; \ + } \ + \ + (cfg)->__##opt##_changed = true; \ + goto option_found; \ + } + +#define CHECK_SUB_OPTION(opt, name, val, cfg) \ + if (strcmp(option, #opt "." #name) == 0) { \ + (cfg)->opt.name = val; \ + (cfg)->opt.__##name##_changed = true; \ + goto option_found; \ + } + +#define CHECK_SUB_STR_OPTION(opt, name, val, cfg) \ + if (strcmp(option, #opt "." #name) == 0) { \ + strcpy((cfg)->opt.name, val); \ + (cfg)->opt.__##name##_changed = true; \ + goto option_found; \ + } + +#define CHECK_SUB_BOOL_OPTION(opt, name, val, cfg) \ + if (strcmp(option, #opt "." #name) == 0) { \ + if (strcmp(val, "yes") == 0 || \ + strcmp(val, "enabled") == 0 || \ + strcmp(val, "1") == 0 || \ + strcmp(val, "true") == 0) { \ + (cfg)->opt . name = true; \ + } else if (strcmp(val, "no") == 0 || \ + strcmp(val, "disabled") == 0 || \ + strcmp(val, "0") == 0 || \ + strcmp(val, "false") == 0) { \ + (cfg)->opt . name = false; \ + } else { \ + PR_WARNING("Invalid boolean value: %s\n", \ + val); \ + return -ENOEXEC; \ + } \ + \ + (cfg)->opt.__##name##_changed = true; \ + goto option_found; \ + } + +#define CHECK_SUB_INT_OPTION(opt, name, val, cfg) \ + if (strcmp(option, #opt "." #name) == 0) { \ + err = 0; \ + (cfg)->opt . name = shell_strtol(val, 10, &err);\ + if (err != 0) { \ + PR_WARNING("Parse error: %s\n", val); \ + return -ENOEXEC; \ + } \ + \ + (cfg)->opt.__##name##_changed = true; \ + goto option_found; \ + } + +#define CHECK_SUB_OPTION_ARRAY(opt, name, var, val, cfg) \ + if (strcmp(option, #opt "." #name) == 0) { \ + if (array_idx < 0 || array_idx >= ARRAY_SIZE((cfg)->opt.name)) { \ + PR_WARNING("Invalid array index: %d, should be >= 0 && < %d\n", \ + array_idx, ARRAY_SIZE((cfg)->opt.name)); \ + } else { \ + strcpy((cfg)->opt.name[array_idx].var, val); \ + (cfg)->opt.name[array_idx].__##var##_changed = true; \ + goto option_found; \ + } \ + } + +static int cmd_net_config_set(const struct shell *sh, size_t argc, char *argv[]) +{ +#if defined(CONFIG_NET_CONFIG_SETTINGS_SHELL_ACCESS) + struct net_cfg_interfaces *iface_cfg; + char *option = NULL, *value = NULL; + int iface_idx = -1, array_idx = -1; + int err; + + for (size_t i = 1; i < argc; ++i) { + + if (*argv[i] != '-') { + /* First non-option argument is the option to set, + * second is the value. Skip others. + */ + if (option == NULL) { + option = argv[i]; + } else if (value == NULL) { + value = argv[i]; + } + + continue; + } + + switch (argv[i][1]) { + case 'i': + err = 0; + + iface_idx = shell_strtol(argv[++i], 10, &err); + if (err != 0) { + PR_WARNING("Parse error: %s\n", argv[i]); + return -ENOEXEC; + } + + if (iface_idx > NET_CONFIG_NETWORK_INTERFACE_COUNT || + iface_idx <= 0) { + PR_WARNING("Invalid interface index: %d, " + "should be > 0 && <= %d\n", + iface_idx, + NET_CONFIG_NETWORK_INTERFACE_COUNT); + return -ENOEXEC; + } + + break; + + case 'j': + err = 0; + + array_idx = shell_strtol(argv[++i], 10, &err); + if (err != 0) { + PR_WARNING("Parse error: %s\n", argv[i]); + return -ENOEXEC; + } + + if (array_idx < 0) { + PR_WARNING("Invalid array index: %d, should be >= 0\n", + array_idx); + return -ENOEXEC; + } + + break; + + default: + PR_WARNING("Unrecognized argument: %s\n", argv[i]); + return -ENOEXEC; + } + } + + if (option == NULL || value == NULL) { + PR_WARNING("Option name and value must be specified.\n"); + return -ENOEXEC; + } + + if (iface_idx < 0) { + /* If user has not set the iface index, we assume 1 (the first interface). + */ + PR("Interface index not set, assuming interface 1.\n"); + iface_idx = 1; + } + + iface_cfg = &config.interfaces[iface_idx - 1]; + + CHECK_BASE_STR_OPTION(name, value, iface_cfg); + CHECK_BASE_OPTION(device_name, value, iface_cfg); + CHECK_BASE_STR_OPTION(set_name, value, iface_cfg); + CHECK_BASE_INT_OPTION(bind_to, value, iface_cfg); + CHECK_BASE_BOOL_OPTION(set_default, value, iface_cfg); + + if (strcmp(option, "flags") == 0) { + strcpy(iface_cfg->flags[0].value, value); + iface_cfg->flags[0].__value_changed = true; + goto option_found; + } + + CHECK_SUB_BOOL_OPTION(ipv6, status, value, iface_cfg); + CHECK_SUB_INT_OPTION(ipv6, hop_limit, value, iface_cfg); + CHECK_SUB_INT_OPTION(ipv6, multicast_hop_limit, value, iface_cfg); + CHECK_SUB_BOOL_OPTION(ipv6.dhcpv6, status, value, iface_cfg); + CHECK_SUB_BOOL_OPTION(ipv6.dhcpv6, do_request_address, value, iface_cfg); + CHECK_SUB_BOOL_OPTION(ipv6.dhcpv6, do_request_prefix, value, iface_cfg); + + CHECK_SUB_OPTION_ARRAY(ipv6, ipv6_addresses, value, value, iface_cfg); + CHECK_SUB_OPTION_ARRAY(ipv6, ipv6_multicast_addresses, value, value, iface_cfg); + + CHECK_SUB_BOOL_OPTION(ipv4, status, value, iface_cfg); + CHECK_SUB_INT_OPTION(ipv4, time_to_live, value, iface_cfg); + CHECK_SUB_INT_OPTION(ipv4, multicast_time_to_live, value, iface_cfg); + CHECK_SUB_STR_OPTION(ipv4, gateway, value, iface_cfg); + CHECK_SUB_BOOL_OPTION(ipv4.dhcpv4, status, value, iface_cfg); + CHECK_SUB_BOOL_OPTION(ipv4.ipv4_autoconf, status, value, iface_cfg); + CHECK_SUB_BOOL_OPTION(ipv4.dhcpv4_server, status, value, iface_cfg); + CHECK_SUB_STR_OPTION(ipv4.dhcpv4_server, base_address, value, iface_cfg); + + CHECK_SUB_OPTION_ARRAY(ipv4, ipv4_addresses, value, value, iface_cfg); + CHECK_SUB_OPTION_ARRAY(ipv4, ipv4_multicast_addresses, value, value, iface_cfg); + + CHECK_SUB_BOOL_OPTION(vlan, status, value, iface_cfg); + CHECK_SUB_INT_OPTION(vlan, tag, value, iface_cfg); + + CHECK_SUB_BOOL_OPTION(ieee_802_15_4, status, value, &config); + CHECK_SUB_INT_OPTION(ieee_802_15_4, bind_to, value, &config); + CHECK_SUB_INT_OPTION(ieee_802_15_4, pan_id, value, &config); + CHECK_SUB_INT_OPTION(ieee_802_15_4, channel, value, &config); + CHECK_SUB_INT_OPTION(ieee_802_15_4, tx_power, value, &config); + CHECK_SUB_BOOL_OPTION(ieee_802_15_4, ack_required, value, &config); + CHECK_SUB_INT_OPTION(ieee_802_15_4, security_key_mode, value, &config); + CHECK_SUB_INT_OPTION(ieee_802_15_4, security_level, value, &config); + + if (strcmp(option, "ieee_802_15_4.security_key") == 0) { + int val; + + err = 0; + val = shell_strtol(value, 10, &err); + if (err != 0) { + PR_WARNING("Parse error: %s\n", value); + return -ENOEXEC; + } + + config.ieee_802_15_4.security_key[0].value = val; + config.ieee_802_15_4.security_key[0].__value_changed = true; + goto option_found; + } + + CHECK_SUB_BOOL_OPTION(sntp, status, value, &config); + CHECK_SUB_INT_OPTION(sntp, bind_to, value, &config); + CHECK_SUB_STR_OPTION(sntp, server, value, &config); + CHECK_SUB_INT_OPTION(sntp, timeout, value, &config); + + PR_WARNING("Unrecognized option: %s\n", option); + return -ENOEXEC; + +option_found: + + PR("User configured network setting set.\n"); + PR("Do 'net config commit' to save the changes to permanent storage.\n"); +#else + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + PR_INFO("Set %s to enable %s support.\n", + "CONFIG_NET_CONFIG_SETTINGS_SHELL_ACCESS", + "network stack settings configuration"); +#endif + + return 0; +} + +static int cmd_net_config_commit(const struct shell *sh, size_t argc, char *argv[]) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + +#if defined(CONFIG_NET_CONFIG_SETTINGS_SHELL_ACCESS) + int ret; + + ret = net_config_set(&config); + if (ret < 0) { + PR_ERROR("Failed to %s network configuration (%d)", "commit", ret); + return ret; + } + + PR("User configured network settings saved.\n"); + + memset(&config, 0, sizeof(config)); +#else + PR_INFO("Set %s to enable %s support.\n", + "CONFIG_NET_CONFIG_SETTINGS_SHELL_ACCESS", + "network stack settings configuration"); +#endif + + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_config, + SHELL_CMD(remove, NULL, "Remove user configured network settings.", + cmd_net_config_remove), + SHELL_CMD(set, NULL, + "'net config set [-i network interface in configuration] [-j array index] " + "