Statistics
| Branch: | Tag: | Revision:

root / lib / netutils.py @ 4008c8ed

History | View | Annotate | Download (8.5 kB)

1
#
2
#
3

    
4
# Copyright (C) 2010 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Ganeti network utility module.
23

24
This module holds functions that can be used in both daemons (all) and
25
the command line scripts.
26

27
"""
28

    
29

    
30
import errno
31
import re
32
import socket
33
import struct
34
import IN
35

    
36
from ganeti import constants
37
from ganeti import errors
38

    
39
# Structure definition for getsockopt(SOL_SOCKET, SO_PEERCRED, ...):
40
# struct ucred { pid_t pid; uid_t uid; gid_t gid; };
41
#
42
# The GNU C Library defines gid_t and uid_t to be "unsigned int" and
43
# pid_t to "int".
44
#
45
# IEEE Std 1003.1-2008:
46
# "nlink_t, uid_t, gid_t, and id_t shall be integer types"
47
# "blksize_t, pid_t, and ssize_t shall be signed integer types"
48
_STRUCT_UCRED = "iII"
49
_STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED)
50

    
51

    
52
def GetSocketCredentials(sock):
53
  """Returns the credentials of the foreign process connected to a socket.
54

55
  @param sock: Unix socket
56
  @rtype: tuple; (number, number, number)
57
  @return: The PID, UID and GID of the connected foreign process.
58

59
  """
60
  peercred = sock.getsockopt(socket.SOL_SOCKET, IN.SO_PEERCRED,
61
                             _STRUCT_UCRED_SIZE)
62
  return struct.unpack(_STRUCT_UCRED, peercred)
63

    
64

    
65
def GetHostInfo(name=None):
66
  """Lookup host name and raise an OpPrereqError for failures"""
67

    
68
  try:
69
    return HostInfo(name)
70
  except errors.ResolverError, err:
71
    raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
72
                               (err[0], err[2]), errors.ECODE_RESOLVER)
73

    
74

    
75
class HostInfo:
76
  """Class implementing resolver and hostname functionality
77

78
  """
79
  _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
80

    
81
  def __init__(self, name=None):
82
    """Initialize the host name object.
83

84
    If the name argument is not passed, it will use this system's
85
    name.
86

87
    """
88
    if name is None:
89
      name = self.SysName()
90

    
91
    self.query = name
92
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
93
    self.ip = self.ipaddrs[0]
94

    
95
  def ShortName(self):
96
    """Returns the hostname without domain.
97

98
    """
99
    return self.name.split('.')[0]
100

    
101
  @staticmethod
102
  def SysName():
103
    """Return the current system's name.
104

105
    This is simply a wrapper over C{socket.gethostname()}.
106

107
    """
108
    return socket.gethostname()
109

    
110
  @staticmethod
111
  def LookupHostname(hostname):
112
    """Look up hostname
113

114
    @type hostname: str
115
    @param hostname: hostname to look up
116

117
    @rtype: tuple
118
    @return: a tuple (name, aliases, ipaddrs) as returned by
119
        C{socket.gethostbyname_ex}
120
    @raise errors.ResolverError: in case of errors in resolving
121

122
    """
123
    try:
124
      result = socket.gethostbyname_ex(hostname)
125
    except (socket.gaierror, socket.herror, socket.error), err:
126
      # hostname not found in DNS, or other socket exception in the
127
      # (code, description format)
128
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
129

    
130
    return result
131

    
132
  @classmethod
133
  def NormalizeName(cls, hostname):
134
    """Validate and normalize the given hostname.
135

136
    @attention: the validation is a bit more relaxed than the standards
137
        require; most importantly, we allow underscores in names
138
    @raise errors.OpPrereqError: when the name is not valid
139

140
    """
141
    hostname = hostname.lower()
142
    if (not cls._VALID_NAME_RE.match(hostname) or
143
        # double-dots, meaning empty label
144
        ".." in hostname or
145
        # empty initial label
146
        hostname.startswith(".")):
147
      raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
148
                                 errors.ECODE_INVAL)
149
    if hostname.endswith("."):
150
      hostname = hostname.rstrip(".")
151
    return hostname
152

    
153

    
154
def _GenericIsValidIP(family, ip):
155
  """Generic internal version of ip validation.
156

157
  @type family: int
158
  @param family: socket.AF_INET | socket.AF_INET6
159
  @type ip: str
160
  @param ip: the address to be checked
161
  @rtype: boolean
162
  @return: True if ip is valid, False otherwise
163

