blob: e0ae61dd9b24e32b87396a192612ed2e4d29c569 [file] [log] [blame]
# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
description "Configure network bridge and disable wlan0 for moblab"
author "chromium-os-dev@chromium.org"
start on (started network-services and
stopped moblab-external-storage-init RESULT=ok)
normal exit 0
script
mkdir -p /var/log/bootup/
exec >>/var/log/bootup/${UPSTART_JOB}.log 2>&1
set -x
set -e
logger -t "$UPSTART_JOB" "Starting."
SERVER_ADDRESS=192.168.231.1
SERVER_NETMASK=255.255.255.0
DHCPD_IFACE='lxcbr0'
SHILL_START_LOCK_PATH="/var/lock/shill-start.lock"
INTERFACE_WAN='WAN'
INTERFACE_LAN='LAN'
# If this file exists, it must contain a single line with the mac address of
# the network interface to be used for private network shared by moblab with
# its DUTs.
PRIVATE_NETWORK_INTERFACE_MAC_ADDR='/mnt/moblab/private-network-macaddr.conf'
# $1: Name of the network interface to check for (e.g.: eth0)
# returns: 1 on success, 0 otherwise.
node_labeled_for_private_network() {
local node="$1"
local expected_addr=$(head -1 "${PRIVATE_NETWORK_INTERFACE_MAC_ADDR}" |
tr -d '[:space:]')
local got_addr=$(ip link show "${node}" | tail -1 | awk '{print $2}')
if [ "${expected_addr}" = "${got_addr}" ]; then
return 1
fi
return 0
}
ensure_br_netfilter_loaded() {
if [ -e /proc/sys/net/bridge ]; then
return 1
fi
# Kernels older that 3.18 do not have br_netfilter as a module.
modprobe -n br_netfilter || return 1
logger -t "${UPSTART_JOB}" "Loading kernel module br_netfilter."
modprobe br_netfilter
local i=0
while [ $i -le 10 ]; do
if [ -e /proc/sys/net/bridge ]; then
return 1
fi
sleep 0.5
i=$(( i + 1 ))
done
return 0
}
# Normal case, one interface connected to a network that should have
# access to google, that interface should have an IP address.
# The other interface has the DUT's connected, no DHCP server has been
# started yet so no IP address on that interface.
find_network_interfaces_by_ip_addr() {
logger -t "${UPSTART_JOB}" "Finding interfaces by ip address"
local node
local server_net_prefix=$( echo "${SERVER_ADDRESS}" |
awk -F '.' '{print $1 $2 $3}' )
for node in "$@"; do
local inet_addr=$( ip addr show $node |
grep "inet\b" |
awk '{print $2}' |
cut -d/ -f1 )
if [ -z "${inet_addr}" ]; then
if [ -z ${INT_IFACE} ]; then
INT_IFACE=$node
fi
else
# Exclude the dhcp server subnet from being the external interface
local net_prefix=$( echo "${inet_addr}" |
awk -F '.' '{print $1 $2 $3}' )
if [ "${net_prefix}" != "${server_net_prefix}" ]; then
if [ -z ${EXT_IFACE} ]; then
EXT_IFACE=$node
else
logger -t "${UPSTART_JOB}" "Multiple interfaces meet the criteria \
for External interface, please check wiring connections, only one\
network cable to internet required."
fi
fi
fi
done
}
# This is the legacy method - only works when a USB dongle is in use,
# and there is only a single built in ethernet
find_network_interfaces_by_device_type() {
logger -t "${UPSTART_JOB}" "Finding interfaces by device type"
for node in "$@"; do
case $(readlink -f /sys/class/net/$node) in
*/usb*)
USB_IFACE=$node
;;
*/pci*)
EXT_IFACE=$node
;;
esac
done
}
# Just guess what if is which so the bootup will happen.
find_network_interfaces_by_guess() {
logger -t "${UPSTART_JOB}" "Guessing interfaces"
INT_IFACE="$1"
EXT_IFACE="$2"
}
find_network_interfaces() {
# Find which network interfaces is connected to the WAN.
EXT_IFACE=""
INT_IFACE=""
local count=0
find_network_interfaces_by_ip_addr $@
until [ ! -z "${EXT_IFACE}" ] && [ ! -z ${INT_IFACE} ] ||
[ ${count} -gt 5 ]; do
count=$(($count+1))
sleep 10
find_network_interfaces_by_ip_addr $@
done
logger -t "${UPSTART_JOB}" "External ${EXT_IFACE} Internal ${INT_IFACE}"
# TODO(haddowk) Remove this check when we are sure IP address is working
# correctly in the field.
if [ -z "${EXT_IFACE}" ] || [ -z ${INT_IFACE} ]; then
find_network_interfaces_by_device_type $@
fi
logger -t "${UPSTART_JOB}" "External ${EXT_IFACE} Internal ${INT_IFACE}"
if [ -z "${EXT_IFACE}" ] || [ -z ${INT_IFACE} ]; then
find_network_interfaces_by_guess $@
fi
# Ethtool exits with an error code if the device does not exist if it
# is not present in this hardware ( happens when a guado has no usb
# plugged in )
if [ ! -e /sys/class/net/${INT_IFACE} ]; then
return 0
fi
# Line below is simpley a check that ethtool exists, -e will not exit on
# piped statements.
ethtool -i ${INT_IFACE}
# Workaround for issue crbug.com/804977
# The realtek driver in the 3.14 kernel has a bug, dropping TX packets
# when the device is in promiscuous mode, which is required for a network
# bridge.
# The bridge is required so lxc containers virtual ethernet interface
# can share the network link on the physical interface of the host.
if [ "$(uname -r)" = "3.14.0" ]; then
if ethtool -i ${INT_IFACE} | grep --quiet "driver: r8152"; then
logger -t "${UPSTART_JOB}" "Realtek USB device detected, disable tso."
ethtool -K ${INT_IFACE} tso off
fi
fi
}
wait_for_interface() {
# Check the interface existence for 5 mins after boot.
# Taking one parameter as the interface name - either 'built-in' or 'USB'
local reps=0
while [ ${reps} -lt 300 ]; do
find_network_interfaces eth0 eth1
local intf
case "$1" in
${INTERFACE_WAN}) intf=${EXT_IFACE}
;;
${INTERFACE_LAN}) intf=${INT_IFACE}
;;
*) logger -t "${UPSTART_JOB}" "Illegal network interface $1"
break
;;
esac
if [ -n "${intf}" ]; then
logger -t "${UPSTART_JOB}" "Found $1 Ethernet interface ${intf}."
break
fi
if [ $((reps % 30)) -eq 0 ]; then
logger -t "${UPSTART_JOB}" "Waiting for $1 Ethernet connection(${reps} secs)"
fi
: $((reps += 1))
sleep 1
done
}
set_up_forwarding() {
# Add delay after restart shill
sleep 5
# Configure NAT service. This needs to be done each time that shill
# is restarted as the restart will re-run the iptables setup job
# and thus wipe the below settings.
logger -t "${UPSTART_JOB}" "Configuring NAT Service."
iptables -t nat -A POSTROUTING -o ${EXT_IFACE} -j MASQUERADE
iptables -A FORWARD -i ${EXT_IFACE} -o ${DHCPD_IFACE} -m state \
--state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i ${DHCPD_IFACE} -o ${EXT_IFACE} -j ACCEPT
logger -t "${UPSTART_JOB}" "Enabling IP forwarding"
echo 1 > /proc/sys/net/ipv4/ip_forward
echo 0 > /proc/sys/net/bridge/bridge-nf-call-iptables
echo 0 > /proc/sys/net/bridge/bridge-nf-call-ip6tables
}
lock_shill() {
if [ -e "${SHILL_START_LOCK_PATH}" ]; then
lock_holder=$(readlink "${SHILL_START_LOCK_PATH}")
logger -t "${UPSTART_JOB}" "Warning: lock is held by $lock_holder; will retry"
elif [ -L "${SHILL_START_LOCK_PATH}" ]; then
logger -t "${UPSTART_JOB}" "Cleaning dangling shill lock held by dead process"
rm -f "${SHILL_START_LOCK_PATH}"
fi
local timeout=30
while [ "$timeout" -gt 0 ]; do
ln -s /proc/$$ "${SHILL_START_LOCK_PATH}" && return 0
# Failure: owner might still have the lock, so retry for a while
sleep 1
timeout=$((timeout - 1))
done
logger -t "${UPSTART_JOB}" "Error: could not grab shill restart lock after 30 seconds"
exit 1
}
unlock_shill() {
rm -f "${SHILL_START_LOCK_PATH}"
}
# Step 1 - make sure the built-in external interface exists.
wait_for_interface ${INTERFACE_WAN}
if [ -z "${EXT_IFACE}" ]; then
logger -t "${UPSTART_JOB}" "No Internet connection on eth0 or eth1."
exit 0
fi
# Step 2 - stop shill before creating the network bridge.
logger -t "${UPSTART_JOB}" "stopping shill"
lock_shill
stop shill
# Step3 - create and setup the network bridge.
# Step 3.1 - bring up the network bridge and set forward delay to 0.
logger -t "${UPSTART_JOB}" "Bringing up network bridge ${DHCPD_IFACE}"
brctl addbr ${DHCPD_IFACE}
brctl setfd ${DHCPD_IFACE} 0
# Step 3.2 - configure server IP address with ${SERVER_ADDRESS}.
logger -t "${UPSTART_JOB}" "setting server IP address to ${SERVER_ADDRESS}"
ifconfig ${DHCPD_IFACE} ${SERVER_ADDRESS} netmask ${SERVER_NETMASK} up
# Step 3.3 - start the dhcpd server on MobLab. It needs the DHCPD_IFACE
# piped in because on stumpy_moblab this value is not static. See
# moblab-network-init for more details.
logger -t "${UPSTART_JOB}" "starting moblab-dhcpd-init"
start moblab-dhcpd-init DHCPD_IFACE=${DHCPD_IFACE}
# Step 4 - restart shill with the network bridge in blacklist
# Disable shill against the bridge interface and wlan0.
# Shill will automatically ignore the virtual ethernet (veth*) interfaces.
BLACKLISTED_DEVICES=${DHCPD_IFACE},wlan0
logger -t "${UPSTART_JOB}" "starting shill with ${BLACKLISTED_DEVICES} blacklisted"
start shill BLACKLISTED_DEVICES=${BLACKLISTED_DEVICES}
unlock_shill
# Step 5 - set up routing between built-in external network interface and the bridge.
# This module is not loaded by default in the newer kernels.
if ! ensure_br_netfilter_loaded; then
logger -t "${UPSTART_JOB}" "Verified kernel module br_netfilter"
else
logger -t "${UPSTART_JOB}" "Error: Could not load module br_netfilter"
exit 1
fi
set_up_forwarding
# Step 6 - wait for USB dongle to be ready.
wait_for_interface ${INTERFACE_LAN}
if [ -z "${INT_IFACE}" ]; then
logger -t "${UPSTART_JOB}" "Failed to find LAN network interface for \
moblab's DUTs."
exit 0
fi
# Step 7 - restarts shill with USB dongle interface in blacklist.
# Disable shill against USB Ethernet dongle(either eth0 or eth1) and wlan0.
logger -t "${UPSTART_JOB}" "stopping shill"
lock_shill
stop shill
logger -t "${UPSTART_JOB}" \
"starting shill with ${BLACKLISTED_DEVICES} and ${INT_IFACE} blacklisted"
start shill BLACKLISTED_DEVICES=${BLACKLISTED_DEVICES},${INT_IFACE}
unlock_shill
# Step 8 - finish setting USB dongle interface into the bridge.
if [ -e /sys/class/net/${INT_IFACE} ]; then
brctl addif ${DHCPD_IFACE} ${INT_IFACE}
fi
if [ -e /sys/class/net/${INT_IFACE} ]; then
logger -t "${UPSTART_JOB}" "Bringing up ${INT_IFACE}"
ifconfig ${INT_IFACE} up
fi
# Set up the routing after shill restarts.
set_up_forwarding
logger -t "$UPSTART_JOB" "Ending."
end script