Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
docs:guide-developer:network-scripting [2020/12/29 09:41] – add arguments to functions lemoerdocs:guide-developer:network-scripting [2023/07/19 09:34] (current) – [Get WAN gateway for unmanaged default route] add vgaetera
Line 1: Line 1:
 ====== Network scripts ====== ====== Network scripts ======
- +netifd can (probably) bring up a wired, static ip configuration without shell scripts. 
-netifd can (probably) bring up a wired, static ip configuration without shell scripts. For everything else (PPPoE or 3G) it needs //protocol handlers// implemented as sets of shell functions.+For everything else (PPPoE or 3G) it needs //protocol handlers// implemented as sets of shell functions.
  
 ===== Writing protocol handlers ===== ===== Writing protocol handlers =====
- +Each protocol handler is a shell script in ''/lib/netifd/proto/''. 
-Each protocol handler is a shell script in ''/lib/netifd/proto/''. It is run (or maybe sourced) when netifd daemon starts. Changes made to the scripts do not take effect until netifd restarts. The name of the file usually matches ''option proto'' in ''/etc/config/network''.+It is run (or maybe sourced) when netifd daemon starts. 
 +Changes made to the scripts do not take effect until netifd restarts. 
 +The name of the file usually matches ''option proto'' in ''/etc/config/network''.
  
 To be able to access the network functions, the script needs to include the necessary shell scripts at the beginning: To be able to access the network functions, the script needs to include the necessary shell scripts at the beginning:
Line 26: Line 28:
  
 ==== init_config ==== ==== init_config ====
- +The main purpose of this function is to let netifd know which parameters does this protocol have. 
-The main purpose of this function is to let netifd know which parameters does this protocol have. These parameters then can be stored in ''/etc/config/network''.+These parameters then can be stored in ''/etc/config/network''.
  
 <code bash> <code bash>
 proto_protocolname_init_config() { proto_protocolname_init_config() {
-    proto_config_add_string "stringparam" + proto_config_add_string "stringparam" 
-    proto_config_add_int "intparam" + proto_config_add_int "intparam" 
-    proto_config_add_boolean "boolparam" + proto_config_add_boolean "boolparam" 
-    ...+ ...
 } }
 </code> </code>
  
 ==== Setup ==== ==== Setup ====
- 
 The setup procedure implements the actual protocol specifc configuration logic and interface bringup. The setup procedure implements the actual protocol specifc configuration logic and interface bringup.
  
Line 49: Line 50:
 <code bash> <code bash>
 proto_protocolname_setup() { proto_protocolname_setup() {
-    local config="$1" + local config="$1" 
-    # set up the interface+ # set up the interface
 } }
 </code> </code>
Line 56: Line 57:
 This function must be implemented by any protocol backend. This function must be implemented by any protocol backend.
  
-There's usually no need to call ''ifconfig //iface// up'' at the end of this function. If ''no_device=0'', netifd won't even try to start our profile until the device is already up. It waits for ''operstate=up'' and ''carrier=1'', then starts the profile.+There's usually no need to call ''ifconfig //iface// up'' at the end of this function. 
 +If ''no_device=0'', netifd won't even try to start our profile until the device is already up. 
 +It waits for ''operstate=up'' and ''carrier=1'', then starts the profile.
  
 If ''no_device=1'', netifd will bring it up, when it receives a notification from us: If ''no_device=1'', netifd will bring it up, when it receives a notification from us:
Line 69: Line 72:
 </code> </code>
    
-However, this only works once. If someone called ''ifconfig //iface// down'', netifd won't try to bring it up again (at least in BB), so just in case you may put the "up" command in the function.+However, this only works once. 
 +If someone called ''ifconfig //iface// down'', netifd won't try to bring it up again (at least in BB), so just in case you may put the "up" command in the function.
  
 ==== Teardown ==== ==== Teardown ====
- +Protocols that need special shutdown handling, for example to kill associated daemons, may implement a stop procedure. 
-Protocols that need special shutdown handling, for example to kill associated daemons, may implement +This procedure is called when an interface is brought down before the associated UCI state is cleared.
-a stop procedure. This procedure is called when an interface is brought down before the associated UCI +
-state is cleared.+
  
 The function is called when we do ''ifdown //profile//'' or when ''no_device=0'' and netifd detects link connectivity loss. The function is called when we do ''ifdown //profile//'' or when ''no_device=0'' and netifd detects link connectivity loss.
