2017-08-11 01:29:47 +02:00
|
|
|
#!/usr/bin/env python2.7
|
2013-07-12 19:53:09 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
2013-12-12 22:16:03 +01:00
|
|
|
#
|
|
|
|
# Copyright © 2013 Zulip, Inc.
|
|
|
|
#
|
|
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
|
|
# in the Software without restriction, including without limitation the rights
|
|
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
|
|
# furnished to do so, subject to the following conditions:
|
|
|
|
#
|
|
|
|
# The above copyright notice and this permission notice shall be included in
|
|
|
|
# all copies or substantial portions of the Software.
|
|
|
|
#
|
|
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
# THE SOFTWARE.
|
|
|
|
|
|
|
|
# Original author: Luke Faraone
|
|
|
|
|
2013-07-12 19:53:09 +02:00
|
|
|
'''Configure a host for EC2-VPC dynamically assigned network interfaces
|
|
|
|
|
|
|
|
Amazon VPC gives us a good deal of flexibility compared to classic EC2.
|
|
|
|
However there are limitations; you can assign multiple IPs to a host
|
|
|
|
yet only the first IP per interface will be DHCP assigned, and you are
|
|
|
|
limited in the total number of interfaces you have, so doing one-IP-per-
|
|
|
|
interface is also untenable.
|
|
|
|
|
|
|
|
This script grabs the metadata provided by AWS and uses it to correctly
|
|
|
|
configure all available network interfaces.
|
|
|
|
|
|
|
|
It is suitable to be hooked in to system boot and network
|
|
|
|
reconfiguration scripts.
|
|
|
|
|
|
|
|
Note that it currently does not handle the deconfiguration of
|
|
|
|
interfaces.
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
2016-03-10 18:43:31 +01:00
|
|
|
from __future__ import absolute_import
|
2013-07-12 19:53:09 +02:00
|
|
|
import sys
|
2013-07-14 04:34:40 +02:00
|
|
|
import logging
|
|
|
|
import logging.handlers
|
2013-07-12 19:53:09 +02:00
|
|
|
import subprocess
|
|
|
|
|
|
|
|
import boto.utils
|
|
|
|
import netifaces
|
2019-07-23 23:58:11 +02:00
|
|
|
|
|
|
|
from typing import Optional
|
2013-07-12 19:53:09 +02:00
|
|
|
|
|
|
|
def address_of(device_id):
|
2016-12-03 03:17:24 +01:00
|
|
|
# type: (int) -> Optional[str]
|
2013-07-12 19:53:09 +02:00
|
|
|
try:
|
2019-04-20 01:00:46 +02:00
|
|
|
return netifaces.ifaddresses("ens%i" % (device_id,))[netifaces.AF_INET][0]['addr']
|
2013-07-12 19:53:09 +02:00
|
|
|
except KeyError:
|
|
|
|
return None
|
|
|
|
|
2013-07-14 04:25:51 +02:00
|
|
|
def guess_gateway(device_id):
|
2017-07-09 21:17:49 +02:00
|
|
|
# type: (int) -> Optional[str]
|
2013-07-14 04:25:51 +02:00
|
|
|
# This will not work if the default gateway isn't n.n.n.1.
|
2017-07-09 21:17:49 +02:00
|
|
|
address = address_of(device_id)
|
|
|
|
if address is None:
|
|
|
|
return None
|
|
|
|
gateway = address.split('.')
|
|
|
|
gateway[3] = '1'
|
|
|
|
return '.'.join(gateway)
|
2013-07-14 04:25:51 +02:00
|
|
|
|
2013-12-12 22:16:03 +01:00
|
|
|
log = logging.getLogger('configure-cloud-interfaces')
|
2013-07-14 04:34:40 +02:00
|
|
|
log.setLevel(logging.DEBUG)
|
|
|
|
|
|
|
|
log.addHandler(logging.handlers.SysLogHandler(facility=logging.handlers.SysLogHandler.LOG_DAEMON))
|
|
|
|
log.addHandler(logging.StreamHandler())
|
2013-12-12 22:16:03 +01:00
|
|
|
log.info("Starting.")
|
2013-07-14 04:34:40 +02:00
|
|
|
|
2018-03-23 16:51:12 +01:00
|
|
|
metadata = boto.utils.get_instance_metadata()
|
|
|
|
if metadata is None:
|
|
|
|
log.error("Could not get instance metadata!")
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
macs = metadata["network"]["interfaces"]["macs"]
|
2013-07-12 19:53:09 +02:00
|
|
|
ids = [int(macdata['device-number']) for macdata in macs.values()]
|
|
|
|
ifaces = [iface for iface in netifaces.interfaces() if ":" not in iface and iface != "lo"]
|
|
|
|
|
|
|
|
# Number of IDs should equal number of interfaces
|
|
|
|
if len(ids) != len(ifaces):
|
2013-07-14 04:34:40 +02:00
|
|
|
log.error("Metadata indicated %i interfaces but we have %i!" % (len(ids), len(ifaces)))
|
2013-07-12 19:53:09 +02:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
for device in macs.values():
|
|
|
|
# There's an annoying API inconsistency here:
|
|
|
|
# If you have multiple IPs, local-ipv4s is a list.
|
|
|
|
# If you only have one, local-ipv4s is a string.
|
|
|
|
# Who knew?
|
2016-03-10 14:52:28 +01:00
|
|
|
if isinstance(device['local-ipv4s'], str):
|
2013-07-12 19:53:09 +02:00
|
|
|
# Only do dhcp, don't try to assign addresses
|
|
|
|
to_configure = [device['local-ipv4s']]
|
|
|
|
else:
|
|
|
|
to_configure = list(device['local-ipv4s'])
|
|
|
|
device_number = int(device['device-number'])
|
2016-08-13 01:43:51 +02:00
|
|
|
if subprocess.check_output(["lsb_release", '-sc']).strip() == "xenial":
|
|
|
|
# HACK: It appears on Xenial device_number in the data set is 0 but in reality it is 3
|
|
|
|
device_number += 3
|
2013-07-12 19:53:09 +02:00
|
|
|
|
2017-07-09 23:05:48 +02:00
|
|
|
address = address_of(device_number)
|
|
|
|
|
|
|
|
if address is None:
|
2013-07-12 19:53:09 +02:00
|
|
|
# If the device was not autoconfigured, do so now.
|
2016-08-13 01:43:51 +02:00
|
|
|
log.info("Device ens%i not configured, starting dhcpd" % (device_number,))
|
2019-04-20 01:00:46 +02:00
|
|
|
subprocess.check_call(['/sbin/dhcpcd', 'ens%i' % (device_number,)])
|
2013-07-12 19:53:09 +02:00
|
|
|
|
2017-07-09 22:30:13 +02:00
|
|
|
dev_num = str(device_number)
|
|
|
|
address = address_of(device_number)
|
|
|
|
gateway = guess_gateway(device_number)
|
|
|
|
assert(address is not None)
|
|
|
|
assert(gateway is not None)
|
|
|
|
|
2013-07-14 04:25:51 +02:00
|
|
|
# Horrible hack to route return packets on the correct interface
|
|
|
|
# See http://unix.stackexchange.com/a/4421/933
|
|
|
|
subprocess.check_call(
|
2017-07-09 22:30:13 +02:00
|
|
|
['/sbin/ip', 'rule', 'add', 'fwmark', dev_num, 'table', dev_num])
|
2013-07-14 04:25:51 +02:00
|
|
|
subprocess.check_call(
|
2017-07-09 22:30:13 +02:00
|
|
|
['/sbin/ip', 'route', 'add', '0.0.0.0/0', 'table', dev_num, 'dev',
|
2019-04-20 01:00:46 +02:00
|
|
|
'ens%i' % (device_number,), 'via', gateway])
|
2013-07-14 04:25:51 +02:00
|
|
|
subprocess.check_call(
|
2017-01-24 07:06:13 +01:00
|
|
|
['/sbin/iptables', '-t', 'mangle', '-A', 'OUTPUT', '-m', 'conntrack', '--ctorigdst',
|
2017-07-09 22:30:13 +02:00
|
|
|
address, '-j', 'MARK', '--set-mark', dev_num])
|
2013-07-14 04:25:51 +02:00
|
|
|
|
2017-07-09 23:05:48 +02:00
|
|
|
to_configure.remove(address)
|
2013-07-12 19:53:09 +02:00
|
|
|
|
|
|
|
for (count, ip) in enumerate(to_configure):
|
|
|
|
# Configure the IP via a virtual interface
|
2016-08-13 01:43:51 +02:00
|
|
|
device = "ens%i:%i" % (device_number, count)
|
2013-07-14 04:34:40 +02:00
|
|
|
log.info("Configuring %s with IP %s" % (device, ip))
|
2013-07-12 19:53:09 +02:00
|
|
|
subprocess.check_call(['/sbin/ifconfig', device, ip])
|
2013-12-12 22:15:19 +01:00
|
|
|
subprocess.check_call(
|
2017-01-24 07:06:13 +01:00
|
|
|
['/sbin/iptables', '-t', 'mangle', '-A', 'OUTPUT', '-m', 'conntrack', '--ctorigdst',
|
|
|
|
ip, '-j', 'MARK', '--set-mark', str(device_number)])
|
2013-08-19 20:48:54 +02:00
|
|
|
|
|
|
|
for throwaway in range(2):
|
|
|
|
# Don't freak out if this doens't work.
|
|
|
|
subprocess.call(
|
|
|
|
['/sbin/ip', 'route', 'del', '10.0.0.0/8'])
|
2013-12-12 22:16:03 +01:00
|
|
|
|
|
|
|
log.info("Finished.")
|