~ubuntu-branches/ubuntu/quantal/lxc/quantal-201206191543

« back to all changes in this revision

Viewing changes to debian/local/lxc-ip

  • Committer: Package Import Robot
  • Author(s): Stéphane Graber
  • Date: 2012-05-18 19:05:44 UTC
  • Revision ID: package-import@ubuntu.com-20120518190544-d2ejphrownapveg1
Tags: 0.8.0~rc1-4ubuntu8
* Update lxc-ubuntu:
  - Update list of extra packages for debootstrap to only include vim
    and ssh. The others were only relevant when we were still using the
    minbase variant. (LP: #996839)
  - Drop any hardcoded Ubuntu version check and replace by feature
    checks instead.
  - Format lxc-ubuntu to consistently use 4-spaces indent instead of
    mixed spaces/tabs.
  - Update default /etc/network/interfaces to include the header.
  - Update default /etc/hosts to match that of a regular Ubuntu system.
  - Drop support for end-of-life releases (gutsy on sparc).
  - Make sure /etc/resolv.conf is valid before running any apt command.
  - Update template help message for release and arch parameters.
  - Switch default Ubuntu version from lucid to precise.
* Update lxc-start-ephemeral:
  - Remove lxc-ip and replace it by a call to "ip netns" until we have
    an extended lxc-attach we can use for that.
  - Fix a race in lxc-start-ephemeral where the container isn't yet
    running when trying to get its IPs.
  - Update a few calls so that lxc-start-ephemeral can be called as a
    user (ensure consistent usage of sudo across the script).
* Add new lxc-default-with-nesting apparmor profile, allowing nested
  containers.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
 
# Copyright 2012 Canonical Ltd.  This software is licensed under the
3
 
# GNU Lesser General Public License version 2.1 (see the file COPYING).
4
 
 
5
 
"""Display the ip address of a container."""
6
 
 
7
 
import argparse
8
 
from contextlib import closing, contextmanager
9
 
import ctypes
10
 
import fcntl
11
 
from functools import wraps
12
 
import os
13
 
import socket
14
 
import struct
15
 
import subprocess
16
 
import sys
17
 
 
18
 
 
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
22
 
ERRORS = {
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',
27
 
    }
28
 
# The ioctl command to retrieve the interface address.
29
 
SIOCGIFADDR = 0x8915
30
 
 
31
 
 
32
 
def _parse_args():
33
 
    """Parse the command line arguments."""
34
 
    parser = argparse.ArgumentParser(description=__doc__)
35
 
    parser.add_argument(
36
 
        '-n', '--name', required=True,
37
 
        help='The name of the container. ')
38
 
    parser.add_argument(
39
 
        '-i', '--interface',
40
 
        help='Display the ip address of the specified network interface.')
41
 
    namespace = parser.parse_args()
42
 
    return namespace.name, namespace.interface
43
 
 
44
 
 
45
 
def _error(code):
46
 
    """Return an OSError containing given `msg`."""
47
 
    return OSError(
48
 
        '{}: error: {}'.format(os.path.basename(sys.argv[0]), ERRORS[code]))
49
 
 
50
 
 
51
 
def _output(interfaces, ip_addresses, short):
52
 
    """Format the output displaying the ip addresses of the container."""
53
 
    if short:
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)
57
 
 
58
 
 
59
 
def _load_library(name, loader=None):
60
 
    """Load a shared library into the process and return it.
61
 
 
62
 
    Search the library `name` inside `/usr/lib` and, if *$DEB_HOST_MULTIARCH*
63
 
    is retrievable, inside `/usr/lib/$DEB_HOST_MULTIARCH/`.
64
 
 
65
 
    The optional argument `loader` is the callable used to load the library:
66
 
    if None, `ctypes.cdll.LoadLibrary` is used.
67
 
 
68
 
    Raise OSError if the library is not found.
69
 
    """
70
 
    if loader is None:
71
 
        loader = ctypes.cdll.LoadLibrary
72
 
    try:
73
 
        return loader(os.path.join('/usr/lib/', name))
74
 
    except OSError:
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))
83
 
        raise
84
 
 
85
 
 
86
 
def _wrap(function, error_code):
87
 
    """Add error handling to the given C `function`.
88
 
 
89
 
    If the function returns an error, the wrapped function raises an
90
 
    OSError using a message corresponding to the given `error_code`.
91
 
    """
92
 
    def errcheck(result, func, arguments):
93
 
        if result < 0:
94
 
            raise _error(error_code)