Line 85: Line 87:
 <code bash> <code bash>
 proto_protocolname_teardown() { proto_protocolname_teardown() {
-    local config="$1" + local config="$1" 
-    local iface="$2" + local iface="$2" 
-    # tear down the interface+ # tear down the interface
 } }
 </code> </code>
Line 94: Line 96:
  
 ==== Coldplug ==== ==== Coldplug ====
- 
 By default, only interfaces present in ''/proc/net/dev'', like //eth0//, are brought up on boot. By default, only interfaces present in ''/proc/net/dev'', like //eth0//, are brought up on boot.
 Protocols which use virtual interfaces must set two variables at the beginning of init_config. Protocols which use virtual interfaces must set two variables at the beginning of init_config.
Line 100: Line 101:
 <code bash> <code bash>
 proto_protocolname_init_config() { proto_protocolname_init_config() {
-    no_device=1 + no_device=1 
-    available=1 + available=1 
-    ...+ ...
 } }
 </code> </code>
  
-==== How does it work internally? ====+==== Debugging ==== 
 +  * STDERR in protos is redirected to the syslog. ''echo "message" > 2>&1'' can be used to print. 
 +  * As ''set -x'' also prints to STDERR, it can be used to trace which commands are called inside the proto. However this is very spammy. 
 +  * There is a pending patch: https://patchwork.ozlabs.org/project/openwrt/patch/20201229233319.164640-1-me@irrelefant.net/.
  
 +==== Available proto flags ====
 +Flags can be added to a proto handler in ''proto_protoname_init_config'', by setting their value to ''1''.
 +The information about all loaded protocols can be obtained by calling //ubus call network get_proto_handlers//.
 +
 +^ Name ^ Name in //ubus call network get_proto_handlers// ^ Meaning ^
 +| no_device | no_device | The interface does not have a lower device. <color #ff7f27>TBD: Explain implications.</color>|
 +| | immediate | <color #ff7f27>TBD: I only saw this for the proto static. Maybe this is impossible to set from shell protos?</color> |
 +| available | init_available | Initialize the device directly as available, without the device specified by ifname being present <color #ff7f27>TBD: Is that correct?</color>|
 +| renew_handler | renew_available | Has a renew handler, which can be called. <color #ff7f27>TBD: How can it be called? Is it called automatically by sth.?</color> |
 +| teardown_on_l3_link_down | teardown_on_l3_link_down | <color #ff7f27>TBD</color> |
 +| | force_link_default | <color #ff7f27>TBD: I only saw this for the proto static. Maybe this is impossible to set from shell protos?</color> |
 +| no_proto_task | no_task | <color #ff7f27>TBD</color> |
 +
 +==== Error codes ====
 +If errors for interfaces occur, the json object in //ifstatus interfacename// or in //ubus call network.interface dump// have an attribute //"error"//.
 +If there are no errors, this attribute is not existing.
 +
 +^ CODE ^ Meaning ^
 +| NO_DEVICE | The configured device in ifname is not found. |
 +| DEVICE_CLAIM_FAILED | One of the reasons for this is, that the device configured by ifname does not exist. Usually this would result in NO_DEVICE as the device is only claimed when it is available. However when the proto flags //available=1// and //no_device=0// are set, the device specified by ifname is tried to be claimed directly. <color #ff7f27>TBD: Is this the only case? Is this correct?</color> |
 +| SETUP_FAILED | <color #ff7f27>TBD</color> |
 +
 +Custom error codes can be thrown from the proto scripts aswell.
 +This is done via ''proto_notify_error "$config" MY_CUSTOM_ERROR_ID''.
 +
 +==== How does it work internally? ====
 <code bash> <code bash>
 init_proto $proto $cmd $interface $data $ifname init_proto $proto $cmd $interface $data $ifname
Line 125: Line 155:
 Otherwise the script will fail with something like: Otherwise the script will fail with something like:
   * ''add_protocol: not found''   * ''add_protocol: not found''
- 
  
 ===== API ===== ===== API =====
