#!/bin/bash ############################################################################### # # # IPFire.org - A linux based firewall # # Copyright (C) 2010 Michael Tremer & Christian Schmidt # # # # 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 . # # # ############################################################################### device_list() { local devices # Add all interfaces list_append devices $(devices_get_all) # Add all PHYs list_append devices $(phy_list) # Add all serial devices list_append devices $(serial_list) # Return a sorted result list_sort ${devices} } # Check if the device exists device_exists() { local device=${1} # If device name was not found, exit. [ -n "${device}" ] || return ${EXIT_ERROR} # Check for a normal network device. [ -d "${SYS_CLASS_NET}/${device}" ] && return ${EXIT_OK} # If the check above did not find a result, # we check for PHYs. phy_exists "${device}" && return ${EXIT_OK} # If the check above did not find a result, # we check for serial devices. serial_exists ${device} } device_matches_pattern() { local device="${1}" assert isset device local pattern="${2}" assert isset pattern pattern="^${pattern//N/[[:digit:]]+}$" [[ ${device} =~ ${pattern} ]] \ && return ${EXIT_TRUE} || return ${EXIT_FALSE} } device_delete() { local device=${1} assert isset device # Nothing to do, it device does not exist. device_exists ${device} || return ${EXIT_OK} # Delete the device. cmd_quiet ip link delete ${device} local ret=$? if [ ${ret} -ne ${EXIT_OK} ]; then log ERROR "device: Could not delete device '${device}': ${ret}" return ${EXIT_ERROR} fi return ${ret} } device_has_flag() { local device=${1} local flag=${2} local flags=$(__device_get_file ${device} flags) if [[ "$(( ${flags} & ${flag} ))" -eq 0 ]]; then return ${EXIT_FALSE} else return ${EXIT_TRUE} fi } # Check if the device is up device_is_up() { local device=${1} device_exists ${device} || return ${EXIT_ERROR} device_has_flag ${device} 0x1 } device_ifindex_to_name() { local idx=${1} assert isset idx local device device_idx for device in $(list_directory "${SYS_CLASS_NET}"); do device_idx=$(device_get_ifindex ${device}) if [ "${device_idx}" = "${idx}" ]; then print "${device}" return ${EXIT_OK} fi done return ${EXIT_ERROR} } device_get_ifindex() { local device=${1} assert isset device local path="${SYS_CLASS_NET}/${1}/ifindex" # Check if file can be read. [ -r "${path}" ] || return ${EXIT_ERROR} print "$(<${path})" } # Check if the device is a batman-adv bridge device_is_batman_adv() { [ -d "${SYS_CLASS_NET}/${1}/mesh" ] } # Check if the device is a batman-adv slave port device_is_batman_adv_slave() { local device="${1}" if [ -d "${SYS_CLASS_NET}/${device}/batman_adv" ]; then local status="$(<${SYS_CLASS_NET}/${device}/batman_adv/iface_status)" case "${status}" in "active") return ${EXIT_TRUE} ;; *) return ${EXIT_FALSE} ;; esac fi return ${EXIT_FALSE} } # Check if the device is a bonding device device_is_bonding() { [ -d "/sys/class/net/${1}/bonding" ] } # Check if the device bonded in a bonding device device_is_bonded() { local device=${1} [ -d "${SYS_CLASS_NET}/${device}/bonding_slave" ] } # Check if the device is a bridge device_is_bridge() { [ -d "/sys/class/net/${1}/bridge" ] } device_is_bridge_attached() { local device=${1} [ -d "${SYS_CLASS_NET}/${device}/brport" ] } device_is_wireless_monitor() { local device="${1}" assert isset device device_is_wireless "${device}" && \ device_matches_pattern "${device}" "${PORT_PATTERN_WIRELESS_MONITOR}" } device_is_wireless_adhoc() { local device="${1}" assert isset device device_is_wireless "${device}" && \ device_matches_pattern "${device}" "${PORT_PATTERN_WIRELESS_ADHOC}" } device_get_bridge() { local device=${1} assert isset device # Check if device is attached to a bridge. device_is_bridge_attached ${device} || return ${EXIT_ERROR} local ifindex_path="${SYS_CLASS_NET}/${device}/brport/bridge/ifindex" [ -r "${ifindex_path}" ] || return ${EXIT_ERROR} local ifindex=$(<${ifindex_path}) assert isset ifindex device_ifindex_to_name ${ifindex} } # Check if the device is a vlan device device_is_vlan() { local device=${1} assert isset device [ -e "${PROC_NET_VLAN}/${device}" ] } # Check if the device has vlan devices device_has_vlans() { local device=${1} assert isset device if device_is_vlan ${device}; then return ${EXIT_FALSE} fi local vlans=$(device_get_vlans ${device}) [ -n "${vlans}" ] && return ${EXIT_OK} || return ${EXIT_ERROR} } device_get_vlans() { local device=${1} assert isset device # If no 8021q module has been loaded into the kernel, # we cannot do anything. [ -r "${PROC_NET_VLAN_CONFIG}" ] || return ${EXIT_OK} local dev spacer1 id spacer2 parent while read dev spacer1 id spacer2 parent; do [ "${parent}" = "${device}" ] || continue print "${dev}" done < ${PROC_NET_VLAN_CONFIG} } # Check if the device is a ppp device device_is_ppp() { local device=${1} local type=$(__device_get_file ${device} type) [ "${type}" = "512" ] && return ${EXIT_OK} || return ${EXIT_ERROR} } # Check if the device is a pointopoint device. device_is_ptp() { local device=${1} device_has_flag ${device} 0x10 } # Check if the device is a loopback device device_is_loopback() { local device=${1} [ "${device}" = "lo" ] } # Check if the device is a dummy device # This is the worst possible check, but all I could come up with device_is_dummy() { local device="${1}" [[ ${device} =~ ^dummy[0-9]+$ ]] } device_is_ipsec() { local device="${1}" [[ ${device} =~ ^ipsec\- ]] } # Check if the device is a wireless device device_is_wireless() { local device=${1} [ -d "${SYS_CLASS_NET}/${device}/phy80211" ] } device_is_vti() { local device=${1} local type=$(__device_get_file ${device} type) [ "${type}" = "768" ] && return ${EXIT_OK} || return ${EXIT_ERROR} } device_get_phy() { local device="${1}" if device_is_wireless "${device}"; then print "$(<${SYS_CLASS_NET}/${device}/phy80211/name)" return ${EXIT_OK} fi return ${EXIT_ERROR} } device_is_phy() { phy_exists "$@" } device_is_serial() { serial_exists "$@" } # Returns true if a device is a tun device device_is_tun() { local device="${1}" [ -e "${SYS_CLASS_NET}/${device}/tun_flags" ] } # Check if the device is a physical network interface device_is_ethernet() { local device=${1} device_is_ethernet_compatible "${device}" || \ return ${EXIT_ERROR} device_is_loopback ${device} && \ return ${EXIT_ERROR} device_is_bonding ${device} && \ return ${EXIT_ERROR} device_is_bridge ${device} && \ return ${EXIT_ERROR} device_is_ppp ${device} && \ return ${EXIT_ERROR} device_is_vlan ${device} && \ return ${EXIT_ERROR} device_is_dummy ${device} && \ return ${EXIT_ERROR} device_is_tun ${device} && \ return ${EXIT_ERROR} return ${EXIT_OK} } # Get the device type device_get_type() { local device=${1} # If the device does not exist (happens on udev remove events), # we do not bother to run all checks. if ! device_exists "${device}"; then echo "unknown" elif device_is_vlan ${device}; then echo "vlan" elif device_is_bonding ${device}; then echo "bonding" elif device_is_bridge ${device}; then echo "bridge" elif device_is_ppp ${device}; then echo "ppp" elif device_is_batman_adv ${device}; then echo "batman-adv" elif device_is_loopback ${device}; then echo "loopback" elif device_is_wireless_adhoc ${device}; then echo "wireless-adhoc" elif device_is_wireless ${device}; then echo "wireless" elif device_is_dummy ${device}; then echo "dummy" elif device_is_tun ${device}; then echo "tun" elif device_is_ethernet ${device}; then echo "ethernet" elif device_is_serial ${device}; then echo "serial" elif device_is_phy ${device}; then echo "phy" elif device_is_vti ${device}; then echo "vti" else echo "unknown" fi } device_is_ethernet_compatible() { local device="${1}" # /sys/class/net/*/type must equal 1 for ethernet compatible devices local type="$(__device_get_file "${device}" "type")" [[ "${type}" = "1" ]] } device_get_status() { local device=${1} assert isset device local status=${STATUS_DOWN} if device_is_up ${device}; then status=${STATUS_UP} if ! device_has_carrier ${device}; then status=${STATUS_NOCARRIER} fi fi echo "${status}" } device_get_address() { local device=${1} cat ${SYS_CLASS_NET}/${device}/address 2>/dev/null } device_set_address() { assert [ $# -eq 2 ] local device="${1}" local addr="${2}" if ! device_exists "${device}"; then error "Device '${device}' does not exist." return ${EXIT_ERROR} fi # Do nothing if the address has not changed local old_addr="$(device_get_address "${device}")" if [ -n "${old_addr}" -a "${addr}" = "${old_addr}" ]; then return ${EXIT_OK} fi log DEBUG "Setting address of '${device}' from '${old_addr}' to '${addr}'" local up if device_is_up "${device}"; then device_set_down "${device}" up=1 fi ip link set "${device}" address "${addr}" local ret=$? if [ "${up}" = "1" ]; then device_set_up "${device}" fi if [ "${ret}" != "0" ]; then error_log "Could not set address '${addr}' on device '${device}'" fi return ${ret} } device_get() { local device for device in $(list_directory "${SYS_CLASS_NET}"); do # bonding_masters is no device [ "${device}" = "bonding_masters" ] && continue echo "${device}" done return ${EXIT_OK} } devices_get_all() { device_get } # Check if a device has a cable plugged in device_has_carrier() { local device=${1} assert isset device local carrier=$(__device_get_file ${device} carrier) [ "${carrier}" = "1" ] } device_is_promisc() { local device=${1} device_has_flag ${device} 0x200 } device_set_promisc() { local device=${1} local state=${2} assert device_exists ${device} assert isset state assert isoneof state on off ip link set ${device} promisc ${state} } # Check if the device is free device_is_free() { ! device_is_used "$@" } # Check if the device is used device_is_used() { local device=${1} device_has_vlans ${device} && \ return ${EXIT_OK} device_is_bonded ${device} && \ return ${EXIT_OK} device_is_bridge_attached ${device} && \ return ${EXIT_OK} return ${EXIT_ERROR} } # Give the device a new name device_set_name() { local source=$1 local destination=${2} # Check if devices exists if ! device_exists ${source} || device_exists ${destination}; then return 4 fi local up if device_is_up ${source}; then ip link set ${source} down up=1 fi ip link set ${source} name ${destination} if [ "${up}" = "1" ]; then ip link set ${destination} up fi } device_set_master() { local device="${1}" assert isset device local master="${2}" assert isset master if ! cmd ip link set "${device}" master "${master}"; then log ERROR "Could not set master ${master} for device ${device}" return ${EXIT_ERROR} fi return ${EXIT_OK} } device_remove_master() { local device="${1}" assert isset device if ! cmd ip link set "${device}" nomaster; then log ERROR "Could not remove master for device ${device}" return ${EXIT_ERROR} fi return ${EXIT_OK} } # Set device up device_set_up() { assert [ $# -eq 1 ] local device=${1} # Do nothing if device is already up device_is_up ${device} && return ${EXIT_OK} log INFO "Bringing up ${device}" device_set_parent_up ${device} if ! cmd ip link set ${device} up; then return ${EXIT_ERROR} fi # Set SMP affinity if interrupt_use_smp_affinity; then device_auto_configure_smp_affinity ${device} fi return ${EXIT_OK} } device_set_parent_up() { local device=${1} local parent if device_is_vlan ${device}; then parent=$(vlan_get_parent ${device}) device_is_up ${parent} && return ${EXIT_OK} log DEBUG "Setting up parent device '${parent}' of '${device}'" device_set_up ${parent} return $? fi return ${EXIT_OK} } # Set device down device_set_down() { assert [ $# -eq 1 ] local device=${1} local ret=${EXIT_OK} if device_is_up ${device}; then log INFO "Bringing down ${device}" cmd ip link set ${device} down ret=$? fi device_set_parent_down ${device} return ${ret} } device_set_parent_down() { local device=${1} local parent if device_is_vlan ${device}; then parent=$(vlan_get_parent ${device}) device_is_up ${parent} || return ${EXIT_OK} if device_is_free ${parent}; then log DEBUG "Tearing down parent device '${parent}' of '${device}'" device_set_down ${parent} fi fi return ${EXIT_OK} } device_get_mtu() { local device=${1} # Return an error if the device does not exist device_exists ${device} || return ${EXIT_ERROR} echo $(<${SYS_CLASS_NET}/${device}/mtu) } # Set mtu to a device device_set_mtu() { local device=${1} local mtu=${2} assert device_exists ${device} # Handle bridges differently if device_is_bridge ${device}; then local port for port in $(bridge_get_members ${device}); do device_set_mtu ${port} ${mtu} done fi log INFO "Setting MTU of ${device} to ${mtu}" local up if device_is_up ${device}; then device_set_down ${device} up=1 fi local ret=${EXIT_OK} if ! cmd ip link set ${device} mtu ${mtu}; then ret=${EXIT_ERROR} log ERROR "Could not set MTU ${mtu} on ${device}" fi if [ "${up}" = "1" ]; then device_set_up ${device} fi return ${ret} } device_adjust_mtu() { assert [ $# -eq 2 ] local device="${1}" local other_device="${2}" local mtu="$(device_get_mtu "${other_device}")" device_set_mtu "${device}" "${mtu}" } device_discover() { local device=${1} log INFO "Running discovery process on device '${device}'." local hook for hook in $(hook_zone_get_all); do hook_zone_exec ${hook} discover ${device} done } device_identify() { assert [ $# -ge 1 ] local device="${1}" # Flash for ten seconds by default local seconds="10" # Run in background? local background="false" local arg while read arg; do case "${arg}" in --background) background="true" ;; --seconds=*) seconds="$(cli_get_val "${arg}")" ;; esac done <<< "$(args "$@")" assert isinteger seconds if ! device_exists "${device}"; then log ERROR "Cannot identify device ${device}: Does not exist" return ${EXIT_ERROR} fi if ! device_is_ethernet "${device}"; then log DEBUG "Cannot identify device ${device}: Not an ethernet device" return ${EXIT_NOT_SUPPORTED} fi log DEBUG "Identifying device ${device}" local command="ethtool --identify ${device} ${seconds}" local ret=0 if enabled background; then cmd_background "${command}" else cmd_quiet "${command}" ret=$? fi return ${ret} } device_has_ip() { local device=${1} local addr=${2} assert isset addr assert device_exists ${device} # IPv6 addresses must be fully imploded local protocol=$(ip_detect_protocol ${addr}) case "${protocol}" in ipv6) addr=$(ipv6_format "${addr}") ;; esac list_match ${addr} $(device_get_addresses ${device}) } device_get_addresses() { local device=${1} assert device_exists ${device} local prot local addr local line ip addr show ${device} | \ while read prot addr line; do [ "${prot:0:4}" = "inet" ] && echo "${addr}" done } __device_get_file() { local device=${1} local file=${2} fread "${SYS_CLASS_NET}/${device}/${file}" } __device_set_file() { assert [ $# -eq 3 ] local device="${1}" local file="${2}" local value="${3}" fappend "${SYS_CLASS_NET}/${device}/${file}" "${value}" } device_get_rx_bytes() { local device=${1} __device_get_file ${device} statistics/rx_bytes } device_get_tx_bytes() { local device=${1} __device_get_file ${device} statistics/tx_bytes } device_get_rx_packets() { local device=${1} __device_get_file ${device} statistics/rx_packets } device_get_tx_packets() { local device=${1} __device_get_file ${device} statistics/tx_packets } device_get_rx_errors() { local device=${1} __device_get_file ${device} statistics/rx_errors } device_get_tx_errors() { local device=${1} __device_get_file ${device} statistics/tx_errors } device_get_speed() { local device=${1} local speed=$(__device_get_file ${device} speed) # Exit for no output (i.e. no link detected) isset speed || return ${EXIT_ERROR} # Don't return anything for negative values [ ${speed} -lt 0 ] && return ${EXIT_ERROR} print "${speed}" } device_get_duplex() { local device=${1} local duplex=$(__device_get_file ${device} duplex) case "${duplex}" in unknown) return ${EXIT_ERROR} ;; *) print "${duplex}" ;; esac } device_get_link_string() { local device="${1}" assert isset device local s local speed="$(device_get_speed "${device}")" if isset speed; then list_append s "${speed} MBit/s" fi local duplex="$(device_get_duplex "${device}")" if isset duplex; then list_append s "${duplex} duplex" fi print "${s}" } device_auto_configure_smp_affinity() { assert [ $# -eq 1 ] local device=${1} if lock_acquire "smp-affinity" 60; then device_set_smp_affinity ${device} auto lock_release "smp-affinity" fi } device_set_smp_affinity() { assert [ $# -eq 2 ] local device=${1} local mode=${2} # mode can be auto which will automatically try to find # the least busy processor, or an integer for the desired # processor that should handle this device local num_processors=$(system_get_processors) if [ "${mode}" = "auto" ]; then local processor=$(interrupt_choose_least_busy_processor) else assert isinteger mode local processor=${mode} if [ ${processor} -gt ${num_processors} ]; then log ERROR "Processor ${processor} does not exist" return ${EXIT_ERROR} fi fi local interrupts=$(interrupts_for_device ${device}) if ! isset interrupts; then log DEBUG "${device} has no interrupts. Not changing SMP affinity" return ${EXIT_OK} fi # Set SMP affinity local interrupt for interrupt in ${interrupts}; do interrupt_set_smp_affinity ${interrupt} ${processor} done # Find all queues and assign them to the next processor local queue for queue in $(device_get_queues ${device}); do case "${queue}" in # Only handle receive queues rx-*) for interrupt in $(interrupts_for_device_queue ${device} ${queue}); do interrupt_set_smp_affinity ${interrupt} ${processor} done device_queue_set_smp_affinity ${device} ${queue} ${processor} ;; # Ignore the rest *) continue ;; esac # Get the next available processor if in auto mode [ "${mode}" = "auto" ] && processor=$(system_get_next_processor ${processor}) done return ${EXIT_OK} } device_get_queues() { assert [ $# -eq 1 ] local device=${1} list_directory "${SYS_CLASS_NET}/${device}/queues" } device_supports_multiqueue() { local device=${1} local num_queues=$(device_num_queues ${device}) if isset num_queues && [ ${num_queues} -gt 2 ]; then return ${EXIT_TRUE} fi return ${EXIT_FALSE} } device_num_queues() { local device=${1} local type=${2} isset type && assert isoneof type rx tx local i=0 local q for q in $(device_get_queues ${device}); do case "${type},${q}" in rx,rx-*) (( i++ )) ;; tx,tx-*) (( i++ )) ;; *,*) (( i++ )) ;; esac done print ${i} } device_queue_get_smp_affinity() { assert [ $# -eq 2 ] local device=${1} local queue=${2} local path="${SYS_CLASS_NET}/${device}/queues/${queue}" case "${queue}" in rx-*) path="${path}/rps_cpus" ;; tx-*) path="${path}/xps_cpus" ;; esac assert [ -r "${path}" ] __bitmap_to_processor_ids $(<${path}) } device_queue_set_smp_affinity() { assert [ $# -eq 3 ] local device=${1} local queue=${2} local processor=${3} local path="${SYS_CLASS_NET}/${device}/queues/${queue}/rps_cpus" assert [ -w "${path}" ] log DEBUG "Setting SMP affinity of ${device} (${queue}) to processor ${processor}" __processor_id_to_bitmap ${processor} > ${path} }