#!/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 . #
# #
###############################################################################
declare -A DEVICE_LINK_SPEEDS=(
[10BaseT-Half]=0x1
[10BaseT-Full]=0x2
[100BaseT-Half]=0x4
[100BaseT-Full]=0x8
[1000BaseT-Half]=0x10
[1000BaseT-Full]=0x20
[10000BaseT-Full]=0x1000
)
device_list() {
# Add all interfaces
local device
for device in $(list_directory ${SYS_CLASS_NET}); do
if device_exists "${device}"; then
print "${device}"
fi
done
# List all PHYs
phy_list
# List all serial devices
serial_list
}
# 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}
# Shut down device before we delete it
device_set_down "${device}"
# 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})"
}
device_get_driver() {
local device="${1}"
assert isset device
get_driver_from_path "${SYS_CLASS_NET}/${device}/device/driver/module"
}
# 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}
}
__device_type_matches() {
local device="${1}"
local type="${2}"
local _type="$(__device_get_file "${device}" "type")"
if [ "${type}" = "${_type}" ]; then
return ${EXIT_TRUE}
fi
return ${EXIT_FALSE}
}
# Check if the device is a ppp device
device_is_ppp() {
local device="${1}"
assert isset device
__device_type_matches "${device}" 512
}
# 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]+$ ]]
}
# Check if the device is a wireless device
device_is_wireless() {
local device=${1}
[ -d "${SYS_CLASS_NET}/${device}/phy80211" ]
}
device_is_gre() {
local device="${1}"
assert isset device
__device_type_matches "${device}" 778
}
device_is_gre6() {
local device="${1}"
assert isset device
__device_type_matches "${device}" 823
}
device_is_vti() {
local device="${1}"
assert isset device
__device_type_matches "${device}" 768
}
device_is_vti6() {
local device="${1}"
assert isset device
__device_type_matches "${device}" 769
}
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_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"
else
echo "$(device_tunnel_get_type "${device}")"
fi
}
# This function just checks the types a ip-tunnel device usually have
# so when we know that the device is an ip-tunnel device we save time
device_tunnel_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_vti ${device}; then
echo "vti"
elif device_is_vti6 ${device}; then
echo "vti6"
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}
}
# 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_advertise_link_speeds() {
local device="${1}"
shift
assert isset device
# Advertised modes in hex
local advertise=0
local mode
for mode in $@; do
local m="${DEVICE_LINK_SPEEDS[${mode}]}"
if isset m; then
advertise="$(( advertise | m ))"
fi
done
# If nothing was selected, we reset and enable everything
if [ ${advertise} -eq 0 ]; then
advertise=0xffffff
fi
# Enable auto-negotiation
cmd_quiet ethtool --change "${device}" autoneg on
# Set advertised link speeds
if ! cmd_quiet ethtool --change "${device}" advertise "0x$(hex "${advertise}")"; then
log ERROR "Could not set link modes of ${device}: $@"
return ${EXIT_ERROR}
fi
log DEBUG "Set device link modes of ${device} to $@"
return ${EXIT_ERROR}
}
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() {
local device="${1}"
assert isset device
lock "smp-affinity" \
device_set_smp_affinity "${device}" "auto"
}
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}
}
# Tries to find a device which has the given IP address assigned
device_get_by_assigned_ip_address() {
local ip=${1}
assert isset ip
local device
# Read the first line of ip addr show to
read -r device <<< $(ip addr show to "${ip}")
# If we did not found a device we return with ${EXIT_ERROR}
if ! isset device; then
return ${EXIT_ERROR}
fi
# We get something like:
# 3: upl0: mtu 1500 qdisc mq state UP group default qlen 1000
# and we want upl0 so we take the second word and removing the :
device=(${device})
device=${device[1]}
device=${device%:}
print "${device}"
return ${EXIT_OK}
}
device_get_by_mac_address() {
local mac=${1}
assert isset mac
local device
for device in $(device_list); do
if [ "${mac}" = "$(device_get_address ${device})" ]; then
print "${device}"
return ${EXIT_OK}
fi
done
# We could not found a port to the given mac address so we return exit error
return ${EXIT_ERROR}
}