File: //etc/sysconfig/network-scripts/ec2net-functions
# -*-Shell-script-*-
# Copyright (C) 2012 Amazon.com, Inc. or its affiliates.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
# OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the
# License.
# This file is not a stand-alone shell script; it provides functions
# to ec2 network scripts that source it.
# Set up a default search path.
PATH="/sbin:/usr/sbin:/bin:/usr/bin"
export PATH
# metadata query requires an interface and hardware address
if [ -z "${INTERFACE}" ]; then
exit
fi
HWADDR=$(cat /sys/class/net/${INTERFACE}/address 2>/dev/null)
while test "${HWADDR}" = "00:00:00:00:00:00"; do
sleep 0.1
HWADDR=$(cat /sys/class/net/${INTERFACE}/address 2>/dev/null)
done
if [ -z "${HWADDR}" ] && [ "${ACTION}" != "remove" ]; then
exit
fi
export HWADDR
# generate a routing table number
RTABLE=${INTERFACE#eth}
let RTABLE+=10000
metadata_base="http://169.254.169.254/latest/meta-data/network/interfaces/macs"
config_file="/etc/sysconfig/network-scripts/ifcfg-${INTERFACE}"
route_file="/etc/sysconfig/network-scripts/route-${INTERFACE}"
route6_file="/etc/sysconfig/network-scripts/route6-${INTERFACE}"
dhclient_file="/etc/dhcp/dhclient-${INTERFACE}.conf"
# make no changes to unmanaged interfaces
if [ -s ${config_file} ]; then
unmanaged=$(LANG=C grep -l "^[[:space:]]*EC2SYNC=no\([[:space:]#]\|$\)" $config_file)
if [ "${config_file}" == "${unmanaged}" ]; then
logger --tag ec2net "Not managing ${INTERFACE}"
exit
fi
fi
get_meta() {
attempts=60
false
while [ "${?}" -gt 0 ]; do
if [ "${attempts}" -eq 0 ]; then
logger --tag ec2net "[get_meta] Failed to get ${metadata_base}/${HWADDR}/${1}"
return
fi
logger --tag ec2net "[get_meta] Trying to get ${metadata_base}/${HWADDR}/${1}"
meta=$(curl -s -f ${metadata_base}/${HWADDR}/${1})
if [ "${?}" -gt 0 ]; then
let attempts--
sleep 0.5
false
fi
done
echo "${meta}"
}
get_cidr() {
cidr=$(get_meta 'subnet-ipv4-cidr-block')
echo "${cidr}"
}
get_ipv4s() {
ipv4s=$(get_meta 'local-ipv4s')
echo "${ipv4s}"
}
get_primary_ipv4() {
ipv4s=($(get_ipv4s))
echo "${ipv4s[0]}"
}
get_secondary_ipv4s() {
ipv4s=($(get_ipv4s))
echo "${ipv4s[@]:1}"
}
get_ipv6s() {
/sbin/ip -6 addr list dev ${INTERFACE} scope global \
| grep "inet6" \
| awk '{print $2}' | cut -d/ -f1
}
get_ipv6_gateway() {
# Because we start dhclient -6 immediately on interface
# hotplug, it's possible we get a DHCP response before we
# receive a router advertisement. The only immediate clue we
# have about the gateway is the MAC address embedded in the
# DHCP6 server ID. If that env var is passed to dhclient-script
# we determine the router address from that; otherwise we wait
# up to 10 seconds for an RA route to be added by the kernel.
if echo "$new_dhcp6_server_id" | grep -q "^0:3:0:1:"; then
logger --tag ec2net "[get_ipv6_gateway] Using DHCP6 environment variable"
octets=($(echo "$new_dhcp6_server_id" | rev | cut -d : -f -6 | rev | tr : ' '))
# The gateway's link local address is derived from the
# hardware address by converting the MAC-48 to an EUI-64:
# 00:00:5e : 00:53:35
# ^^ ^^^^^ ff:fe is inserted in the middle
# first octet is xored with 0x2 (second LSB is flipped)
# thus 02:00:5e:ff:fe:00:53:35.
#
# The EUI-64 is used as the last 64 bits in an fe80::/64
# address, so fe80::200:5eff:fe00:5335.
declare -A quibbles # quad nibbles
quibbles[0]=$(( ((0x${octets[0]} ^ 2) << 8) + 0x${octets[1]} ))
quibbles[1]=$(( 0x${octets[2]}ff ))
quibbles[2]=$(( 0xfe00 + 0x${octets[3]} ))
quibbles[3]=$(( (0x${octets[4]} << 8) + 0x${octets[5]} ))
printf "fe80::%04x:%04x:%04x:%04x\n" ${quibbles[@]}
else
logger --tag ec2net "[get_ipv6_gateway] Waiting for IPv6 router advertisement"
attempts=20
while true; do
if [ "${attempts}" -eq 0 ]; then
logger --tag ec2net "[get_ipv6_gateway] Failed to receive router advertisement"
return
fi
gateway6=$(/sbin/ip -6 route show dev "${INTERFACE}" | grep ^default | awk '{print $3}')
if [ -n "${gateway6}" ]; then
break
else
let attempts--
sleep 0.5
fi
done
echo "${gateway6}"
fi
}
remove_primary() {
if [ "${INTERFACE}" == "eth0" ]; then
return
fi
logger --tag ec2net "[remove_primary] Removing configs for ${INTERFACE}"
rm -f ${config_file}
rm -f ${route_file}
rm -f ${route6_file}
rm -f ${dhclient_file}
}
rewrite_primary() {
if [ "${INTERFACE}" == "eth0" ]; then
return
fi
logger --tag ec2net "[rewrite_primary] Rewriting configs for ${INTERFACE}"
cidr=$(get_cidr)
if [ -z ${cidr} ]; then
return
fi
network=$(echo ${cidr}|cut -d/ -f1)
router=$(( $(echo ${network}|cut -d. -f4) + 1))
gateway="$(echo ${network}|cut -d. -f1-3).${router}"
primary_ipv4="$(get_primary_ipv4)"
cat <<- EOF > ${config_file}
DEVICE=${INTERFACE}
BOOTPROTO=dhcp
ONBOOT=yes
TYPE=Ethernet
USERCTL=yes
PEERDNS=no
IPV6INIT=yes
DHCPV6C=yes
DHCPV6C_OPTIONS=-nw
PERSISTENT_DHCLIENT=yes
HWADDR=${HWADDR}
DEFROUTE=no
EC2SYNC=yes
EOF
cat <<- EOF > ${route_file}
default via ${gateway} dev ${INTERFACE} table ${RTABLE}
default via ${gateway} dev ${INTERFACE} metric ${RTABLE}
${cidr} dev ${INTERFACE} proto kernel scope link src ${primary_ipv4} table ${RTABLE}
EOF
# We would normally write to ${route6_file} here but the
# gateway is an fe80:: link local address that we get from the
# RA. We only get an RA if the interface has an IPv6 address.
# So we wait until dhclient -6 runs rewrite_rules() and add the
# ${RTABLE} table there.
rm -f ${route6_file}
# Use broadcast address instead of unicast dhcp server address.
# Works around an issue with two interfaces on the same subnet.
# Unicast lease requests go out the first available interface,
# and dhclient ignores the response. Broadcast requests go out
# the expected interface, and dhclient accepts the lease offer.
cat <<- EOF > ${dhclient_file}
supersede dhcp-server-identifier 255.255.255.255;
EOF
}
remove_aliases() {
logger --tag ec2net "[remove_aliases] Removing aliases of ${INTERFACE}"
/sbin/ip -4 addr flush dev ${INTERFACE} secondary
}
rewrite_aliases() {
aliases=$(get_secondary_ipv4s)
if [ ${#aliases[*]} -eq 0 ]; then
remove_aliases
return
fi
logger --tag ec2net "[rewrite_aliases] Rewriting aliases of ${INTERFACE}"
# The network prefix can be provided in the environment by
# e.g. DHCP, but if it's not available then we need it to
# correctly configure secondary addresses.
if [ -z "${PREFIX}" ]; then
cidr=$(get_cidr)
PREFIX=$(echo ${cidr}|cut -d/ -f2)
fi
[ -n "${PREFIX##*[!0-9]*}" ] || return
# Retrieve a list of secondary IP addresses on the interface.
# Treat this as the stale list. For each IP address obtained
# from metadata, cross it off the stale list if present, or
# add it to the interface otherwise. Then, remove any address
# remaining in the stale list.
declare -A secondaries
for secondary in $(/sbin/ip -4 addr list dev ${INTERFACE} secondary \
|grep "inet .* secondary ${INTERFACE}" \
|awk '{print $2}'|cut -d/ -f1); do
secondaries[${secondary}]=1
done
for alias in ${aliases}; do
if [[ ${secondaries[${alias}]} ]]; then
unset secondaries[${alias}]
else
/sbin/ip -4 addr add ${alias}/${PREFIX} brd + dev ${INTERFACE}
fi
done
for secondary in "${!secondaries[@]}"; do
/sbin/ip -4 addr del ${secondary}/${PREFIX} dev ${INTERFACE}
done
}
remove_rules() {
if [ "${INTERFACE}" == "eth0" ]; then
return
fi
logger --tag ec2net "[remove_rules] Removing rules for ${INTERFACE}"
for rule in $(/sbin/ip -4 rule list \
|grep "from .* lookup ${RTABLE}" \
|awk -F: '{print $1}'); do
/sbin/ip -4 rule delete pref "${rule}"
done
for rule in $(/sbin/ip -6 rule list \
|grep "from .* lookup ${RTABLE}" \
|awk -F: '{print $1}'); do
/sbin/ip -6 rule delete pref "${rule}"
done
}
rewrite_rules() {
if [ "${INTERFACE}" == "eth0" ]; then
return
fi
ips=($(get_ipv4s))
ip6s=($(get_ipv6s))
if [ ${#ips[*]} -eq 0 ]; then
remove_rules
return
fi
# This is the part we would do in rewrite_primary() if we knew
# the gateway address.
if [ ${#ip6s[*]} -gt 0 -a -z "$(/sbin/ip -6 route show table ${RTABLE})" ]; then
gateway6=$(get_ipv6_gateway)
# Manually add the route, then add it to ${route6_file} so it
# gets brought down with the rest of the interface.
/sbin/ip -6 route add default via ${gateway6} dev ${INTERFACE} table ${RTABLE}
cat <<- EOF > ${route6_file}
default via ${gateway6} dev ${INTERFACE} table ${RTABLE}
EOF
fi
logger --tag ec2net "[rewrite_rules] Rewriting rules for ${INTERFACE}"
# Retrieve a list of IP rules for the route table that belongs
# to this interface. Treat this as the stale list. For each IP
# address obtained from metadata, cross the corresponding rule
# off the stale list if present. Otherwise, add a rule sending
# outbound traffic from that IP to the interface route table.
# Then, remove all other rules found in the stale list.
declare -A rules
for rule in $(/sbin/ip -4 rule list \
|grep "from .* lookup ${RTABLE}" \
|awk '{print $1$3}'); do
split=(${rule//:/ })
rules[${split[1]}]=${split[0]}
done
for ip in ${ips[@]}; do
if [[ ${rules[${ip}]} ]]; then
unset rules[${ip}]
else
/sbin/ip -4 rule add from ${ip} lookup ${RTABLE}
fi
done
for rule in "${!rules[@]}"; do
/sbin/ip -4 rule delete pref "${rules[${rule}]}"
done
# Now do the same, but for IPv6
declare -A rules6
for rule in $(/sbin/ip -6 rule list \
|grep "from .* lookup ${RTABLE}" \
|awk '{print $1$3}'); do
split=(${rule/:/ }) # take care to only replace the first :
rules6[${split[1]}]=${split[0]}
done
for ip in ${ip6s[@]}; do
if [[ ${rules6[${ip}]} ]]; then
unset rules6[${ip}]
else
/sbin/ip -6 rule add from ${ip} lookup ${RTABLE}
fi
done
for rule in "${!rules6[@]}"; do
/sbin/ip -6 rule delete pref "${rules6[${rule}]}"
done
}
plug_interface() {
logger --tag ec2net "[plug_interface] ${INTERFACE} plugged"
rewrite_primary
}
unplug_interface() {
logger --tag ec2net "[unplug_interface] ${INTERFACE} unplugged"
remove_rules
remove_aliases
}
activate_primary() {
logger --tag ec2net "[activate_primary] Activating ${INTERFACE}"
/sbin/ifup ${INTERFACE}
}
deactivate_primary() {
logger --tag ec2net "[deactivate_primary] Deactivating ${INTERFACE}"
/sbin/ifdown ${INTERFACE}
}