1
by Luke Faraone
Initial import of configure-cloud-interfaces. |
1 |
#!/usr/bin/python
|
2 |
# -*- coding: utf-8 -*-
|
|
3 |
#
|
|
4 |
# Copyright © 2013 Zulip, Inc.
|
|
5 |
#
|
|
6 |
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7 |
# of this software and associated documentation files (the "Software"), to deal
|
|
8 |
# in the Software without restriction, including without limitation the rights
|
|
9 |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10 |
# copies of the Software, and to permit persons to whom the Software is
|
|
11 |
# furnished to do so, subject to the following conditions:
|
|
12 |
#
|
|
13 |
# The above copyright notice and this permission notice shall be included in
|
|
14 |
# all copies or substantial portions of the Software.
|
|
15 |
#
|
|
16 |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17 |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18 |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19 |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20 |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21 |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22 |
# THE SOFTWARE.
|
|
23 |
||
24 |
# Original author: Luke Faraone
|
|
25 |
||
26 |
'''Configure a host for EC2-VPC dynamically assigned network interfaces
|
|
27 |
||
28 |
Amazon VPC gives us a good deal of flexibility compared to classic EC2.
|
|
29 |
However there are limitations; you can assign multiple IPs to a host
|
|
30 |
yet only the first IP per interface will be DHCP assigned, and you are
|
|
31 |
limited in the total number of interfaces you have, so doing one-IP-per-
|
|
32 |
interface is also untenable.
|
|
33 |
||
34 |
This script grabs the metadata provided by AWS and uses it to correctly
|
|
35 |
configure all available network interfaces.
|
|
36 |
||
37 |
It is suitable to be hooked in to system boot and network
|
|
38 |
reconfiguration scripts.
|
|
39 |
||
40 |
Note that it currently does not handle the deconfiguration of
|
|
41 |
interfaces.
|
|
42 |
||
43 |
'''
|
|
44 |
||
45 |
import sys |
|
46 |
import logging |
|
47 |
import logging.handlers |
|
48 |
import subprocess |
|
49 |
import re |
|
50 |
||
51 |
import boto.utils |
|
52 |
import netifaces |
|
53 |
||
54 |
def address_of(device_id): |
|
55 |
try: |
|
56 |
return netifaces.ifaddresses("eth%i" % device_id)[netifaces.AF_INET][0]['addr'] |
|
57 |
except KeyError: |
|
58 |
return None |
|
59 |
||
60 |
def guess_gateway(device_id): |
|
61 |
# This will not work if the default gateway isn't n.n.n.1.
|
|
62 |
address = address_of(device_id).split('.') |
|
63 |
address[3] = '1' |
|
64 |
return '.'.join(address) |
|
65 |
||
66 |
log = logging.getLogger('configure-cloud-interfaces') |
|
67 |
log.setLevel(logging.DEBUG) |
|
68 |
||
69 |
log.addHandler(logging.handlers.SysLogHandler(facility=logging.handlers.SysLogHandler.LOG_DAEMON)) |
|
70 |
log.addHandler(logging.StreamHandler()) |
|
71 |
log.info("Starting.") |
|
72 |
||
73 |
macs = boto.utils.get_instance_metadata()["network"]["interfaces"]["macs"] |
|
74 |
ids = [int(macdata['device-number']) for macdata in macs.values()] |
|
75 |
ifaces = [iface for iface in netifaces.interfaces() if ":" not in iface and iface != "lo"] |
|
76 |
||
77 |
# Number of IDs should equal number of interfaces
|
|
78 |
if len(ids) != len(ifaces): |
|
79 |
log.error("Metadata indicated %i interfaces but we have %i!" % (len(ids), len(ifaces))) |
|
80 |
sys.exit(1) |
|
81 |
||
82 |
for device in macs.values(): |
|
83 |
# There's an annoying API inconsistency here:
|
|
84 |
# If you have multiple IPs, local-ipv4s is a list.
|
|
85 |
# If you only have one, local-ipv4s is a string.
|
|
86 |
# Who knew?
|
|
87 |
if type(device['local-ipv4s']) is str: |
|
88 |
# Only do dhcp, don't try to assign addresses
|
|
89 |
to_configure = [device['local-ipv4s']] |
|
90 |
else: |
|
91 |
to_configure = list(device['local-ipv4s']) |
|
92 |
device_number = int(device['device-number']) |
|
93 |
||
94 |
if address_of(device_number) is None: |
|
95 |
# If the device was not autoconfigured, do so now.
|
|
96 |
log.info("Device eth%i not configured, starting dhcpd" % device_number) |
|
97 |
subprocess.check_call(['/sbin/dhcpcd', 'eth%i' % device_number]) |
|
98 |
||
99 |
# Horrible hack to route return packets on the correct interface
|
|
100 |
# See http://unix.stackexchange.com/a/4421/933
|
|
101 |
subprocess.check_call( |
|
102 |
['/sbin/ip', 'rule', 'add', 'fwmark', str(device_number), 'table', str(device_number)]) |
|
103 |
subprocess.check_call( |
|
104 |
['/sbin/ip', 'route', 'add', '0.0.0.0/0', 'table', str(device_number), 'dev', |
|
105 |
'eth%i' % device_number, 'via', guess_gateway(device_number)]) |
|
4
by Luke Faraone
Create an iptables rule for the primary IP, too. |
106 |
subprocess.check_call( |
107 |
['/sbin/iptables', '-t', 'mangle', '-A', 'OUTPUT', '-m', 'conntrack', '--ctorigdst', |
|
108 |
address_of(device_number), '-j', 'MARK', '--set-mark', str(device_number)]) |
|
109 |
||
1
by Luke Faraone
Initial import of configure-cloud-interfaces. |
110 |
|
111 |
to_configure.remove(address_of(device_number)) |
|
112 |
||
113 |
for (count, ip) in enumerate(to_configure): |
|
114 |
# Configure the IP via a virtual interface
|
|
115 |
device = "eth%i:%i" % (device_number, count) |
|
116 |
log.info("Configuring %s with IP %s" % (device, ip)) |
|
117 |
subprocess.check_call(['/sbin/ifconfig', device, ip]) |
|
118 |
subprocess.check_call( |
|
119 |
['/sbin/iptables', '-t', 'mangle', '-A', 'OUTPUT', '-m', 'conntrack', '--ctorigdst', |
|
120 |
ip, '-j', 'MARK', '--set-mark', str(device_number)]) |
|
121 |
||
122 |
for throwaway in range(2): |
|
123 |
# Don't freak out if this doens't work.
|
|
124 |
subprocess.call( |
|
125 |
['/sbin/ip', 'route', 'del', '10.0.0.0/8']) |
|
126 |
||
127 |
log.info("Finished.") |