- 
 The following functions are defined in ''/lib/netifd/netifd-proto.sh''. The following functions are defined in ''/lib/netifd/netifd-proto.sh''.
  
 ==== netifd functions ==== ==== netifd functions ====
- 
 === initialization functions === === initialization functions ===
 +  * ''add_protocol''
 +  * ''init_proto''
 +  * ''proto_config_add_boolean''
 +  * ''proto_config_add_int''
 +  * ''proto_config_add_string''
  
-----+=== notification functions === 
 +Note: some of these function exit immediately. 
 +These functions are dispatched to ubus and arrive [[commit>?p=project/netifd.git;a=blob;f=proto-shell.c;hb=c00c8335d6188daa326ecfe5a62da15a9b9987e1#l792|here]]
  
-** ''add_protocol'' ** +  ''proto_block_restart $interface'' 
-----+  * ''proto_init_update $ifname $up [$external]'' 
 +  ''proto_kill_command $interface [$signal]'' 
 +  ''proto_notify_error $interface $str1 [$str2] [$str3] ...'' 
 +  * ''proto_run_command''
  
-** ''init_proto'' ** +We can start a daemon that maintains the connection asynchronously by calling ''proto_run_command "$config" //custom_script//''. 
-----+Netifd remembers its pid. 
 +It can be killed manually by calling ''proto_kill_command "$config"''
 +Netifd can automatically kill it, when the profile stopped.
  
-** ''proto_config_add_boolean'' ** + * ''proto_add_host_dependency''
-----+
  
-** ''proto_config_add_int'' ** +It seems to avoid race conditions in protos. 
-----+We can register the following type of dependencies by calling: 
 +  * ''proto_add_host_dependency "$config" "$host" "$ifname"'' 
 +  ''proto_add_host_dependency "$config" \'\' "$ifname"'' (only wait for an iface) 
 +  * (maybe more?)
  
-** ''proto_config_add_string'' ** +Only if ''$iface'' is up, the corresponding ''$config'' will be loaded. 
-----+So we need another proto to be completed, we can use this here.
  
-=== notification functions ===+From some observations I think it looks like technically the ''$config'' is initially loaded, then the ''proto_add_host_dependency'' is called inside the proto handler and then the ''$config'' is removed again until ''$iface'' is available, up, ... or so. 
 +However, currently I do not know where to find the source code, so this is only a vague idea of what happens.
  
-Note: some of these function exit immediately.+  * ''proto_send_update $interface '' 
 +  * '' proto_set_available $interface $state''
  
-----+==== common functions ==== 
 +  * '' json_add_string '' 
 +  * '' json_dump '' 
 +  * '' json_init '' 
 +  * '' json_get_var '' 
 +  * '' json_get_vars ''
  
-** ''proto_block_restart $interface'' ** +===== Examples ===== 
-----+[[https://github.com/openwrt/openwrt/blob/master/package/base-files/files/lib/functions/network.sh|Network functions]] rely on runtime configuration and can return unexpected result if you are using MWAN or VPN. 
 +Replace automatic WAN detection with explicit interface definition if necessary.
  
-** ''proto_init_update $ifname $up $external'' ** +==== Get LAN address ==== 
-----+<code bash> 
 +# Runtime configuration 
 +NET_IF="lan" 
 +. /lib/functions/network.sh 
 +network_flush_cache 
 +network_get_ipaddr NET_ADDR "${NET_IF}" 
 +network_get_ipaddr6 NET_ADDR6 "${NET_IF}" 
 +echo "${NET_ADDR}" 
 +echo "${NET_ADDR6}" 
 +</code>
  
-** ''proto_kill_command $interface [$signal]'' ** +==== Get WAN interface ==== 
-----+<code bash> 
 +# Runtime configuration 
 +. /lib/functions/network.sh 
 +network_flush_cache 
 +network_find_wan NET_IF 
 +network_find_wan6 NET_IF6 
 +echo "${NET_IF}" 
 +echo "${NET_IF6}" 
 +</code>
  
-** ''proto_notify_error $interface $str1 [$str2] [$str3] ...'' ** +==== Get WAN L2 device ==== 
-----+<code bash> 
 +# Runtime configuration 
 +. /lib/functions/network.sh 
 +network_flush_cache 
 +network_find_wan NET_IF 
 +network_find_wan6 NET_IF6 
 +network_get_physdev NET_L2D "${NET_IF}" 
 +network_get_physdev NET_L2D6 "${NET_IF6}" 
 +echo "${NET_L2D}" 
 +echo "${NET_L2D6}" 
 +</code>
  
-** ''proto_run_command'' **+==== Get WAN L3 device ==== 
 +<code bash> 
 +# Runtime configuration 
 +. /lib/functions/network.sh 
 +network_flush_cache 
 +network_find_wan NET_IF 
 +network_find_wan6 NET_IF6 
 +network_get_device NET_L3D "${NET_IF}" 
 +network_get_device NET_L3D6 "${NET_IF6}" 
 +echo "${NET_L3D}" 
 +echo "${NET_L3D6}"
  
-We can start a daemon that maintains the connection asynchronously by calling ''proto_run_command "$config" //custom_script//''Netifd remembers its pidIt can be killed manually by calling ''proto_kill_command "$config"''Netifd can automatically kill it, when the profile stopped+# Persistent configuration 
 +uci get network.wan.device 
 +uci get network.wan6.device 
 +</code>
  
-----+==== Get WAN address ==== 
 +<code bash> 
 +# Runtime configuration 
 +. /lib/functions/network.sh 
 +network_flush_cache 
 +network_find_wan NET_IF 
 +network_find_wan6 NET_IF6 
 +network_get_ipaddr NET_ADDR "${NET_IF}" 
 +network_get_ipaddr6 NET_ADDR6 "${NET_IF6}" 
 +echo "${NET_ADDR}" 
 +echo "${NET_ADDR6}"
  
-** ''proto_add_host_dependency'' **+# Persistent static configuration 
 +uci get network.wan.ipaddr 
 +uci get network.wan6.ip6addr 
 +</code>
  
-It seems to avoid race conditions in protosWe can register the following type of dependencies by calling: +==== Get WAN gateway ==== 
-  * ''proto_add_host_dependency "$config" "$host" "$ifname"'' +<code bash> 
-  * ''proto_add_host_dependency "$config\'\' "$ifname"'' (only wait for an iface) +# Runtime configuration 
-  * (maybe more?)+/lib/functions/network.sh 
 +network_flush_cache 
 +network_find_wan NET_IF 
 +network_find_wan6 NET_IF6 
 +network_get_gateway NET_GW "${NET_IF}" 
 +network_get_gateway6 NET_GW6 "${NET_IF6}" 
 +echo "${NET_GW}
 +echo "${NET_GW6}"
  
-Only if ''$iface'' is up, the corresponding ''$config'' will be loadedSo we need another proto to be completed, we can use this here\\+# Persistent static configuration 
 +uci get network.wan.gateway 
 +uci get network.wan6.ip6gw 
 +</code>
  
-From some observations I think it looks like technically the ''$config'' is initially loaded, then the ''proto_add_host_dependency'' is called inside the proto handler and then the ''$config'' is removed again until ''$iface'' is available, up, ... or so. However, currently I do not know where to find the source code, so this is only a vague idea of what happens.  \\+==== Get WAN prefix ==== 
 +<code bash> 
 +# Runtime configuration 
 +. /lib/functions/network.sh 
 +network_flush_cache 
 +network_find_wan6 NET_IF6 
 +network_get_prefix6 NET_PFX6 "${NET_IF6}" 
 +echo "${NET_PFX6}"
  
 +# Persistent static configuration
 +uci get network.wan6.ip6prefix
 +</code>
  
----- +==== Get WAN gateway for unmanaged default route ==== 
- +<code bash> 
-** ''proto_send_update $interface '' ** +# Runtime configuration 
- +ubus call network.interface dump \ 
----- +| jsonfilter -e "$['interface'][*]['inactive'] 
-** '' proto_set_available $interface $state'' ** +['route'][*]['target'='0.0.0.0','mask'='0','nexthop']" 
----- +</code>
- +
-==== common functions ==== +
- +
----- +
-** '' json_add_string '' ** +
----- +
-** '' json_dump '' ** +
----- +
-** '' json_init '' ** +
----- +
-** '' json_get_var '' ** +
----- +
-** '' json_get_vars '' **+
  
  • Last modified: 2020/12/29 09:41
  • by lemoer