diff --git a/cmake/modules/configuration_files.cmake b/cmake/modules/configuration_files.cmake index ff7a172445c38..8c55beea88840 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. @@ -99,5 +100,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/extensions.cmake b/cmake/modules/extensions.cmake index d5cd84256d2e6..b09cbc02a402c 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 @@ -717,6 +718,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/net_init_config.inc) + set(yaml_to_config_script ${ZEPHYR_BASE}/scripts/net/net-yaml-config.py) + set(yaml_schema_file ${ZEPHYR_BASE}/scripts/schemas/net-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. diff --git a/cmake/modules/kernel.cmake b/cmake/modules/kernel.cmake index 6a1a48b172d06..13b97328d3ddd 100644 --- a/cmake/modules/kernel.cmake +++ b/cmake/modules/kernel.cmake @@ -250,3 +250,17 @@ 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(NOT NET_INIT_CONFIG_FILE) + set(NET_INIT_CONFIG_FILE ${APPLICATION_SOURCE_DIR}/net-init-config.yaml) + endif() + if(EXISTS ${APPLICATION_SOURCE_DIR}/net-init-config.yaml) + network_generate_config_file_for_target(app ${NET_INIT_CONFIG_FILE}) + else() + network_generate_config_file_for_target(app ${DOTCONFIG}) + endif() +endif() diff --git a/doc/connectivity/networking/index.rst b/doc/connectivity/networking/index.rst index 2a34c1330eca8..66b17739d741a 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 + net_init_config.rst networking_with_host.rst network_monitoring.rst network_tracing.rst diff --git a/doc/connectivity/networking/net_init_config.rst b/doc/connectivity/networking/net_init_config.rst new file mode 100644 index 0000000000000..1808a937c8741 --- /dev/null +++ b/doc/connectivity/networking/net_init_config.rst @@ -0,0 +1,310 @@ +.. _network_initial_configuration: + +Network Stack Initial 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 ``net-init-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 ``net-init-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/net-configuration-schema.yaml`` file. + +User can use a different configuration yaml file by setting ``NET_INIT_CONFIG_FILE`` +when calling cmake or west. + +.. code-block:: console + + west build -p -b native_sim myapp -d build -- -DNET_INIT_CONFIG_FILE=my-net-init-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 + + net_init_config: + network_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_enabled: true + ipv4_autoconf_enabled: true + + - &vlan-interface + set_name: vlan0 + bind_to: *main-interface + flags: + - ^NET_IF_PROMISC + vlan: + status: true + tag: 2432 + ipv4: + status: true + dhcpv4_enabled: 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 ``network_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_enabled | bool | Is IPv4 auto-conf enabled to this interface. | + +--------------------------+-------------+----------------------------------------------------+ + | dhcpv4_enabled | bool | Is DHCPv4 client enabled for this interface. | + +--------------------------+-------------+----------------------------------------------------+ + | dhcpv4_server | struct | DHCPv4 server options. | + +--------------------------+-------------+----------------------------------------------------+ + +.. 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. | + +-------------------+-----------+-------------------------------------------------------------+ 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/include/zephyr/net/net_ip.h b/include/zephyr/net/net_ip.h index 0963bdd69bc31..23e9d249a16e9 100644 --- a/include/zephyr/net/net_ip.h +++ b/include/zephyr/net/net_ip.h @@ -1667,6 +1667,30 @@ __syscall char *net_addr_ntop(sa_family_t family, const void *src, bool net_ipaddr_parse(const char *str, size_t str_len, struct sockaddr *addr); +/** + * @brief Parse a string that contains either IPv4 or IPv6 address + * and optional IPv4 netmask or IPv6 prefix length, and store the address + * information in user supplied sockaddr struct. + * + * @details Syntax of the IP address string: + * 192.0.2.1/24 + * 192.0.2.42 + * 2001:db8::1/64 + * 2001:db::42 + * Note that the str_len parameter is used to restrict the amount of + * characters that are checked. If the string does not contain mask length + * value, then the mask_len parameter is not modified. + * + * @param str String that contains the IP address. + * @param str_len Length of the string to be parsed. + * @param addr Pointer to user supplied struct sockaddr. + * @param mask_len IPv4 netmask length or IPv6 prefix length. + * + * @return True if parsing could be done, false otherwise. + */ +bool net_ipaddr_mask_parse(const char *str, size_t str_len, + struct sockaddr *addr, uint8_t *mask_len); + /** * @brief Set the default port in the sockaddr structure. * If the port is already set, then do nothing. diff --git a/scripts/net/net-yaml-config.py b/scripts/net/net-yaml-config.py new file mode 100755 index 0000000000000..0c24c71d40985 --- /dev/null +++ b/scripts/net/net-yaml-config.py @@ -0,0 +1,336 @@ +#!/usr/bin/env python3 +# Copyright(c) 2024 Nordic Semiconductor +# SPDX-License-Identifier: Apache-2.0 + +import yaml, sys, os, re + +data = {} +schema = {} + +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 with two places 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, "")) + continue + 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(l[0])) for l 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} + """ + 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 = """ +net_init_config: + network_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_enabled: !ENV tag:yaml.org,2002:bool ${CONFIG_NET_DHCPV4:false} + + """ + 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 pykwalify is installed, then validate the yaml + try: + import pykwalify.core + + def yaml_validate(data, schema): + if not schema: + return + c = pykwalify.core.Core(source_data=data, schema_files=[schema]) + c.validate(raise_exception=True) + + except ImportError as e: + sys.stderr.write("can't import pykwalify; won't validate YAML (%s)", e) + def yaml_validate(data, schema): + pass + + yaml_validate(data, sys.argv[1]) + +with open(sys.argv[1], 'r') 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['net_init_config']['network_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" + "\"" + 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 walk_dict_schema(level, top_level_name, cdict, key_upper, map, indent): + for key, value in map.items(): + if key == "type": + if value == "str": + print(indent + "const char *" + key_upper + ";") + elif value == "bool": + print(indent + "bool " + key_upper + ";") + elif value == "int": + print(indent + "int " + key_upper + ";") + elif value == "seq": + print(indent + "struct " + top_level_name + "_" + key_upper + " {") + elif value == "map": + if key_upper != "value": + if level == 1: + print(indent + "struct " + key_upper + " {") + else: + print(indent + "struct " + top_level_name + "_" + key_upper + " {") + elif value == "any" and key_upper == "bind_to": + print(indent + "int bind_to;") + continue + elif key == "mapping": + walk_dict_schema(level + 1, top_level_name, cdict, key, value, "\t" + indent) + 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: + print(indent + "};") + else: + print(indent + "} " + key_upper + ";") + continue + elif key == "sequence": + walk_list_schema(level + 1, top_level_name, cdict, "value", value, "\t" + indent) + if key_upper in combined_data: + print(indent + "} " + key_upper + "[" + str(combined_data[key_upper]) + "];") + else: + print(indent + "} " + key_upper + "[1];") + continue + + if isinstance(value, dict): + walk_dict_schema(level + 1, top_level_name, cdict, key, value, indent) + +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 isinstance(value, dict): + walk_dict_schema(0, list(data.keys())[0], 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() + "_ENABLE_DATA)") + print("#define " + key.upper() + "_DATA " + key + "_data") + print("static const struct " + key + " " + key + "_data = {") + walk_dict(value, "\t") + print("};") + print("#endif /* " + key.upper() + "_ENABLE_DATA */") diff --git a/scripts/schemas/net-configuration-schema.yml b/scripts/schemas/net-configuration-schema.yml new file mode 100644 index 0000000000000..e4386171dcf35 --- /dev/null +++ b/scripts/schemas/net-configuration-schema.yml @@ -0,0 +1,152 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# A pykwalify schema for basic validation of the network configuration yaml file. + +type: map +mapping: + "net_init_config": + type: map + mapping: + "network_interfaces": + type: seq + sequence: + - type: map + required: true + mapping: + "bind_to": + type: any + "name": + type: str + "device_name": + type: str + "set_name": + type: str + "set_default": + type: bool + "flags": + type: seq + sequence: + - type: str + required: true + "ipv6": + type: map + mapping: + "status": + type: bool + "ipv6_addresses": + type: seq + sequence: + - type: str + required: true + "ipv6_multicast_addresses": + type: seq + sequence: + - type: str + required: true + "prefixes": + type: seq + sequence: + - type: map + required: true + mapping: + "address": + type: str + required: true + "len": + type: int + required: true + "lifetime": + type: int + required: true + "hop_limit": + type: int + "multicast_hop_limit": + type: int + "dhcpv6": + type: map + mapping: + "status": + type: bool + "do_request_address": + type: bool + "do_request_prefix": + type: bool + "ipv4": + type: map + mapping: + "status": + type: bool + "ipv4_addresses": + type: seq + sequence: + - type: str + required: true + "ipv4_multicast_addresses": + type: seq + sequence: + - type: str + required: true + "gateway": + type: str + "time_to_live": + type: int + "multicast_time_to_live": + type: int + "dhcpv4_enabled": + type: bool + "dhcpv4_server": + type: map + mapping: + "status": + type: bool + "base_address": + type: str + "ipv4_autoconf_enabled": + type: bool + "vlan": + type: map + mapping: + "status": + type: bool + "tag": + type: int + required: true + "ieee_802_15_4": + type: map + mapping: + "status": + type: bool + "bind_to": + type: any + "pan_id": + type: int + required: true + "channel": + type: int + required: true + "tx_power": + type: int + "security_key": + type: seq + sequence: + - type: int + "security_key_mode": + type: int + "security_level": + type: int + "ack_required": + type: bool + "sntp": + type: map + mapping: + "status": + type: bool + "server": + type: str + required: true + "timeout": + type: int + "bind_to": + type: any diff --git a/subsys/net/ip/utils.c b/subsys/net/ip/utils.c index f130a184bcb27..15f1459fae1fb 100644 --- a/subsys/net/ip/utils.c +++ b/subsys/net/ip/utils.c @@ -706,7 +706,6 @@ uint16_t net_calc_chksum_igmp(struct net_pkt *pkt) } #endif /* CONFIG_NET_IPV4_IGMP */ -#if defined(CONFIG_NET_IP) static bool convert_port(const char *buf, uint16_t *port) { unsigned long tmp; @@ -723,7 +722,6 @@ static bool convert_port(const char *buf, uint16_t *port) return true; } -#endif /* CONFIG_NET_IP */ #if defined(CONFIG_NET_IPV6) static bool parse_ipv6(const char *str, size_t str_len, @@ -932,6 +930,51 @@ bool net_ipaddr_parse(const char *str, size_t str_len, struct sockaddr *addr) return false; } +bool net_ipaddr_mask_parse(const char *str, size_t str_len, + struct sockaddr *addr, uint8_t *mask_len) +{ + const char *mask_ptr = NULL; + int i, count; + + if (!str || str_len == 0) { + return false; + } + + /* We cannot accept empty string here */ + if (*str == '\0') { + return false; + } + + for (count = i = 0; i < str_len && str[i]; i++) { + if (str[i] == ':') { + count++; + } + + /* IPv5 netmask len or IPv6 prefix len can follow the address */ + if (str[i] == '/') { + mask_ptr = &str[i] + 1; + str_len = &str[i] - &str[0]; + break; + } + } + + if (mask_ptr != NULL) { + uint16_t val; + int ret; + + ret = convert_port(mask_ptr, &val); + if (ret) { + *mask_len = (uint8_t)val; + } + } + + if (count == 0) { + return parse_ipv4(str, str_len, addr, false); + } + + return parse_ipv6(str, str_len, addr, false); +} + int net_port_set_default(struct sockaddr *addr, uint16_t default_port) { if (IS_ENABLED(CONFIG_NET_IPV4) && addr->sa_family == AF_INET && 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/configuration-example.yaml b/subsys/net/lib/config/configuration-example.yaml new file mode 100644 index 0000000000000..2b05edc86801a --- /dev/null +++ b/subsys/net/lib/config/configuration-example.yaml @@ -0,0 +1,117 @@ +# This is an example for defining network configuration data that +# can be applied automatically when the device boots. +# +net_init_config: + network_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_enabled: true + ipv4_autoconf_enabled: 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_enabled: 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_enabled: 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 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 76d1fd7b191c7..dfd78e498194c 100644 --- a/subsys/net/lib/config/init.c +++ b/subsys/net/lib/config/init.c @@ -22,468 +22,624 @@ 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 "net_init_config.inc" -extern int net_init_clock_via_sntp(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, + uint32_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, - uint32_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 net_init_config *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_init_config_network_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->network_interfaces) - 1)); - if (sizeof(CONFIG_NET_CONFIG_MY_IPV4_ADDR) == 1) { - /* Empty address, skip setting ANY address in this case */ - return; - } + cfg = &config->network_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) +static void ipv6_setup(struct net_if *iface, + int ifindex, + const struct net_init_config_network_interfaces *cfg) { - 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), - }; +#if defined(CONFIG_NET_IPV6) + const struct net_init_config_ipv6 *ipv6 = &cfg->ipv6; + struct net_if_mcast_addr *ifmaddr; + struct net_if_addr *ifaddr; + bool ret; - NET_INFO("Running dhcpv6 client..."); + if (!ipv6->status) { + NET_DBG("Skipping IPv%c setup for iface %d", '6', ifindex); + net_if_flag_clear(iface, NET_IF_IPV6); + return; + } - net_dhcpv6_start(iface, ¶ms); -} -#else /* CONFIG_NET_DHCPV6 */ -#define setup_dhcpv6(...) -#endif /* CONFIG_NET_DHCPV6 */ + /* 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 !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 + if (ipv6->ipv6_addresses[j].value == NULL || + ipv6->ipv6_addresses[j].value[0] == '\0') { + continue; + } -static struct net_mgmt_event_callback mgmt6_cb; -static struct in6_addr laddr; + ret = net_ipaddr_mask_parse(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; + } -static void ipv6_event_handler(struct net_mgmt_event_callback *cb, - uint32_t mgmt_event, struct net_if *iface) -{ - struct net_if_ipv6 *ipv6 = iface->config.ip.ipv6; - int i; + if (net_ipv6_is_addr_unspecified(&addr.sin6_addr)) { + continue; + } - if (!ipv6) { - return; + 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; + } + + NET_DBG("Added %s address %s to iface %d", "unicast", + net_sprint_ipv6_addr(&addr.sin6_addr), + ifindex); } - 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; - } + 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') { + 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 = net_ipaddr_mask_parse(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; + } - 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)); + 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 (ifaddr->addr_type == NET_ADDR_DHCP) { - char remaining_str[] = "infinite"; - uint32_t remaining; + NET_DBG("Added %s address %s to iface %d", "multicast", + net_sprint_ipv6_addr(&addr.sin6_addr), + ifindex); + } - remaining = net_timeout_remaining(&ifaddr->lifetime, - k_uptime_get_32()); + ARRAY_FOR_EACH(ipv6->prefixes, j) { + struct net_if_ipv6_prefix *prefix; + struct sockaddr_in6 addr = { 0 }; - if (!ifaddr->is_infinite) { - snprintk(remaining_str, sizeof(remaining_str), - "%u", remaining); - } + if (ipv6->prefixes[j].address == NULL || + ipv6->prefixes[j].address[0] == '\0') { + continue; + } - NET_INFO("Lifetime: %s seconds", remaining_str); + ret = net_ipaddr_mask_parse(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; } -#endif - services_notify_ready(NET_CONFIG_NEED_IPV6); + 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 (mgmt_event == NET_EVENT_IPV6_ROUTER_ADD) { - services_notify_ready(NET_CONFIG_NEED_ROUTER); + if (ipv6->hop_limit > 0) { + net_if_ipv6_set_hop_limit(iface, ipv6->hop_limit); } + + if (ipv6->multicast_hop_limit > 0) { + net_if_ipv6_set_mcast_hop_limit(iface, ipv6->multicast_hop_limit); + } + + 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)), + }; + + net_dhcpv6_start(iface, ¶ms); + } +#endif } -static void setup_ipv6(struct net_if *iface, uint32_t flags) +static void ipv4_setup(struct net_if *iface, + int ifindex, + const struct net_init_config_network_interfaces *cfg) { +#if defined(CONFIG_NET_IPV4) + const struct net_init_config_ipv4 *ipv4 = &cfg->ipv4; struct net_if_addr *ifaddr; - uint32_t mask = NET_EVENT_IPV6_DAD_SUCCEED; + struct net_if_mcast_addr *ifmaddr; + bool ret; - if (sizeof(CONFIG_NET_CONFIG_MY_IPV6_ADDR) == 1) { - /* Empty address, skip setting ANY address in this case */ - goto exit; + if (!ipv4->status) { + NET_DBG("Skipping IPv%c setup for iface %d", '4', ifindex); + net_if_flag_clear(iface, NET_IF_IPV4); + return; } - 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; - } + /* 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; - if (flags & NET_CONFIG_NEED_ROUTER) { - mask |= NET_EVENT_IPV6_ROUTER_ADD; - } + if (ipv4->ipv4_addresses[j].value == NULL || + ipv4->ipv4_addresses[j].value[0] == '\0') { + continue; + } - net_mgmt_init_event_callback(&mgmt6_cb, ipv6_event_handler, mask); - net_mgmt_add_event_callback(&mgmt6_cb); + ret = net_ipaddr_mask_parse(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; + } - /* - * check for CMD_ADDR_ADD bit here, NET_EVENT_IPV6_ADDR_ADD is - * a combination of _NET_EVENT_IPV6_BASE | NET_EVENT_IPV6_CMD_ADDR_ADD - * so it will always return != NET_EVENT_IPV6_CMD_ADDR_ADD if any other - * event is set (for instance NET_EVENT_IPV6_ROUTER_ADD) - */ - if ((mask & NET_EVENT_IPV6_CMD_ADDR_ADD) == - NET_EVENT_IPV6_CMD_ADDR_ADD) { - 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); + if (net_ipv4_is_addr_unspecified(&addr.sin_addr)) { + continue; } - } -exit: + ifaddr = net_if_ipv4_addr_add( + iface, + &addr.sin_addr, + /* If DHCPv4 is enabled, then allow address + * to be overridden. + */ + ipv4->dhcpv4_enabled ? 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; + } - 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); - } + /* 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)); + } + } - return; -} + NET_DBG("Added %s address %s to iface %d", "unicast", + net_sprint_ipv4_addr(&addr.sin_addr), + ifindex); -#else -#define setup_ipv6(...) -#define setup_dhcpv6(...) -#endif /* CONFIG_NET_IPV6 */ + if (mask_len > 0) { + struct in_addr netmask = { 0 }; -#if defined(CONFIG_NET_NATIVE) -static void iface_up_handler(struct net_mgmt_event_callback *cb, - uint32_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); + netmask.s_addr = BIT_MASK(mask_len); - k_sem_reset(&counter); - k_sem_give(&waiter); - } -} + net_if_ipv4_set_netmask_by_addr(iface, + &addr.sin_addr, + &netmask); -static bool check_interface(struct net_if *iface) -{ - if (net_if_is_up(iface)) { - k_sem_reset(&counter); - k_sem_give(&waiter); - return true; + NET_DBG("Added %s address %s to iface %d", "netmask", + net_sprint_ipv4_addr(&netmask), + ifindex); + } } - NET_INFO("Waiting interface %d (%p) to be up...", - net_if_get_by_iface(iface), iface); + ARRAY_FOR_EACH(ipv4->ipv4_multicast_addresses, j) { + struct sockaddr_in addr = { 0 }; - net_mgmt_init_event_callback(&mgmt_iface_cb, iface_up_handler, - NET_EVENT_IF_UP); - net_mgmt_add_event_callback(&mgmt_iface_cb); - - return false; -} -#else -static bool check_interface(struct net_if *iface) -{ - k_sem_reset(&counter); - k_sem_give(&waiter); + if (ipv4->ipv4_multicast_addresses[j].value == NULL || + ipv4->ipv4_multicast_addresses[j].value[0] == '\0') { + continue; + } - return true; -} -#endif + ret = net_ipaddr_mask_parse(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; + } -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 (net_ipv4_is_addr_unspecified(&addr.sin_addr)) { + continue; + } - if (app_info) { - NET_INFO("%s", app_info); - } + 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; + } - if (!iface) { - iface = net_if_get_default(); + NET_DBG("Added %s address %s to iface %d", "multicast", + net_sprint_ipv4_addr(&addr.sin_addr), + ifindex); } - if (!iface) { - return -ENOENT; + if (ipv4->time_to_live > 0) { + net_if_ipv4_set_ttl(iface, ipv4->time_to_live); } - if (net_if_flag_is_set(iface, NET_IF_NO_AUTO_START)) { - return -ENETDOWN; + if (ipv4->multicast_time_to_live > 0) { + net_if_ipv4_set_mcast_ttl(iface, ipv4->multicast_time_to_live); } - if (timeout < 0) { - count = -1; - } else if (timeout == 0) { - count = 0; - } else { - count = LOOP_DIVIDER; - } + if (ipv4->gateway != NULL && ipv4->gateway[0] != '\0') { + struct sockaddr_in addr = { 0 }; - /* First make sure that network interface is up */ - if (check_interface(iface) == false) { - k_sem_init(&counter, 1, K_SEM_MAX_LIMIT); + ret = net_ipaddr_mask_parse(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); - while (count-- > 0) { - if (!k_sem_count_get(&counter)) { - break; + 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 (IS_ENABLED(CONFIG_NET_DHCPV4) && ipv4->dhcpv4_enabled) { + 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 = net_ipaddr_mask_parse(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 (IS_ENABLED(CONFIG_NET_IPV4_AUTO) && ipv4->ipv4_autoconf_enabled) { + 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 net_init_config *config, + const struct net_init_config_network_interfaces *cfg) +{ +#if defined(CONFIG_NET_VLAN) + const struct net_init_config_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 net_init_config *config, + const struct net_init_config_network_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) @@ -497,29 +653,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_init_config_network_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 - " @@ -527,27 +669,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; + } + + ARRAY_FOR_EACH(flags, i) { + if (strcmp(flags[i].str, &flag_str[(*clear) ? 1 : 0]) == 0) { + return flags[i].flag; + } + } + + 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 (IS_ENABLED(CONFIG_NET_CONFIG_NEED_IPV6_ROUTER)) { - flags |= NET_CONFIG_NEED_ROUTER; + 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); } - if (IS_ENABLED(CONFIG_NET_CONFIG_NEED_IPV4)) { - flags |= NET_CONFIG_NEED_IPV4; + return 0; +} + +static int generated_net_config_init_app(const struct device *dev, + const char *app_info) +{ + const struct net_init_config *config; + int ret, ifindex; + + config = net_config_get_init_config(); + if (config == NULL) { + NET_ERR("Network configuration not found."); + return -ENOENT; } - /* Initialize the application automatically if needed */ - ret = net_config_init_by_iface(iface, app_info, flags, - CONFIG_NET_CONFIG_INIT_TIMEOUT * MSEC_PER_SEC); + ret = wait_for_interface(config->network_interfaces, + sizeof(config->network_interfaces)); if (ret < 0) { - NET_ERR("Network initialization failed (%d)", ret); + NET_WARN("Timeout while waiting network interfaces (%d)", ret); + return ret; + } + + ARRAY_FOR_EACH(config->network_interfaces, i) { + const struct net_init_config_network_interfaces *cfg; + struct net_if *iface; + const char *name; + + cfg = &config->network_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)) { - net_init_clock_via_sntp(); + 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 @@ -566,17 +901,37 @@ int net_config_init_app(const struct device *dev, const char *app_info) } } - 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 net_init_config *net_config_get_init_config(void) +{ +#define NET_INIT_CONFIG_ENABLE_DATA +#include "net_init_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 &NET_INIT_CONFIG_DATA; /* defined in net_init_config.inc file */ +} + #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 7f90df1d9e3ec..42ba27a7f4253 100644 --- a/subsys/net/lib/config/init_clock_sntp.c +++ b/subsys/net/lib/config/init_clock_sntp.c @@ -18,7 +18,9 @@ static void sntp_resync_handler(struct k_work *work); static K_WORK_DELAYABLE_DEFINE(sntp_resync_work_handle, sntp_resync_handler); #endif -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(); @@ -29,35 +31,39 @@ 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); } 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); } -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) { + ret = sntp_init_helper(&ts, server, timeout); + if (ret < 0) { LOG_ERR("Cannot set time using SNTP"); goto end; } tspec.tv_sec = ts.seconds; tspec.tv_nsec = ((uint64_t)ts.fraction * (1000 * 1000 * 1000)) >> 32; - res = clock_settime(CLOCK_REALTIME, &tspec); + + ret = clock_settime(CLOCK_REALTIME, &tspec); end: #ifdef CONFIG_NET_CONFIG_SNTP_INIT_RESYNC k_work_reschedule(&sntp_resync_work_handle, K_SECONDS(CONFIG_NET_CONFIG_SNTP_INIT_RESYNC_INTERVAL)); #endif - return res; + return ret; } #ifdef CONFIG_NET_CONFIG_SNTP_INIT_RESYNC 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/net-init-config.yaml b/tests/net/lib/config/net-init-config.yaml new file mode 100644 index 0000000000000..9a3c112437174 --- /dev/null +++ b/tests/net/lib/config/net-init-config.yaml @@ -0,0 +1,151 @@ +# 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_init_config: + 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: 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_enabled: true + ipv4_autoconf_enabled: 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_enabled: 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_enabled: 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_enabled: 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..285dce2e99ff1 --- /dev/null +++ b/tests/net/lib/config/prj.conf @@ -0,0 +1,50 @@ +CONFIG_NETWORKING=y +CONFIG_NET_TEST=y +CONFIG_ZTEST=y +CONFIG_NET_LOG=y +CONFIG_LOG=y + +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..ed5fc5415aed7 --- /dev/null +++ b/tests/net/lib/config/src/main.c @@ -0,0 +1,1028 @@ +/* + * 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 "net_init_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.addr = data->mac_addr; + data->ll_addr.len = 6U; + + 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; + + LOG_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; + + LOG_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) +{ + DBG("Interface %p (%s) [%d]\n", iface, 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_init_config_network_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 != NULL) { + ifindex = net_if_get_by_name(cfg->set_name); + } + + if (cfg->name != NULL) { + 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_names) +{ + const struct net_init_config *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->network_interfaces[i]); + } +} + +#if defined(CONFIG_NET_IPV4) +static void check_ipv4(const struct net_init_config_network_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 == NULL) { + continue; + } + + zassert_true(net_ipaddr_mask_parse(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 == NULL) { + continue; + } + + 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 != NULL) { + 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_enabled) { +#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_enabled) { + 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 net_init_config *cfg, int index) +{ + ARG_UNUSED(cfg); + ARG_UNUSED(index); +} +#endif + +#if defined(CONFIG_NET_IPV6) +static void check_ipv6(const struct net_init_config_network_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 == NULL) { + continue; + } + + zassert_true(net_ipaddr_mask_parse(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 == NULL) { + continue; + } + + zassert_true(net_ipaddr_mask_parse( + 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 == NULL) { + continue; + } + + zassert_true(net_ipaddr_mask_parse( + 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_init_config_iface *cfg) +{ +} +#endif + +ZTEST(net_config, test_interface_ipv4_addresses) +{ + const struct net_init_config *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->network_interfaces[i]); + +#define IF_ENABLED_IPV4(cfg, i) \ + COND_CODE_1(CONFIG_NET_IPV4, \ + ((cfg)->network_interfaces[i].ipv4.status), \ + (false)) + + if (IF_ENABLED_IPV4(config, i)) { + check_ipv4(&config->network_interfaces[i]); + } + } +} + +ZTEST(net_config, test_interface_ipv6_addresses) +{ + const struct net_init_config *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->network_interfaces[i]); + +#define IF_ENABLED_IPV6(cfg, i) \ + COND_CODE_1(CONFIG_NET_IPV6, \ + ((cfg)->network_interfaces[i].ipv6.status), \ + (false)) + + if (IF_ENABLED_IPV6(config, i)) { + check_ipv6(&config->network_interfaces[i]); + } + } +} + +ZTEST(net_config, test_interface_vlan) +{ + const struct net_init_config *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->network_interfaces, i) { + const struct net_init_config_network_interfaces *cfg; + const struct net_init_config_vlan *vlan; + struct net_if *iface; + uint16_t tag; + + cfg = &config->network_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_flags) +{ + const struct net_init_config *config; + int ifindex = 0; + + config = net_config_get_init_config(); + zassert_not_null(config, "Network configuration not found."); + + ARRAY_FOR_EACH(config->network_interfaces, i) { + const struct net_init_config_network_interfaces *cfg; + + cfg = &config->network_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 == NULL || + 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..1e4c88f1df870 --- /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_posix + - native_posix/native/64 +tests: + net.config: + tags: + - net + - iface