95
 
        return result
96
 
    function.errcheck = errcheck
97
 
    return function
98
 
 
99
 
 
100
 
@contextmanager
101
 
def redirect_stderr(path):
102
 
    """Redirect system stderr to `path`.
103
 
 
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.
107
 
    """
108
 
    fd = sys.stderr.fileno()
109
 
    backup = os.dup(fd)
110
 
    new_fd = os.open(path, os.O_WRONLY)
111
 
    sys.stderr.flush()
112
 
    os.dup2(new_fd, fd)
113
 
    os.close(new_fd)
114
 
    try:
115
 
        yield
116
 
    finally:
117
 
        sys.stderr.flush()
118
 
        os.dup2(backup, fd)
119
 
 
120
 
 
121
 
def root_required(func):
122
 
    """A decorator checking for current user effective id.
123
 
 
124
 
    The decorated function is only executed if the current user is root.
125
 
    Otherwise, an OSError is raised.
126
 
    """
127
 
    @wraps(func)
128
 
    def decorated(*args, **kwargs):
129
 
        if os.geteuid():
130
 
            raise _error('not_root')
131
 
        return func(*args, **kwargs)
132
 
    return decorated
133
 
 
134
 
 
135
 
class SetNamespace(object):
136
 
    """A context manager to switch the network namespace for this thread.
137
 
 
138
 
    A namespace is one of the entries in /proc/[pid]/ns/.
139
 
    """
140
 
    def __init__(self, pid, nstype=CLONE_NEWNET):
141
 
        libc = ctypes.cdll.LoadLibrary('libc.so.6')
142
 
        self._pid = pid
143
 
        self._nstype = nstype
144
 
        self._setns = _wrap(libc.setns, 'not_connected')
145
 
 
146
 
    def __enter__(self):
147
 
        """Switch the namespace."""
148
 
        self.set(self._pid)
149
 
        return self
150
 
 
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.
155
 
        self.set(1)
156
 
 
157
 
    def set(self, pid):
158
 
        try:
159
 
            fd = os.open('/proc/{}/ns/net'.format(pid), os.O_RDONLY)
160
 
        except OSError:
161
 
            raise _error('not_found')
162
 
        self._setns(fd, self._nstype)
163
 
        os.close(fd)
164
 
 
165
 
 
166
 
@root_required
167
 
def get_pid(name):
168
 
    """Return the pid of an LXC, given its `name`.
169
 
 
170
 
    Raise OSError if LXC is not installed or the container is not found.
171
 
    """
172
 
    try:
173
 
        liblxc = _load_library('lxc/liblxc.so.0')
174
 
    except OSError:
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)
181
 
 
182
 
 
183
 
@root_required
184
 
def get_interfaces(pid, exclude=()):
185
 
    """Return a list of active net interfaces, given the container's `pid`.
186
 
 
187
 
    Raise OSError if the container does not exist, is not running, or if
188
 
    no interface is found.
189
 
    """
190
 
    path = '/proc/{}/root/sys/class/net/'.format(pid)
191
 
    try:
192
 
        interfaces = [
193
 
            i for i in os.listdir(path)
194
 
            if i not in exclude and
195
 
            os.path.isdir(os.path.join(path, i))
196
 
            ]
197
 
    except OSError:
198
 
        raise _error('not_found')
199
 
    if not interfaces:
200
 
        raise _error('not_connected')
201
 
    return interfaces
202
 
 
203
 
 
204
 
@root_required
205
 
def get_ip_addresses(pid, interfaces):
206
 
    """Return ip addresses of LXC `interfaces`, given the container's `pid`.
207
 
 
208
 
    Raise OSError if the container is not found or one the ip addresses
209
 
    is not retrievable.
210
 
 
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.
213
 
    """
214
 
    ip_addresses = []
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])
225
 
                try:
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)
235
 
    return ip_addresses
236
 
 
237
 
 
238
 
def main():
239
 
    name, interface = _parse_args()
240
 
    try:
241
 
        pid = get_pid(name)
242
 
        if interface is None:
243
 
            interfaces = get_interfaces(pid, exclude=['lo'])
244
 
        else:
245
 
            interfaces = [interface]
246
 
        ip_addresses = get_ip_addresses(pid, interfaces)
247
 
    except (KeyboardInterrupt, OSError) as err:
248
 
        return err
249
 
    print _output(interfaces, ip_addresses, interface is not None)
250
 
 
251
 
 
252
 
if __name__ == '__main__':
253
 
    sys.exit(main())