164
  """
165
  try:
166
    socket.inet_pton(family, ip)
167
    return True
168
  except socket.error:
169
    return False
170

    
171

    
172
def IsValidIP4(ip):
173
  """Verifies an IPv4 address.
174

175
  This function checks if the given address is a valid IPv4 address.
176

177
  @type ip: str
178
  @param ip: the address to be checked
179
  @rtype: boolean
180
  @return: True if ip is valid, False otherwise
181

182
  """
183
  return _GenericIsValidIP(socket.AF_INET, ip)
184

    
185

    
186
def IsValidIP6(ip):
187
  """Verifies an IPv6 address.
188

189
  This function checks if the given address is a valid IPv6 address.
190

191
  @type ip: str
192
  @param ip: the address to be checked
193
  @rtype: boolean
194
  @return: True if ip is valid, False otherwise
195

196
  """
197
  return _GenericIsValidIP(socket.AF_INET6, ip)
198

    
199

    
200
def IsValidIP(ip):
201
  """Verifies an IP address.
202

203
  This function checks if the given IP address (both IPv4 and IPv6) is valid.
204

205
  @type ip: str
206
  @param ip: the address to be checked
207
  @rtype: boolean
208
  @return: True if ip is valid, False otherwise
209

210
  """
211
  return IsValidIP4(ip) or IsValidIP6(ip)
212

    
213

    
214
def GetAddressFamily(ip):
215
  """Get the address family of the given address.
216

217
  @type ip: str
218
  @param ip: ip address whose family will be returned
219
  @rtype: int
220
  @return: socket.AF_INET or socket.AF_INET6
221
  @raise errors.GenericError: for invalid addresses
222

223
  """
224
  if IsValidIP6(ip):
225
    return socket.AF_INET6
226
  elif IsValidIP4(ip):
227
    return socket.AF_INET
228
  else:
229
    raise errors.GenericError("Address %s not valid" % ip)
230

    
231

    
232
def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
233
  """Simple ping implementation using TCP connect(2).
234

235
  Check if the given IP is reachable by doing attempting a TCP connect
236
  to it.
237

238
  @type target: str
239
  @param target: the IP or hostname to ping
240
  @type port: int
241
  @param port: the port to connect to
242
  @type timeout: int
243
  @param timeout: the timeout on the connection attempt
244
  @type live_port_needed: boolean
245
  @param live_port_needed: whether a closed port will cause the
246
      function to return failure, as if there was a timeout
247
  @type source: str or None
248
  @param source: if specified, will cause the connect to be made
249
      from this specific source address; failures to bind other
250
      than C{EADDRNOTAVAIL} will be ignored
251

252
  """
253
  try:
254
    family = GetAddressFamily(target)
255
  except errors.GenericError:
256
    return False
257

    
258
  sock = socket.socket(family, socket.SOCK_STREAM)
259
  success = False
260

    
261
  if source is not None:
262
    try:
263
      sock.bind((source, 0))
264
    except socket.error, (errcode, _):
265
      if errcode == errno.EADDRNOTAVAIL:
266
        success = False
267

    
268
  sock.settimeout(timeout)
269

    
270
  try:
271
    sock.connect((target, port))
272
    sock.close()
273
    success = True
274
  except socket.timeout:
275
    success = False
276
  except socket.error, (errcode, _):
277
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
278

    
279
  return success
280

    
281

    
282
def OwnIpAddress(address):
283
  """Check if the current host has the the given IP address.
284

285
  This is done by trying to bind the given address. We return True if we
286
  succeed or false if a socket.error is raised.
287

288
  @type address: string
289
  @param address: the address to check
290
  @rtype: bool
291
  @return: True if we own the address
292

293
  """
294
  family = GetAddressFamily(address)
295
  s = socket.socket(family, socket.SOCK_DGRAM)
296
  success = False
297
  try:
298
    try:
299
      s.bind((address, 0))
300
      success = True
301
    except socket.error:
302
      success = False
303
  finally:
304
    s.close()
305
  return success
306

    
307

    
308
def GetDaemonPort(daemon_name):
309
  """Get the daemon port for this cluster.
310

311
  Note that this routine does not read a ganeti-specific file, but
312
  instead uses C{socket.getservbyname} to allow pre-customization of
313
  this parameter outside of Ganeti.
314

315
  @type daemon_name: string
316
  @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
317
  @rtype: int
318

319
  """
320
  if daemon_name not in constants.DAEMONS_PORTS:
321
    raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
322

    
323
  (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
324
  try:
325
    port = socket.getservbyname(daemon_name, proto)
326
  except socket.error:
327
    port = default_port
328

    
329
  return port