2
# Copyright 2012 Canonical Ltd. This software is licensed under the
3
# GNU Lesser General Public License version 2.1 (see the file COPYING).
5
"""Display the ip address of a container."""
8
from contextlib import closing, contextmanager
11
from functools import wraps
19
# The namespace type to use with setns(2): in this case we want to
20
# reassociate this thread with the network namespace.
21
CLONE_NEWNET = 0x40000000
23
'not_connected': 'unable to find the container ip address',
24
'not_found': 'the container does not exist or is not running',
25
'not_installed': 'lxc does not seem to be installed',
26
'not_root': 'you must be root',
28
# The ioctl command to retrieve the interface address.
33
"""Parse the command line arguments."""
34
parser = argparse.ArgumentParser(description=__doc__)
36
'-n', '--name', required=True,
37
help='The name of the container. ')
40
help='Display the ip address of the specified network interface.')
41
namespace = parser.parse_args()
42
return namespace.name, namespace.interface
46
"""Return an OSError containing given `msg`."""
48
'{}: error: {}'.format(os.path.basename(sys.argv[0]), ERRORS[code]))
51
def _output(interfaces, ip_addresses, short):
52
"""Format the output displaying the ip addresses of the container."""
54
return ip_addresses[0]
55
interface_ip_map = zip(interfaces, ip_addresses)
56
return '\n'.join('{}: {}'.format(*i) for i in interface_ip_map)
59
def _load_library(name, loader=None):
60
"""Load a shared library into the process and return it.
62
Search the library `name` inside `/usr/lib` and, if *$DEB_HOST_MULTIARCH*
63
is retrievable, inside `/usr/lib/$DEB_HOST_MULTIARCH/`.
65
The optional argument `loader` is the callable used to load the library:
66
if None, `ctypes.cdll.LoadLibrary` is used.
68
Raise OSError if the library is not found.
71
loader = ctypes.cdll.LoadLibrary
73
return loader(os.path.join('/usr/lib/', name))
75
# Search the library in `/usr/lib/$DEB_HOST_MULTIARCH/`:
76
# see https://wiki.ubuntu.com/MultiarchSpec.
77
process = subprocess.Popen(
78
['dpkg-architecture', '-qDEB_HOST_MULTIARCH'],
79
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
80
if not process.returncode:
81
output, _ = process.communicate()
82
return loader(os.path.join('/usr/lib/', output.strip(), name))
86
def _wrap(function, error_code):
87
"""Add error handling to the given C `function`.
89
If the function returns an error, the wrapped function raises an
90
OSError using a message corresponding to the given `error_code`.
92
def errcheck(result, func, arguments):
94
raise _error(error_code)
96
function.errcheck = errcheck
101
def redirect_stderr(path):
102
"""Redirect system stderr to `path`.
104
This context manager does not use normal sys.std* Python redirection
105
because we also want to intercept and redirect stderr written by
106
underlying C functions called using ctypes.
108
fd = sys.stderr.fileno()
110
new_fd = os.open(path, os.O_WRONLY)
121
def root_required(func):
122
"""A decorator checking for current user effective id.
124
The decorated function is only executed if the current user is root.
125
Otherwise, an OSError is raised.
128
def decorated(*args, **kwargs):
130
raise _error('not_root')
131
return func(*args, **kwargs)
135
class SetNamespace(object):
136
"""A context manager to switch the network namespace for this thread.
138
A namespace is one of the entries in /proc/[pid]/ns/.
140
def __init__(self, pid, nstype=CLONE_NEWNET):
141
libc = ctypes.cdll.LoadLibrary('libc.so.6')
143
self._nstype = nstype
144
self._setns = _wrap(libc.setns, 'not_connected')
147
"""Switch the namespace."""
151
def __exit__(self, exc_type, exc_val, exc_tb):
152
"""Restore normal namespace."""
153
# To restore the namespace we use the file descriptor associated
154
# with the hosts's init process. In Linux the init pid is always 1.
159
fd = os.open('/proc/{}/ns/net'.format(pid), os.O_RDONLY)
161
raise _error('not_found')
162
self._setns(fd, self._nstype)
168
"""Return the pid of an LXC, given its `name`.
170
Raise OSError if LXC is not installed or the container is not found.
173
liblxc = _load_library('lxc/liblxc.so.0')
175
raise _error('not_installed')
176
get_init_pid = _wrap(liblxc.get_init_pid, 'not_found')
177
# Redirect the system stderr in order to get rid of the error raised by
178
# the underlying C function call if the container is not found.
179
with redirect_stderr('/dev/null'):
180
return get_init_pid(name)
184
def get_interfaces(pid, exclude=()):
185
"""Return a list of active net interfaces, given the container's `pid`.
187
Raise OSError if the container does not exist, is not running, or if
188
no interface is found.
190
path = '/proc/{}/root/sys/class/net/'.format(pid)
193
i for i in os.listdir(path)
194
if i not in exclude and
195
os.path.isdir(os.path.join(path, i))
198
raise _error('not_found')
200
raise _error('not_connected')
205
def get_ip_addresses(pid, interfaces):
206
"""Return ip addresses of LXC `interfaces`, given the container's `pid`.
208
Raise OSError if the container is not found or one the ip addresses
211
Note that `socket.gethostbyname` is not usable in this context: it uses
212
the system's dns resolver that by default does not resolve lxc names.
215
with SetNamespace(pid):
216
# Retrieve the ip address for the given network interface.
217
# Original from http://code.activestate.com/recipes/
218
# 439094-get-the-ip-address-associated-with-a-network-inter/
219
with closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as s:
220
for interface in interfaces:
221
# Slice the interface because the buffer size used to hold an
222
# interface name, including its terminating zero byte,
223
# is 16 in Linux (see /usr/include/linux/if.h).
224
packed = struct.pack('256s', interface[:15])
226
# Use the ioctl Unix routine to request SIOCGIFADDR.
227
binary_ip = fcntl.ioctl(
228
s.fileno(), SIOCGIFADDR, packed)[20:24]
229
# Convert the packet ipv4 address to its standard
230
# dotted-quad string representation.
231
ip = socket.inet_ntoa(binary_ip)
232
except (IOError, socket.error):
233
raise _error('not_connected')
234
ip_addresses.append(ip)
239
name, interface = _parse_args()
242
if interface is None:
243
interfaces = get_interfaces(pid, exclude=['lo'])
245
interfaces = [interface]
246
ip_addresses = get_ip_addresses(pid, interfaces)
247
except (KeyboardInterrupt, OSError) as err:
249
print _output(interfaces, ip_addresses, interface is not None)
252
if __name__ == '__main__':