#!/bin/sh #------------------------------------------------------------------------------ # /etc/rc.d/circuit-common - common generic functions operating on circuits # # Last Update: $Id$ #------------------------------------------------------------------------------ if [ "$circuit_api" != yes ] then . /etc/boot.d/string.inc . /etc/boot.d/list.inc . /etc/boot.d/env.inc . /etc/boot.d/locking.inc [ -f /etc/rc.d/bundle-common ] && . /etc/rc.d/bundle-common . /usr/share/mom/core # needed by some scripts included by this script eval $(grep '^OPT_IPV[46]=' /etc/rc.cfg) ############################################################# # ------------------------ C O R E ------------------------ # ############################################################# # contains circuit information circuit_dir=/var/run/circuits circuit_state_dir=$circuit_dir/states circuit_states="online semionline ready active deleted failed inactive" # include information about specific layer-3 protocols circuit_protocols= for f in /etc/rc.d/circuit-proto.* do [ -f $f ] && . $f done # include information about specific circuit types for f in /etc/rc.d/circuits.* do [ -f $f ] && . $f done # the logging facility to use for circuit system circuit_logfacility=local2 # circuit prefix circuit_prefix=circ circuit_prefix_len=${#circuit_prefix} # we allow a single ip(v6)-down pass to take up to one minute ip_down_timeout=60 # maximum number of seconds to wait for a reply from circd circd_timeout=30 # contains information which circuits are part of which bundle links_file=/var/run/circuits/links # contains identifiers for various circuit types id_dir=/var/run/circuits/ids # contains log FIFOs circd_fifo_dir=/var/run/circuits/fifo # Acquires a read lock on a specific circuit's properties. A read lock inhibits # write operations from other processes but allows read operations from other # processes. # # Input: # $1 = circuit identifier # $2 = caller # Exit code: # 0 if lock has been acquired, a nonzero value otherwise circuit_read_lock() { sync_lock_resource_for_reading $1 $2 } # Releases a read lock held on a specific circuit's properties. # # Input: # $1 = circuit identifier # $2 = caller circuit_read_unlock() { sync_unlock_resource_for_reading $1 $2 } # Acquires a write lock on a specific circuit's properties. A write lock # inhibits read and write operations from other processes. # # Input: # $1 = circuit identifier # $2 = caller # Exit code: # 0 if lock has been acquired, a nonzero value otherwise circuit_write_lock() { sync_lock_resource_for_writing $1 $2 } # Releases a write lock held on a specific circuit's properties. # # Input: # $1 = circuit identifier # $2 = caller circuit_write_unlock() { sync_unlock_resource_for_writing $1 $2 } # Acquires an exclusive lock on a specific circuit and a read lock on the # circuit's properties. This allows # a) other processes that do not hold the exclusive lock to read the circuit's # properties without having to wait # b) the process holding the read lock to promote it to a write lock if # necessary without risking a deadlock # # Input: # $1 = circuit identifier # $2 = caller # Exit code: # 0 if lock has been acquired, a nonzero value otherwise circuit_lock() { sync_lock_resource $1 $2 circuit_read_lock $1 $2 } # Releases an exclusive lock held on a specific circuit and the read lock held # on the circuit's properties. # # Input: # $1 = circuit identifier # $2 = caller circuit_unlock() { circuit_read_unlock $1 $2 sync_unlock_resource $1 $2 } # Allocates a numbered and prefixed circuit identifier (e.g. ppp0) and stores # it in a user-defined variable. # # Input: # $1 = circuit prefix # $2 = target variable # $3 = (optional) start value, defaults to 0 # Output: # ${$2} = circuit identifier (= ${1}X with some number X) circuit_allocate_device() { local next=$id_dir/$1.next sync_lock_resource circuit_id circuit_allocate_device if [ -f $next ] then local idx read idx < $next else local idx=${3:-0} fi echo $((idx+1)) > $next sync_unlock_resource circuit_id circuit_allocate_device eval $2=\$1\$idx } # Determines if a circuit exists. # # Input: # $1 = circuit identifier # Exit code: # 0 if circuit exists, a nonzero value otherwise circuit_exists() { local id=$1 circuit_resolve_alias id [ -f $circuit_state_dir/all/$id ] } # Prints variables of a given circuit. # # Input: # $1 = circuit identifier # Output: # The variable definitions (e.g. "a=x b=y"). Use # vars=$(circuit_get_data $id) # eval $vars # to make them available in your shell context. # Exit code: # 0 if circuit exists, a nonzero value otherwise # Synchronization: # Takes a read lock on the circuit's properties. circuit_get_data() { local id=$1 circuit_resolve_alias id local file=$circuit_state_dir/all/$id local rc=0 circuit_read_lock $id circuit_get_data if [ -f $file ] then cat $file rc=$? else rc=1 fi circuit_read_unlock $id circuit_get_data return $rc } # Associates a dynamic field with a circuit. Multi-line data is converted into # single-line data by replacing newlines by spaces. # # Input: # $1 = circuit identifier # $2 = field # $3 = data # Synchronization: # Takes a write lock on the circuit's properties. circuit_write_field() { local id=$1 field=$2 data="$(echo -n "$3" | tr '\n' ' ')" circuit_resolve_alias id local file=$circuit_state_dir/all/$id local rc=0 circuit_write_lock $id circuit_write_field if [ -f $file ] then sed -i "/^$field=/d" $file echo "$field=\"$data\"" >> $file else rc=1 fi circuit_write_unlock $id circuit_write_field return $rc } # Reads a dynamic field from a circuit. # Input: # $1 = circuit identifier # $2 = field # $3 = (optional) name of variable receiving the result; if missing, the # result is stored in a variable with the name $2 # Don't use variable names starting with "_"! # Output: # variable $2 (or $3, if set) is set with the value of the field # Synchronization: # Takes a read lock on the circuit's properties. circuit_read_field() { local _id=$1 _field=$2 _result=${3:-$2} circuit_resolve_alias _id local _file=$circuit_state_dir/all/$_id local _rc=0 eval $_result= circuit_read_lock $_id circuit_read_field if [ -f $_file ] then eval $(sed -n "s/^$_field=/$_result=/p" $_file) else _rc=1 fi circuit_read_unlock $_id circuit_read_field return $_rc } # Creates a new circuit. # Input: # $1 = variable receiving the circuit identifier # $2 = variable receiving an error message (if any) # Output: # ${$2} = circuit identifier # Exit code: # 0 if successful, a non-zero value otherwise (in the latter case, $2 is set # to an error message) circuit_add() { local _circid=$1 _errmsg=$2 _result if _result=$(mom_unicast_message_and_receive_reply \ circd \ create_circuit_message \ create_circuit_reply_message \ $circd_timeout) then local _varnames=$(echo "$_result" | extract_variable_names) local $_varnames eval "$_result" case $message_type in ack_create_circuit_reply_message) eval $_circid=\$ack_create_circuit_reply_message_id return 0 ;; nak_create_circuit_reply_message) eval $_errmsg=\$nak_create_circuit_reply_message_errmsg return 1 ;; *) eval $_errmsg="\"Unknown reply from circd of type $message_type\"" return 2 ;; esac else eval $_errmsg="\"Timeout while waiting for a reply from circd\"" return 3 fi } # Starts a creation group. All circuits that are associated with a creation # group have their post-processing deferred to the moment when the creation # group ends. This is necessary e.g. for MRRU computation and firewall rule # installation where forward or circular references are possible. # # Input: # $1 = variable receiving the identifier of the new creation group # Output: # ${$1} = identifier of a new creation group # Exit code: # 0 if successful, a non-zero value otherwise circuit_start_creation_group() { local _result if _result=$(mom_unicast_message_and_receive_reply \ circd \ start_creation_group_circuit_message \ start_creation_group_circuit_reply_message \ $circd_timeout) then local _varnames=$(echo "$_result" | extract_variable_names) local $_varnames eval "$_result" eval $1=\$start_creation_group_circuit_reply_message_id [ -n "$start_creation_group_circuit_reply_message_id" ] else return 1 fi } # Ends a creation group. Post-processing is performed for all circuits that # belong to this creation group. # # Input: # $1 = creation group identifier circuit_end_creation_group() { end_creation_group_circuit_message_id=$1 \ mom_unicast_message \ circd \ end_creation_group_circuit_message >/dev/null } # Instantiates a multi-instance circuit. If this circuit is part of a bundle, # the bundle is also cloned, providing a unique bundle for the new instance. # This requires the circuit to be cloned to have circ_multi=yes. # # The new circuit is an identical clone of the old one with the following # exceptions: # - circ_origin is set to the old circuit # - circ_name is set to the old circ_name, suffixed by ":" and a number which # makes the name unique # - circ_up and circ_dialmode are set to "yes" and "auto", respectively # # Input: # $1 = circuit identifier # $2 = name of a function that may adapt the settings of the circuit before # it is cloned # - its most important task is to change $circ_type # - if empty, "-instance" is simply appended to the circuit type # - note that the variables mentioned above are already changed when # this function is called # $3 = user-defined value passed as first argument to the adaptor function # specified in $2 # $4 = variable receiving the circuit identifier of the new instance # $5 = variable receiving an error message (if any) # Output: # ${$4} = circuit identifier # Exit code: # 0 if successful # 1 if circ_multi is not "yes" for the circuit to be cloned # 2 if the associated bundle circuit could not be cloned (can only happen to # circuits which are part of a bundle) # 3 if the circuit itself could not be cloned # In case of any failure, $5 is set to an error message. circuit_clone() { local _id=$1 _adaptor=$2 _arg=$3 _res=$4 _errvar=$5 if _result=$(clone_circuit_message_id=$_id \ clone_circuit_message_adaptor=$_adaptor \ clone_circuit_message_adaptor_arg=$_arg \ mom_unicast_message_and_receive_reply \ circd \ clone_circuit_message \ clone_circuit_reply_message \ $circd_timeout) then local _varnames=$(echo "$_result" | extract_variable_names) local $_varnames eval "$_result" case $message_type in ack_clone_circuit_reply_message) eval $_res=\$ack_clone_circuit_reply_message_id return 0 ;; nak_clone_circuit_reply_message) eval $_errvar=\$nak_clone_circuit_reply_message_errmsg return 1 ;; *) eval $_errvar="\"Unknown reply from circd of type $message_type\"" return 2 ;; esac else eval $_errvar="\"Timeout while waiting for a reply from circd\"" return 3 fi } # Attaches a circuit to a bundle. This is necessary for servers where bundle # circuits have to be created on-the-fly. # Input: # $1 = circuit identifier # $2 = bundle circuit identifier # Exit code: # 0 if successful, 1 otherwise circuit_attach_to_bundle() { attach_to_bundle_circuit_message_idchild=$1 \ attach_to_bundle_circuit_message_idbundle=$2 \ mom_unicast_message \ circd \ attach_to_bundle_circuit_message >/dev/null } # Returns all links attached to a bundle circuit. # Input: # $1 = bundle circuit identifier # Output: # one entry for each circuit that is part of the bundle passed circuit_get_bundle_links() { local id=$1 circuit_resolve_alias id sync_lock_resource bundle_db circuit_get_bundle_links sed -n "s/^$id \(.*\)$/\1/p" $links_file sync_unlock_resource bundle_db circuit_get_bundle_links return 0 } ############################################################# # --------------------- A L I A S E S --------------------- # ############################################################# # contains information about circuit aliases alias_file=/var/run/circuits/aliases # Registers a circuit alias. # Input: # $1 = circuit alias # $2 = circuit identifier the alias points to # Exit code: # 0 if successful, a non-zero value otherwise circuit_register_alias() { local alias=$1 circ_id=$2 rc=1 local alias_regex=$(echo "$alias" | sed 's,/,\\/,g') sync_lock_resource alias_db circuit_register_alias if ! grep -q "^${alias_regex} " $alias_file then echo "$alias $circ_id" >> $alias_file rc=0 fi sync_unlock_resource alias_db circuit_register_alias return $rc } # Deregisters a circuit alias. # Input: # $1 = circuit alias # Exit code: # always 0 circuit_deregister_alias() { local alias=$1 local alias_regex=$(echo "$alias" | sed 's,/,\\/,g') sync_lock_resource alias_db circuit_deregister_alias sed -i "/^${alias_regex} /d" $alias_file sync_unlock_resource alias_db circuit_deregister_alias return 0 } # Looks up a circuit alias. # Input: # $1 = circuit alias # Output: # The circuit the alias maps to or the empty string if alias is not found. # Exit code: # always 0 circuit_lookup_alias() { local alias=$1 local alias_regex=$(echo "$alias" | sed 's,/,\\/,g') sync_lock_resource alias_db circuit_lookup_alias sed -n "s/^${alias_regex} \(.*\)$/\1/p" $alias_file sync_unlock_resource alias_db circuit_lookup_alias return 0 } # Resolves a circuit alias if possible. If passed alias is unknown, it is # returned unchanged. # Input: # $1 = variable containing circuit alias # Output: # ${$1} receives the circuit the alias maps to or remains unchanged if the # alias is unknown. # Exit code: # always 0 circuit_resolve_alias() { local _varname=$1 eval local _alias=\$${_varname} local _circ_id=$(circuit_lookup_alias $_alias) if [ -n "$_circ_id" ] then eval $_varname=\$_circ_id fi return 0 } ############################################################# # ------------------ D I A L M O D E S ------------------ # ############################################################# # Returns the global dial mode. # # Output: # The dial mode. circuit_get_global_dialmode() { local _result if _result=$(mom_unicast_message_and_receive_reply \ circd \ get_global_dialmode_message \ get_global_dialmode_reply_message \ $circd_timeout) then local _varnames=$(echo "$_result" | extract_variable_names) local $_varnames eval "$_result" echo $get_global_dialmode_reply_message_mode return 0 else return 1 fi } # Stores the global dial mode. # # Input: # $1 = global dial mode circuit_set_global_dialmode() { local mode=$1 set_global_dialmode_message_mode=$mode \ mom_unicast_message \ circd \ set_global_dialmode_message >/dev/null return 0 } # Returns the local dial mode for a circuit. # # Input: # $1 = circuit identifier # Output: # The circuit's local dial mode. circuit_get_local_dialmode() { local id=$1 _result if _result=$(get_local_dialmode_message_id=$id \ mom_unicast_message_and_receive_reply \ circd \ get_local_dialmode_message \ get_local_dialmode_reply_message \ $circd_timeout) then local _varnames=$(echo "$_result" | extract_variable_names) local $_varnames eval "$_result" echo $get_local_dialmode_reply_message_mode return 0 else return 1 fi } # Stores the local dial mode for a circuit. # # Input: # $1 = circuit identifier # $2 = circuit's local dial mode circuit_set_local_dialmode() { local id=$1 mode=$2 set_local_dialmode_message_id=$id set_local_dialmode_message_mode=$mode \ mom_unicast_message \ circd \ set_local_dialmode_message >/dev/null return 0 } # Returns the effective dial mode for a circuit. # # Input: # $1 = circuit identifier # Output: # The dial mode. circuit_get_effective_dialmode() { local id=$1 _result if _result=$(get_effective_dialmode_message_id=$id \ mom_unicast_message_and_receive_reply \ circd \ get_effective_dialmode_message \ get_effective_dialmode_reply_message \ $circd_timeout) then local _varnames=$(echo "$_result" | extract_variable_names) local $_varnames eval "$_result" echo $get_effective_dialmode_reply_message_mode return 0 else return 1 fi } ############################################################# # ---------------------- S T A T E S ---------------------- # ############################################################# # contains information about the 'online' state of the router # this file is created by rc340.circuits router_state_file=/var/run/circuits/router-state # contains information which circuits control the 'online' state of the router # this file is created by rc340.circuits when_online_file=/var/run/circuits/when-online # Returns all circuits with a given state. # # Input: # $1... = states # Output: # A list of circuits belonging to passed states. May be empty. circuit_get_by_state() { local states=$* _result if _result=$(get_by_state_circuit_message_states=$states \ mom_unicast_message_and_receive_reply \ circd \ get_by_state_circuit_message \ get_by_state_circuit_reply_message \ $circd_timeout) then local _varnames=$(echo "$_result" | extract_variable_names) local $_varnames eval "$_result" echo $get_by_state_circuit_reply_message_circuits return 0 else return 1 fi } # Returns the state of a circuit. # # Input: # $1 = circuit identifier # Output: # The circuit state. # Exit code: # 0 if circuit exists and has a known state, a nonzero value otherwise # Synchronization: # Takes the state lock. circuit_get_state() { local id=$1 _result if _result=$(get_state_circuit_message_id=$id \ mom_unicast_message_and_receive_reply \ circd \ get_state_circuit_message \ get_state_circuit_reply_message \ $circd_timeout) then local _varnames=$(echo "$_result" | extract_variable_names) local $_varnames eval "$_result" echo $get_state_circuit_reply_message_state return 0 else return 1 fi } # Associates passed circuit with passed layer-3 protocol and changes the # circuit's state accordingly ("online" if all configured layer-3 protocols are # up, else "semionline"). # Input: # $1 = circuit identifier # $2 = layer-3 protocol circuit_go_online_if_possible() { local id=$1 l3prot=$2 circuit_event_id=$id \ l3prot_circuit_event_l3prot=$l3prot \ mom_unicast_message \ circd \ up_l3prot_circuit_event >/dev/null return 0 } # Disssociates passed circuit from passed layer-3 protocol and changes the # circuit's state accordingly ("semionline" if at least one other layer-3 # protocol is up yet, else the circuit is hung up). # Input: # $1 = circuit identifier # $2 = layer-3 protocol circuit_go_offline_if_necessary() { local id=$1 l3prot=$2 circuit_event_id=$id \ l3prot_circuit_event_l3prot=$l3prot \ mom_unicast_message \ circd \ down_l3prot_circuit_event >/dev/null return 0 } # Determines whether a given layer-3 protocol is up for a given circuit # Input: # $1 = circuit identifier # $2 = layer-3 protocol # Exit code: # 0 if the layer-3 protocol is up, 1 otherwise circuit_is_l3prot_up() { local id=$1 l3prot=$2 circ_protocols_up res=1 circuit_read_field $id circ_protocols_up [ "$circ_protocols_up" != "${circ_protocols_up/ $l3prot/}" ] && res=0 return $res } # Determines the online state for a given circuit and a given layer-3 protocol. # If no layer-3 protocol is given, all configured layer-3 protocols have to be # up for the circuit to be online. # Note that a circuit is not considered to be online if it is in the middle of # a hangup operation. # Input: # $1 = circuit identifier # $2 = layer-3 protocol (may be empty) # Exit code: # 0 if the circuit/layer-3 protocol combination is online, 1 otherwise # Synchronization: # Takes a read lock on the circuit. circuit_is_online() { local id=$1 l3prot=$2 if _result=$(is_online_circuit_message_id=$id \ is_online_circuit_message_l3prot=$l3prot \ mom_unicast_message_and_receive_reply \ circd \ is_online_circuit_message \ is_online_circuit_reply_message \ $circd_timeout) then local _varnames=$(echo "$_result" | extract_variable_names) local $_varnames eval "$_result" case $message_type in ack_is_online_circuit_reply_message) return 0 ;; *) return 1 ;; esac else return 2 fi } # Determines if a non-deleted circuit exists. # # Input: # $1 = circuit identifier # Exit code: # 0 if circuit exists and is not in state "deleted", a nonzero value otherwise circuit_usable() { local id=$1 circuit_resolve_alias id [ -f $circuit_state_dir/all/$id -a ! -f $circuit_state_dir/deleted/$id ] } ############################################################# # --------------------- C L A S S E S --------------------- # ############################################################# # A class groups certain circuit properties, namely: # # - routed networks # # - firewall rules # # and gives them a unique name. Circuits can be associated # # to one or more classes, which extends their routed # # networks and firewall rules by those of the class. This # # makes it possible to define properties which are common # # to a set of circuits. For an example, think e.g. of a # # class called "Internet" which routes 0.0.0.0/0 (i.e. is # # a default gateway) and which contains firewall rules for # # forwarding service requests to internal servers, e.g. to # # a HTTP server. # # # # Circuits can be substituted by classes in all situations # # where multiple circuits cause no problems, e.g. in # # - dependency lists (CIRC_x_DEPS) # # - firewall rules (CIRC_x_PF_...) # # - address prefixes # # # # They are disallowed where a 1:n mapping cannot be handled # # properly, e.g. in # # - network interfaces (circ_dev) # # - almost all circuit_* APIs like circuit_read_field() # # # # Classes share the same namespace as circuit names, # # identifiers, and aliases. # ############################################################# # mapps classes to circuits classes_file=/var/run/circuits/classes # Determines if a circuit class exists. # # Input: # $1 = circuit class # Exit code: # 0 if circuit class exists, a nonzero value otherwise circuit_class_exists() { local class=$1 rc sync_lock_resource class_db circuit_add_to_class grep -q "^$class:" $classes_file rc=$? sync_unlock_resource class_db circuit_add_to_class return $rc } # Adds a circuit to a circuit class. # Input: # $1 = circuit identifier # $2 = circuit class circuit_add_to_class() { local id=$1 class=$2 circuit_resolve_alias id sync_lock_resource class_db circuit_add_to_class if grep -q "^$class:" $classes_file then sed -i "s/^\($class:.*\)$/\1 $id/" $classes_file else echo "$class: $id" >> $classes_file fi sync_unlock_resource class_db circuit_add_to_class } # Removes a circuit from a circuit class. # Input: # $1 = circuit identifier # $2 = circuit class circuit_remove_from_class() { local id=$1 class=$2 circuit_resolve_alias id sync_lock_resource class_db circuit_remove_from_class sed -i "s/^\($class:.*\) $id\( .*\)\?$/\1\2/" $classes_file sync_unlock_resource class_db circuit_remove_from_class } # Retrieves the circuits belonging to some circuit class. # Input: # $1 = circuit class # Output: # A list of circuits belonging to this class. This list may be empty. circuit_get_class_members() { local class=$1 sync_lock_resource class_db circuit_get_class_members sed -n "s/^$class: //p" $classes_file sync_unlock_resource class_db circuit_get_class_members } # Resolves a list of circuit aliases, names, or classes to circuit identifiers. # Aliases and names are tried first, then classes. Unknown entities are left # as-is. # Input: # $1... = circuit identifier, alias, name, or a circuit class # Output: # A list of circuit identifiers. This list may be empty only if the list # passed is empty or if empty groups are passed. # The list is sorted and does not contain duplicates. circuit_resolve() { local id for id do circuit_resolve_alias id if circuit_class_exists $id then circuit_get_class_members $id | sed 's/[[:space:]]\+/\n/g' else echo $id fi done | sort -unk 1.$((circuit_prefix_len+1)) } ############################################################# # --------------------- N E T W O R K --------------------- # ############################################################# # contains information which local address is used by which circuit local_addresses_file=/var/run/circuits/local-addresses # contains information which network prefix is provided by which circuit # this file is maintained by ip-up/down prefixes_file=/var/run/circuits/net-prefixes # contains information which network is routed by which circuit # this file is maintained by ip-up-down routed_nets_file=/var/run/circuits/routed-nets # contains information which route is maintained by which circuit # this file is maintained by ip-up-down routes_file=/var/run/circuits/routes # Translates an interface specification. This is either a fixed interface # (e.g. eth0 or ppp2) or a circuit name in braces (e.g. {LAN}). In the latter # case, the circuit's interface is returned, possibly recursively translated # by this function again. # # For convenience, this function also accepts prefixed addresses, i.e. # '{LAN}+::1/64', as circuit_resolve_address() does. In this case the circuit # name put into braces is extracted and translated as described above. # # Note that supplying a class name is _not_ supported. This is because a class # can potentially map to different interfaces, and a circuit can only have # exactly one interface. # # Input: # $1 = interface to be translated # Output: # resulting interface or the empty string if translation was unsuccessful # (e.g. if the reference structure is cyclic) # Exit code: # zero if the translation is successful and returns a non-empty string, # else a non-zero value circuit_get_interface() { local if=$1 shift case $if in {*}*) local circ=$(echo "$if" | sed -n 's/^{\([^}]*\)}.*$/\1/p') local c for c do # detect cycles [ "$c" = "$circ" ] && return 1 done local circ_dev circuit_read_field $circ circ_dev if [ -n "$circ_dev" ] then circuit_get_interface $circ_dev $* $circ else return 1 fi ;; *) echo "$if" ;; esac } # Registers a network prefix with a circuit. A circuit can be associated with # multiple prefixes (the layer-3 protocols of which may be equal). If a prefix # is registered with a limited lifetime, a timer is started. As soon as the # lifetime of the prefix is reached, the prefix is removed again using # /etc/ppp/prefix-down. # # If the prefix has already been registered with passed circuit and layer-3 # protocol, its lifetime is updated, i.e. the prefix timer is restarted. # # Input: # $1 = circuit identifier # $2 = layer-3 protocol (e.g. ipv4 or ipv6) # $3 = network prefix to register # $4 = valid lifetime in seconds # $5 = preferred lifetime in seconds circuit_register_prefix() { local id=$1 l3prot=$2 prefix=$3 valid_lft=$4 preferred_lft=$5 \ now=$(date +%s) circuit_resolve_alias id sync_lock_resource_for_writing prefixes_db circuit_register_prefix if grep -q "^$id $l3prot $prefix " $prefixes_file then circuit_deregister_prefix $id $l3prot $prefix fi echo "$id $l3prot $prefix $now $valid_lft $preferred_lft" >> $prefixes_file if [ "$valid_lft" != "forever" ] then local circ_dev prefix_id="$id-prefix-$prefix" script= case $l3prot in ipv4) script=/etc/ppp/prefix-down;; ipv6) script=/etc/ppp/prefixv6-down;; esac circuit_read_field $id circ_dev atd.sh add @$((now+valid_lft)) "$script \"$circ_dev\" \"$circ_id\" \"$prefix\"" "$prefix_id" fi sync_unlock_resource_for_writing prefixes_db circuit_register_prefix } # Find an atd job for a given prefix ID. # Input: # $1 = prefix ID to look up # Output: # The file name designating the corresponding atd job or nothing if not found. circuit_find_atd_job_for_prefix() { local prefix_id=$1 atd.sh list | while read -r line do # sets "comment" and "filename", among others eval $line if [ "$comment" = "$prefix_id" ] then echo "$filename" return 0 fi done return 1 } # Deregisters a network prefix from a circuit. If the prefix has not been # registered with passed circuit and layer-3 protocol, nothing happens. A # possibly existing outstanding timer is cancelled. # # Input: # $1 = circuit identifier # $2 = layer-3 protocol (e.g. ipv4 or ipv6) # $3 = network prefix to deregister circuit_deregister_prefix() { local id=$1 l3prot=$2 prefix=$3 local prefix_regex=$(echo "$prefix" | sed 's,/,\\/,g') circuit_resolve_alias id sync_lock_resource_for_writing prefixes_db circuit_deregister_prefix sed -i "/^$id $l3prot $prefix_regex /d" $prefixes_file local prefix_id="$id-prefix-$prefix" local filename=$(circuit_find_atd_job_for_prefix "$prefix_id") [ -n "$filename" ] && atd.sh remove "$filename" sync_unlock_resource_for_writing prefixes_db circuit_deregister_prefix } # Maps a circuit to all associated network prefixes. # Input: # $1 = circuit identifier or class # $2 = layer-3 protocol (e.g. ipv4 or ipv6, may be empty to match everything) # Output: # A list of all associated network prefixes. For each prefix, a line with # prefix, start, valid lifetime in seconds, and preferred lifetime in seconds # is returned, where "start" is an absolute time point (number of seconds # since epoch). The list is empty if the circuit is unknown or if no prefixes # are associated with it. circuit_get_prefixes() { local id=$1 l3prot=${2:-"[^ ]\\+"} circ sync_lock_resource_for_reading prefixes_db circuit_prefix_get for circ in $(circuit_resolve $id) do sed -n "s/^$circ $l3prot //p" $prefixes_file done sync_unlock_resource_for_reading prefixes_db circuit_prefix_get } # Maps a prefixed address to the prefix and associated information. # Input: # $1 = prefixed address # $2 = layer-3 protocol (e.g. ipv4 or ipv6, may be empty to match everything) # Output: # A line of the format: # # where # "id" identifies the circuit that registered the prefix in question # "start" is an absolute time point (number of seconds since epoch) when # the prefix started to be valid # "valid_lft" is the prefix's valid lifetime in seconds # "preferred_lft" is the prefix's preferred lifetime in seconds # The function returns the empty string if no such circuit could be found. circuit_get_prefix_by_address() { local addr=$1 l3prot=${2:-"[^ ]\\+"} sync_lock_resource_for_reading prefixes_db circuit_prefix_get sed -n "s/^\([^ ]\+\) $l3prot \(.*\)$/\1 \2/p" $prefixes_file | while read id prefix now valid_lft preferred_lft do prefixlen=${prefix##*/} addrprefix=$(netcalc network $addr/$prefixlen)/$prefixlen if [ "$addrprefix" = "$prefix" ] then echo "$id $prefix $now $valid_lft $preferred_lft" break fi done sync_unlock_resource_for_reading prefixes_db circuit_prefix_get } # Maps an address to the circuits it depends on. Circuit classes are resolved # immediately. # Input: # $1 = address # Output: # List of circuits the address depends on. May be empty. # Example: # circuit_get_address_dependees "{LAN}+::1:0:0:0:1/64" returns "LAN" if "LAN" # is a circuit, and the members of "LAN" if it is a circuit class. It returns # the empty string if "LAN" is neither a circuit nor a circuit class. # circuit_get_address_dependees "2001:db8:1234::/48" returns the empty string # as the address is not dependent on any circuits. circuit_get_address_dependees() { local address=$1 local id=$(echo "$address" | sed -n 's/^{\([^}]*\)}.*$/\1/p') [ -n "$id" ] && circuit_resolve $id } # Performs various address translations. The following syntax is supported: # # Input | Type | Output # ------------------------------------+--------------------------------------- # | literal | as is # ------------------------------------+--------------------------------------- # {} | literal | same as {.local} # ------------------------------------+--------------------------------------- # {.local} | projection | extracts the local addresses of the # | | circuit (with network mask); # | | must be a circuit id # ------------------------------------+--------------------------------------- # {.remote} | projection | extracts the routed networks of the # | | circuit (with network mask); # | | must be a circuit id # ------------------------------------+--------------------------------------- # {.prefix} | projection | extracts the prefixes associated with # | | (with network mask); # | | must be a circuit id # ------------------------------------+--------------------------------------- # #ipv4 | operator | evaluates ; # | | non-IPv4 resulting addresses are # | | removed # ------------------------------------+--------------------------------------- # #ipv6 | operator | evaluates ; # | | non-IPv6 resulting addresses are # | | removed # ------------------------------------+--------------------------------------- # #address | operator | evaluates ; # | | for each resulting address, the # | | network mask is stripped off # ------------------------------------+--------------------------------------- # #net | operator | evaluates ; # | | for each resulting address, the host # | | part is set to zero, and a network # | | mask is added if missing # ------------------------------------+--------------------------------------- # #host | operator | evaluates ; # | | for each resulting address, the # | | network part is set to zero, and a # | | network mask is added if missing # ------------------------------------+--------------------------------------- # + | operator | evaluates ; # | | each resulting address of # | | is combined with each resulting # | | address of ; this only succeeds # | | if the address families are compatible # | | and if the address parts do not # | | overlap # # All resulting addresses are finally filtered by layer-3 protocol. If no # layer-3 protocol is supplied, all IP addresses are returned (but non-IP # addresses are filtered out). # # If a {} literal is used without a projection, the context is used as # projection. If no context is passed, 'local' is used as default. # # In the following examples, there is a circuit 'LAN' which is assigned the # local addresses 192.168.11.4/24 and 2001:db8:11::4/64, a circuit 'WAN' which # is assigned the local addresses 192.0.2.2/32 and fe80::2/128, and a circuit # 'DHCP', which is assigned the prefixes 172.16.0.0/14 and 2001:db8:12::/48. # Over the circuit 'WAN', both the networks '0.0.0.0/0' and '::/0' are routed. # # Input | Output # -----------------------+---------------------------------------------------- # 1.2.3.4 | 1.2.3.4 # 1.2.3.4 ipv4 | 1.2.3.4 # 1.2.3.4 ipv6 | # 1.2.3.4/24 | 1.2.3.4/24 # 1.2.3.4/24 ipv4 | 1.2.3.4/24 # 1.2.3.4/24 ipv6 | # ::1234 | ::1234 # ::1234 ipv4 | # ::1234 ipv6 | ::1234 # ::1234/64 | ::1234/64 # ::1234/64 ipv4 | # ::1234/64 ipv6 | ::1234/64 # {LAN} | 192.168.11.4/24 2001:db8:11::4/64 # {LAN}#ipv4 | 192.168.11.4/24 # {LAN}#ipv6 | 2001:db8:11::4/64 # {LAN.local} | 192.168.11.4/24 2001:db8:11::4/64 # {LAN.local}#ipv4 | 192.168.11.4/24 # {LAN.local}#ipv6 | 2001:db8:11::4/64 # {LAN.remote} | # {LAN.prefix} | # {WAN} | 192.0.2.2/32 fe80::2/128 # {WAN.local} | 192.0.2.2/32 fe80::2/128 # {WAN.remote} | 0.0.0.0/0 ::/0 # {WAN.remote}#ipv4 | 0.0.0.0/0 # {WAN.remote}#ipv6 | ::/0 # {WAN.prefix} | # {DHCP} | # {DHCP.local} | # {DHCP.remote} | # {DHCP.prefix} | 172.16.0.0/14 2001:db8:12::/48 # {DHCP.prefix}#ipv4 | 172.16.0.0/14 # {DHCP.prefix}#ipv6 | 2001:db8:12::/48 # {LAN}#address | 192.168.11.4 2001:db8:11::4 # {LAN}#address#ipv4 | 192.168.11.4 # {LAN}#address#ipv6 | 2001:db8:11::4 # {LAN}#ipv4#address | 192.168.11.4 # {LAN}#ipv6#address | 2001:db8:11::4 # {LAN.local}#address | 192.168.11.4 2001:db8:11::4 # {WAN}#address | 192.0.2.2 fe80::2 # {WAN.local}#address | 192.0.2.2 fe80::2 # {WAN.remote}#address | 0.0.0.0 :: # {DHCP.prefix}#address | 172.16.0.0 2001:db8:12:: # {LAN}#net | 192.168.11.0/24 2001:db8:11::/64 # {LAN}#net#ipv4 | 192.168.11.0/24 # {LAN}#net#ipv6 | 2001:db8:11::/64 # {LAN}#ipv4#net | 192.168.11.0/24 # {LAN}#ipv6#net | 2001:db8:11::/64 # {LAN.local}#net | 192.168.11.0/24 2001:db8:11::/64 # {WAN}#net | 192.0.2.2/32 fe80::2/128 # {WAN.local}#net | 192.0.2.2/32 fe80::2/128 # {WAN.remote}#net | 0.0.0.0/0 ::/0 # {DHCP.prefix}#net | 172.16.0.0/14 2001:db8:12::/48 # {LAN}#host | 0.0.0.4/24 ::4/64 # {LAN}#host#ipv4 | 0.0.0.4/24 # {LAN}#host#ipv6 | ::4/64 # {LAN}#ipv4#host | 0.0.0.4/24 # {LAN}#ipv6#host | ::4/64 # {LAN.local}#host | 0.0.0.4/24 ::4/64 # {WAN}#host | 0.0.0.0/32 ::/128 # {WAN.local}#host | 0.0.0.0/32 ::/128 # {WAN.remote}#host | 0.0.0.0/0 ::/0 # {DHCP.prefix}#host | 0.0.0.0/14 ::/48 circuit_resolve_address() { local address=$1 l3prot=$2 context=${3:-local} result case $address in # operator: combine prefix and (tail) address *+*) local prefix suffix for prefix in $(circuit_resolve_address "${address%+*}" "$l3prot" prefix) do for suffix in $(circuit_resolve_address ${address##*+} "$l3prot" $context) do case $suffix in '') result="$result $prefix" ;; */*) local prefixlen=${prefix##*/} local suffixlen=${suffix##*/} if [ $prefixlen -le $suffixlen ] then local combined=$(netcalc combine $prefix ${suffix%/*} 2>/dev/null) if [ -n "$combined" ] then result="$result ${combined%/*}/$suffixlen" #else: prefix and suffix overlap fi #else: prefix longer than network part of address, ignore it fi ;; *) local combined=$(netcalc combine $prefix $suffix 2>/dev/null) result="$result ${combined%/*}" ;; esac done done ;; # operator: evaluate in IPv4 context *#ipv4) result=$(circuit_resolve_address "${address%#*}" ipv4 $context) ;; # operator: evaluate in IPv6 context *#ipv6) result=$(circuit_resolve_address "${address%#*}" ipv6 $context) ;; # operator: strip off network mask *#address) local addr for addr in $(circuit_resolve_address "${address%#*}" "$l3prot" $context) do addr=$(netcalc canonicalize $addr 2>/dev/null) result="$result ${addr%/*}" done ;; # operator: add network mask if missing, set host part to zero *#net) local addr for addr in $(circuit_resolve_address "${address%#*}" "$l3prot" $context) do addr=$(netcalc canonicalize "$addr" 2>/dev/null) result="$result $(netcalc network $addr 2>/dev/null)/${addr##*/}" done ;; # operator: add network mask if missing, set network part to zero *#host) local addr for addr in $(circuit_resolve_address "${address%#*}" "$l3prot" $context) do addr=$(netcalc canonicalize "$addr" 2>/dev/null) result="$result $(netcalc host $addr 2>/dev/null)/${addr##*/}" done ;; # user-defined operator *#*) local prefix=${address%#*} op=${address##*#} func for func in $circuit_resolve_address_operators do result=$($func "$prefix" "$op" "$l3prot" $context) && break done ;; {*}) set -- $(echo "$address" | sed -n 's/^{\([^.]*\)\(.*\)}$/\1 \2/p') local id=$1 tail=${2:-".${context}"} [ -n "$id" ] || return 1 case $tail in # circuit projection: prefixes .prefix) result=$(circuit_get_prefixes $id "$l3prot" | cut -d' ' -f 1) ;; # circuit projection: local addresses (with network mask) .local) result=$(circuit_get_local_addresses $id "$l3prot") ;; # circuit projection: routed networks .remote) result=$(circuit_get_routed_networks $id "$l3prot") ;; # user-defined circuit projection *) local func for func in $circuit_resolve_address_projections do result=$($func $id "$tail" "$l3prot" $context) && break done ;; esac ;; *) # call user-defined translation to parse and interpret address; # if no translation succeeds, the result is left as is result=$address local func for func in $circuit_resolve_address_translations do result=$($func "$address" "$l3prot" $context) && break result=$address done if [ "$result" != "$address" ] then # call ourselves recursively with translated input; this is useful # if some user-defined translation generates something that is not # a simple address but needs to be translated a second time result=$(list_foreach "circuit_resolve_address \"\$1\" \"\$l3prot\" \$context" $result) fi ;; esac for address in $result do case $l3prot in '') netcalc isip "${address%/*}" 2>/dev/null && echo "$address" ;; *) netcalc is${l3prot} "${address%/*}" 2>/dev/null && echo "$address" ;; esac done } circuit_resolve_address_operators= circuit_resolve_address_projections= circuit_resolve_address_translations= # Registers a user-defined operator for circuit_resolve_address(). # # Input: # $1 = name of operator function, which has the following interface: # Input: # $1 = address prefix without operator # $2 = operator to handle # $3 = layer-3 protocol (may be empty to match everything) # $4 = context (never empty) # Output: # resulting address or the empty string if not successful # Exit code: # 0 if operator succeeded # !=0 if operator failed and the next operator should be tried circuit_register_resolve_address_operator() { local oeprator=$1 circuit_resolve_address_operators="$circuit_resolve_address_operators $oeprator" } # Registers a user-defined circuit projection for circuit_resolve_address(). # # Input: # $1 = name of projection function, which has the following interface: # Input: # $1 = identifier within braces # $2 = projection to translate (e.g. ".local") # $3 = layer-3 protocol (may be empty to match everything) # $4 = context (never empty) # Output: # resulting address or the empty string if not successful # Exit code: # 0 if projection succeeded # !=0 if projection failed and the next projection should be tried circuit_register_resolve_address_projection() { local projection=$1 circuit_resolve_address_projections="$circuit_resolve_address_projections $projection" } # Registers a user-defined translation for circuit_resolve_address(). # # Input: # $1 = name of translation function, which has the following interface: # Input: # $1 = address to translate # $2 = layer-3 protocol (may be empty to match everything) # $3 = context (never empty) # Output: # resulting address or the empty string if not successful # Exit code: # 0 if translation succeeded # !=0 if translation failed and the next translation should be tried circuit_register_resolve_address_translation() { local translation=$1 circuit_resolve_address_translations="$circuit_resolve_address_translations $translation" } # include information about circuit_resolve_address extensions for f in /etc/rc.d/cra-ext.* do [ -f $f ] && . $f done # Associates a local address with a circuit. # Input: # $1 = circuit identifier # $2 = layer-3 protocol (e.g. ipv4 or ipv6) # $3 = local address circuit_register_local_address() { local id=$1 l3prot=$2 addr=$3 circuit_resolve_alias id sync_lock_resource_for_writing local_addresses_db circuit_register_local_address echo "$id $l3prot $addr" >> $local_addresses_file sync_unlock_resource_for_writing local_addresses_db circuit_register_local_address } # Disassociates a local address from a circuit. # Input: # $1 = circuit identifier # $2 = layer-3 protocol (e.g. ipv4 or ipv6) # $3 = local address circuit_deregister_local_address() { local id=$1 l3prot=$2 addr=$(echo "$3" | sed 's,/,\\/,g') circuit_resolve_alias id sync_lock_resource_for_writing local_addresses_db circuit_deregister_local_address sed -i "/^$id $l3prot $addr$/d" $local_addresses_file sync_unlock_resource_for_writing local_addresses_db circuit_deregister_local_address } # Maps a circuit to all local addresses associated with it. # Input: # $1 = circuit identifier or class # $2 = layer-3 protocol (e.g. ipv4 or ipv6, may be empty to match everything) # Output: # A list of all associated local addresses. It is empty if the circuit is # unknown or if no local addresses are associated with it. circuit_get_local_addresses() { local id=$1 l3prot=${2:-[^ ]\\+} sync_lock_resource_for_reading local_addresses_db circuit_get_local_addresses for circ in $(circuit_resolve $id) do sed -n "s/^$circ $l3prot //p" $local_addresses_file done sync_unlock_resource_for_reading local_addresses_db circuit_get_local_addresses } # Returns all local addresses associated with the router. # Input: # $1 = layer-3 protocol (e.g. ipv4 or ipv6, may be empty to match everything) # Output: # A list of all associated local addresses. circuit_get_all_local_addresses() { local l3prot=${1:-[^ ]\\+} sync_lock_resource_for_reading local_addresses_db circuit_get_local_addresses sed -n "s/^[^ ]\+ $l3prot //p" $local_addresses_file sync_unlock_resource_for_reading local_addresses_db circuit_get_local_addresses } # Associates a routed network with a circuit. # Input: # $1 = circuit identifier # $2 = layer-3 protocol (e.g. ipv4 or ipv6) # $3 = routed network circuit_register_routed_network() { local id=$1 l3prot=$2 net=$3 circuit_resolve_alias id sync_lock_resource_for_writing routed_nets_db circuit_register_routed_network echo "$id $l3prot $net" >> $routed_nets_file sync_unlock_resource_for_writing routed_nets_db circuit_register_routed_network } # Disassociates a routed network from a circuit. # Input: # $1 = circuit identifier # $2 = layer-3 protocol (e.g. ipv4 or ipv6) # $3 = routed network circuit_deregister_routed_network() { local id=$1 l3prot=$2 net=$(echo "$3" | sed 's,/,\\/,g') circuit_resolve_alias id sync_lock_resource_for_writing routed_nets_db circuit_deregister_routed_network sed -i "/^$id $l3prot $net$/d" $routed_nets_file sync_unlock_resource_for_writing routed_nets_db circuit_deregister_routed_network } # Maps a circuit to all networks routed through it. # Input: # $1 = circuit identifier or class # $2 = layer-3 protocol (e.g. ipv4 or ipv6, may be empty to match everything) # Output: # A list of all associated routed networks. It is empty if the circuit is # unknown or if no networks are routed through it. circuit_get_routed_networks() { local id=$1 l3prot=${2:-[^ ]\\+} sync_lock_resource_for_reading routed_nets_db circuit_get_routed_networks for circ in $(circuit_resolve $id) do sed -n "s/^$circ $l3prot //p" $routed_nets_file done sync_unlock_resource_for_reading routed_nets_db circuit_get_routed_networks } # Maps a routed network to all circuits which have installed a route to this # network. # Input: # $1 = routed network # $2 = layer-3 protocol (e.g. ipv4 or ipv6) # Output: # A list of all circuits which have been associated with passed network. circuit_find_by_routed_network() { local net=$(echo "$1" | sed 's,/,\\/,g') l3prot=$2 sync_lock_resource_for_reading routed_nets_db circuit_find_by_routed_network sed -n "s/^\([^ ]\+\) $l3prot $net$/\1/p" $routed_nets_file sync_unlock_resource_for_reading routed_nets_db circuit_find_by_routed_network } # Adds a route for a circuit and remembers it in $routes_file. If a route for # the same network (and with the same metric) but using a different gateway or # device already exists, the new route is added anyway and overrides the old # route. # Input: # $1 = circuit identifier # $2 = protocol (4 or 6) # $3 = route circuit_add_route() { local id=$1 prot=$2 route=$3 circuit_resolve_alias id sync_lock_resource routes_db circuit_add_route ip -$prot route append $route echo "$id $2 $3" >> $routes_file sync_unlock_resource routes_db circuit_add_route } # Removes all routes for a circuit and a given protocol. # Input: # $1 = circuit identifier # $2 = protocol (4 or 6) circuit_remove_routes() { local id=$1 circuit_resolve_alias id local dummy prot route sync_lock_resource routes_db circuit_remove_routes grep "^$id $2 " $routes_file | while read dummy prot route do ip -$prot route del $route 2>/dev/null done sed -i "/^$id $2 /d" $routes_file sync_unlock_resource routes_db circuit_remove_routes } ############################################################# # --------------------- D A E M O N S --------------------- # ############################################################# # contains information which daemon is associated with which circuit daemons_file=/var/run/circuits/daemons # Terminates the daemon controlling a circuit if not already done so. # Input: # $1 = circuit identifier # $2 = (optional) signal name, e.g. HUP. If nothing is passed, TERM is used. circuit_daemon_terminate() { local id=$1 signal=$2 stop_daemon_circuit_message_id=$id \ stop_daemon_circuit_message_signal=$signal \ mom_unicast_message \ circd \ stop_daemon_circuit_message >/dev/null } # Circuit daemon wrapper. Starts a daemon, waits for it to exit, sets the # correct state after termination and takes care of the PID file. Note that # the script assumes that the daemon does not fork itself into the background! # # Input: # $1 = circuit identifier # $2 = name of the daemon # $3 = name of cleanup function to be called when daemon exits (may be empty); # please be aware that the function is called in the context of the # daemon wrapper, not in the context of circd # $4... = daemon arguments # Exit code: # The exit code of the daemon. circuit_daemon_wrapper() { local id=$1 daemon=$2 cleanup_func=$3 args shift 3 pack_args args "$@" start_daemon_circuit_message_id=$id \ start_daemon_circuit_message_daemon=$daemon \ start_daemon_circuit_message_daemon_args=$args \ start_daemon_circuit_message_cleanup_func=$cleanup_func \ mom_unicast_message \ circd \ start_daemon_circuit_message >/dev/null } # Returns the fdpass path to be used for a certain circuit. # # Input: # $1 = circuit identifier circuit_build_fdpass_path() { local id=$1 echo "/var/run/fd.$id" } circuit_api='yes' fi # $circuit_api != yes