#!/bin/bash ############################################################################### # # # IPFire.org - A linux based firewall # # Copyright (C) 2012 IPFire Network Development Team # # # # This program is free software: you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # # the Free Software Foundation, either version 3 of the License, or # # (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU General Public License for more details. # # # # You should have received a copy of the GNU General Public License # # along with this program. If not, see . # # # ############################################################################### # # Functions for static routing. # function route_init() { # Apply configured static routes. route_apply } init_register route_init function route_add() { local ${NETWORK_CONFIG_ROUTES_PARAMS} while [ $# -gt 0 ]; do case "${1}" in --gateway=*) gateway=$(cli_get_val ${1}) ;; --unreachable) unreachable="true" ;; --prohibit) prohibit="true" ;; --blackhole) blackhole="true" ;; --mtu=*) mtu=$(cli_get_val ${1}) ;; *) network=${1} ;; esac shift done assert isset network if ! ip_is_valid ${network}; then error "The given network is invalid: ${network}" return ${EXIT_ERROR} fi if route_find_duplicate ${network}; then error "A route to ${network} does already exist." return ${EXIT_ERROR} fi # Check if gateway and unreachable are both enabled. if isset gateway; then if enabled unreachable; then error "You cannot use both, --gateway=${gateway} and --unreachable at the same time." return ${EXIT_ERROR} fi if enabled prohibit; then error "You cannot use both, --gateway=${gateway} and --prohibit at the same time." return ${EXIT_ERROR} fi if enabled blackhole; then error "You cannot use both, --gateway=${gateway} and --blackhole at the same time." return ${EXIT_ERROR} fi # Check if network and gateway IP protocol version match. if ! ip_is_valid ${gateway}; then error "--gateway= is not a valid IP address." return ${EXIT_ERROR} fi local network_proto=$(ip_detect_protocol ${network}) local gateway_proto=$(ip_detect_protocol ${gateway}) if [ "${network_proto}" != "${gateway_proto}" ]; then error "The IP protocol version of the given network and gateway did not match." return ${EXIT_ERROR} fi else local counter=$(list_count true ${unreachable} ${prohibit} ${blackhole}) if [ ${counter} -gt 1 ]; then error "You can only use one of --unreachable, --prohibit or --blackhole." return ${EXIT_ERROR} fi fi if isset mtu && ! isinteger mtu; then error "MTU must be an integer number: ${mtu}" return ${EXIT_ERROR} fi local line list_append line "network=\"${network}\"" # Add gateway to configuration entry when it is set. if isset gateway; then list_append line "gateway=\"${gateway}\"" fi # Add unreachable to configuration entry when it is set. local arg for arg in unreachable prohibit blackhole; do if enabled ${arg}; then list_append line "${arg}=\"true\"" break fi done # Add MTU (if set). if isset mtu; then list_append line "mtu=\"${mtu}\"" fi # Write line to file. print "${line}" >> ${NETWORK_CONFIG_ROUTES} log INFO "New route to network '${network}' has been added." return ${EXIT_OK} } function route_remove() { local _network=${1} assert isset _network local found="false" local ${NETWORK_CONFIG_ROUTES_PARAMS} local line while read line; do route_parse_line ${line} [ $? -eq ${EXIT_OK} ] || continue # Skip the rule, we want to delete. if [ "${network}" = "${_network}" ]; then found="true" continue fi print "${line}" done < ${NETWORK_CONFIG_ROUTES} > ${NETWORK_CONFIG_ROUTES}.tmp mv ${NETWORK_CONFIG_ROUTES}{.tmp,} if enabled found; then log INFO "Route to network '${_network}' has been removed." else error "No route to network '${_network}' was found." return ${EXIT_ERROR} fi return ${EXIT_OK} } function route_list() { local protocol while [ $# -gt 0 ]; do case "${1}" in --protocol=*) protocol=$(cli_get_val ${1}) ;; *) warning "Unrecognized argument: ${1}" ;; esac shift done if [ ! -r "${NETWORK_CONFIG_ROUTES}" ]; then print "No static routes defined." return ${EXIT_OK} fi local format="%-40s %-20s %-4s" print "${format}" "NETWORK/HOST" "GATEWAY" "MTU" local ${NETWORK_CONFIG_ROUTES_PARAMS} local line while read line; do route_parse_line ${line} [ $? -eq ${EXIT_OK} ] || continue local arg for arg in unreachable prohibit blackhole; do if enabled ${arg}; then gateway="<${arg}>" break fi done # Filter all entries with a wrong protocol. if isset protocol; then local proto=$(ip_detect_protocol ${network}) [ "${protocol}" = "${proto}" ] || continue fi # Print something when no MTU was set. if ! isset mtu; then mtu="-" fi print "${format}" "${network}" "${gateway}" "${mtu}" done < ${NETWORK_CONFIG_ROUTES} } function route_find_duplicate() { local _network=${1} [ -r "${NETWORK_CONFIG_ROUTES}" ] || return ${EXIT_FALSE} local ${NETWORK_CONFIG_ROUTES_PARAMS} local line while read line; do route_parse_line ${line} [ $? -eq ${EXIT_OK} ] || continue # Check if the network is already in use. [ "${network}" = "${_network}" ] && return ${EXIT_TRUE} done < ${NETWORK_CONFIG_ROUTES} return ${EXIT_FALSE} } function route_parse_line() { local arg # Reset all possible settings. for arg in ${NETWORK_CONFIG_ROUTES_PARAMS}; do printf -v ${arg} "%s" "" done while read arg; do case "${arg}" in network=*) network=$(cli_get_val ${arg}) ;; gateway=*) gateway=$(cli_get_val ${arg}) ;; unreachable=*) unreachable=$(cli_get_val ${arg}) ;; prohibit=*) prohibit=$(cli_get_val ${arg}) ;; blackhole=*) blackhole=$(cli_get_val ${arg}) ;; mtu=*) mtu=$(cli_get_val ${arg}) ;; esac done <<< "$(args $@)" ### Check if all values are correctly set. # network must be set. isset network || return ${EXIT_ERROR} # network must be a valid. ip_is_network ${network} || return ${EXIT_ERROR} # Check gateway settings. if isset gateway; then # When gateway is set, unreachable cannot be set. isset unreachable && return ${EXIT_ERROR} # Must be a valid IP address. ip_is_valid ${gateway} || return ${EXIT_ERROR} else # Check if exactly one of unreachable, prohibit or blackhole is set. local counter=$(list_count true ${unreachable} ${prohibit} ${blackhole}) [ ${counter} -eq 1 ] || return ${EXIT_ERROR} fi # mtu must be an integer number. if isset mtu; then isinteger mtu || return ${EXIT_ERROR} fi return ${EXIT_OK} } function route_apply() { local table="static" local type log INFO "Applying static routes..." # Flush the routing table. route_table_flush ${table} local ${NETWORK_CONFIG_ROUTES_PARAMS} local line while read line; do route_parse_line ${line} [ $? -eq ${EXIT_OK} ] || continue type="unicast" local arg for arg in unreachable prohibit blackhole; do if enabled ${arg}; then type="${arg}" break fi done # Add the route. route_entry_add ${network} --table="static" --proto="static" \ --type="${type}" --gateway="${gateway}" --mtu="${mtu}" local ret=$? if [ ${ret} -ne ${EXIT_OK} ]; then log WARNING "Could not set route '${network}'." fi done < ${NETWORK_CONFIG_ROUTES} # Create a lookup rule for the static routing table. route_rule_add --lookup="static" --priority=1000 } function route_entry_add() { local gateway local network local proto local table local type="unicast" local mtu local command while [ $# -gt 0 ]; do case "${1}" in --gateway=*) gateway=$(cli_get_val ${1}) ;; --table=*) table=$(cli_get_val ${1}) ;; --type=*) type=$(cli_get_val ${1}) ;; --proto=*) proto=$(cli_get_val ${1}) ;; --mtu=*) mtu=$(cli_get_val ${1}) ;; *) if isset network; then warning "Unrecognized argument: ${1}" else network=${1} fi ;; esac shift done # Validate input. assert isoneof type unicast broadcast unreachable prohibit blackhole assert ip_is_network ${network} if isset mtu; then assert isinteger mtu fi # Detect the protocol of the given network. local protocol=$(ip_detect_protocol ${network}) case "${protocol}" in ipv6) command="ip -6 route add" ;; ipv4) command="ip route add" ;; esac assert isset command # Add type. list_append command "${type}" # Add network/prefix. list_append command "${network}" if [ "${type}" = "unicast" ]; then assert isset gateway assert ip_is_valid ${gateway} list_append command "via ${gateway}" fi # Add table (if any). if isset table; then # Create routing table, if it does not exist, yet. route_table_create ${table} list_append command "table ${table}" fi # Add proto. if isset proto; then list_append command "proto ${proto}" fi # Add MTU. if isset mtu; then list_append command "mtu ${mtu}" fi cmd_quiet "${command}" } function route_table_create() { local table=${1} assert isset table if route_table_exists ${table}; then return ${EXIT_OK} fi # Get the next free id. local id=$(_route_table_next_id) assert isset id # Write everything to file. print "%d\t%s" "${id}" "${table}" >> /etc/iproute2/rt_tables log DEBUG "Created routing table '${table}'." return ${EXIT_OK} } function _route_table_next_id() { # The Linux kernel is able to manage 255 routing tables (1-255). # This function returns the next free id, starting from 255. local next_id for next_id in {255..1}; do if ! route_table_exists --id="${next_id}"; then print "${next_id}" return ${EXIT_OK} fi done return ${EXIT_FALSE} } function route_table_flush() { local protocol local table while [ $# -gt 0 ]; do case "${1}" in --protocol=*) protocol=$(cli_get_val ${1}) ;; *) table="${1}" ;; esac shift done # If the table does not exists, there is nothing to # flush. route_table_exists ${table} || return ${EXIT_OK} local command local proto for proto in ${IP_SUPPORTED_PROTOCOLS}; do # Skip unwanted protocols. if isset protocol; then [ "${protocol}" = "${proto}" ] || continue fi command="" case "${proto}" in ipv6) command="ip -6 route flush" ;; ipv4) command="ip route flush" ;; esac assert isset command list_append command "table ${table}" # Execute command. cmd "${command}" done return ${EXIT_OK} } function route_table_exists() { local _id _table while [ $# -gt 0 ]; do case "${1}" in --id=*) _id=$(cli_get_val ${1}) ;; *) _table=${1} break ;; esac shift done local id table while read -r id table; do # Skip all comments. [ "${id:0:1}" = "#" ] && continue if [ "${_table}" = "${table}" ] || [ "${_id}" = "${id}" ]; then # Found a match. return ${EXIT_TRUE} fi done < /etc/iproute2/rt_tables return ${EXIT_FALSE} } function route_rule_add() { local priority local protocols=${IP_SUPPORTED_PROTOCOLS} local lookup while [ $# -gt 0 ]; do case "${1}" in --lookup=*) lookup=$(cli_get_val ${1}) ;; --priority=*) priority=$(cli_get_val ${1}) ;; --protocol=*) protocols=$(cli_get_val ${1}) assert isoneof protocols ${IP_SUPPORTED_PROTOCOLS} ;; *) warning "Unhandled argument: ${1}" ;; esac shift done local command options if isset lookup; then route_table_create ${lookup} list_append options "lookup ${lookup}" fi if isset priority; then assert isinteger priority list_append options "prio ${priority}" fi local proto for proto in ${protocols}; do command= case "${proto}" in ipv6) command="ip -6 rule add ${options}" ;; ipv4) command="ip rule add ${options}" ;; esac assert isset command # Skip, if the rule does already exist. route_rule_exists \ --protocol=${proto} \ --lookup=${lookup} \ --priority=${priority} \ && continue cmd "${command}" done } function route_rule_exists() { local from local lookup local proto local prio while [ $# -gt 0 ]; do case "${1}" in --from=*) from=$(cli_get_val ${1}) ;; --lookup=*) lookup=$(cli_get_val ${1}) ;; --priority=*) prio=$(cli_get_val ${1}) ;; --protocol=*) proto=$(cli_get_val ${1}) ;; *) warning "Unrecognized argument: ${1}" ;; esac shift done local command case "${proto}" in ipv6) command="ip -6 rule show" ;; ipv4) command="ip rule show" ;; esac assert isset command local _lookup _from _prio local line while read -r line; do _route_rule_exists_parse ${line} if isset from; then [ "${from}" = "${_from}" ] || continue fi if isset prio; then [ "${prio}" = "${_prio}" ] || continue fi if isset lookup; then [ "${lookup}" = "${_lookup}" ] || continue fi return ${EXIT_TRUE} done <<< "$(${command})" return ${EXIT_FALSE} } function _route_rule_exists_parse() { # Reset all variables. _lookup= _from= _prio= while [ $# -gt 0 ]; do case "${1}" in lookup) _lookup=${2} shift 2 ;; from) _from=${2} shift 2 ;; *:) _prio=${1//:/} shift ;; *) # Skip unknown arguments. shift ;; esac done }