From 1a8337f2993bb426c7965e06d63178e4e7e4078a Mon Sep 17 00:00:00 2001 From: Manuel Franceschini <livewire@google.com> Date: Fri, 13 Aug 2010 11:32:15 +0200 Subject: [PATCH] rapi.client, http.client: Format url correctly when using IPv6 This patch moves the FormatAddress helper function from daemon.py to netutils.py. This enables its use in http.client as well as in rapi.client. Furthermore this adds functionality to format IPv6 addresses according to RFC 3986. It is required for use of literal IPv6 addresses in URLs in pycurl. For some reason it worked also without the bracketing ("["<address>"]"), but we do not want to rely on that. Signed-off-by: Manuel Franceschini <livewire@google.com> Reviewed-by: Michael Hanselmann <hansmi@google.com> --- lib/daemon.py | 23 +++-------------------- lib/http/client.py | 8 +++++++- lib/netutils.py | 26 ++++++++++++++++++++++++++ lib/rapi/client.py | 9 ++++++++- test/ganeti.netutils_unittest.py | 30 ++++++++++++++++++++++++++++++ 5 files changed, 74 insertions(+), 22 deletions(-) diff --git a/lib/daemon.py b/lib/daemon.py index 1fd1b741e..86e6c02da 100644 --- a/lib/daemon.py +++ b/lib/daemon.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2006, 2007, 2008 Google Inc. +# Copyright (C) 2006, 2007, 2008, 2010 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -100,23 +100,6 @@ class GanetiBaseAsyncoreDispatcher(asyncore.dispatcher): return False -def FormatAddress(family, address): - """Format a client's address - - @type family: integer - @param family: socket family (one of socket.AF_*) - @type address: family specific (usually tuple) - @param address: address, as reported by this class - - """ - if family == socket.AF_INET and len(address) == 2: - return "%s:%d" % address - elif family == socket.AF_UNIX and len(address) == 3: - return "pid=%s, uid=%s, gid=%s" % address - else: - return str(address) - - class AsyncStreamServer(GanetiBaseAsyncoreDispatcher): """A stream server to use with asyncore. @@ -159,7 +142,7 @@ class AsyncStreamServer(GanetiBaseAsyncoreDispatcher): # is passed in from accept anyway client_address = netutils.GetSocketCredentials(connected_socket) logging.info("Accepted connection from %s", - FormatAddress(self.family, client_address)) + netutils.FormatAddress(self.family, client_address)) self.handle_connection(connected_socket, client_address) def handle_connection(self, connected_socket, client_address): @@ -290,7 +273,7 @@ class AsyncTerminatedMessageStream(asynchat.async_chat): def close_log(self): logging.info("Closing connection from %s", - FormatAddress(self.family, self.peer_address)) + netutils.FormatAddress(self.family, self.peer_address)) self.close() # this method is overriding an asyncore.dispatcher method diff --git a/lib/http/client.py b/lib/http/client.py index 8658aed45..891865f39 100644 --- a/lib/http/client.py +++ b/lib/http/client.py @@ -28,6 +28,7 @@ from cStringIO import StringIO from ganeti import http from ganeti import compat +from ganeti import netutils class HttpClientRequest(object): @@ -104,8 +105,13 @@ class HttpClientRequest(object): """Returns the full URL for this requests. """ + if netutils.IPAddress.IsValid(self.host): + family = netutils.IPAddress.GetAddressFamily(self.host) + address = netutils.FormatAddress(family, (self.host, self.port)) + else: + address = "%s:%s" % (self.host, self.port) # TODO: Support for non-SSL requests - return "https://%s:%s%s" % (self.host, self.port, self.path) + return "https://%s%s" % (address, self.path) @property def identity(self): diff --git a/lib/netutils.py b/lib/netutils.py index edbebb4e6..3ab985467 100644 --- a/lib/netutils.py +++ b/lib/netutils.py @@ -472,3 +472,29 @@ class IP6Address(IPAddress): address_int = (address_int << 16) + int(part or '0', 16) return address_int + +def FormatAddress(family, address): + """Format a socket address + + @type family: integer + @param family: socket family (one of socket.AF_*) + @type address: family specific (usually tuple) + @param address: address, as reported by this class + + """ + if family == socket.AF_UNIX and len(address) == 3: + return "pid=%s, uid=%s, gid=%s" % address + + if family in (socket.AF_INET, socket.AF_INET6) and len(address) == 2: + host, port = address + if family == socket.AF_INET6: + res = "[%s]" % host + else: + res = host + + if port is not None: + res += ":%s" % port + + return res + + raise errors.ParameterError(family, address) diff --git a/lib/rapi/client.py b/lib/rapi/client.py index 498983154..8c2c75c09 100644 --- a/lib/rapi/client.py +++ b/lib/rapi/client.py @@ -35,6 +35,7 @@ import logging import simplejson +import socket import urllib import threading import pycurl @@ -265,7 +266,13 @@ class GanetiRapiClient(object): self._curl_config_fn = curl_config_fn self._curl_factory = curl_factory - self._base_url = "https://%s:%s" % (host, port) + try: + socket.inet_pton(socket.AF_INET6, host) + address = "[%s]:%s" % (host, port) + except socket.error: + address = "%s:%s" % (host, port) + + self._base_url = "https://%s" % address if username is not None: if password is None: diff --git a/test/ganeti.netutils_unittest.py b/test/ganeti.netutils_unittest.py index 6e324439a..109fe44bf 100755 --- a/test/ganeti.netutils_unittest.py +++ b/test/ganeti.netutils_unittest.py @@ -385,5 +385,35 @@ class TestIP6TcpPingDeaf(unittest.TestCase, _BaseTcpPingDeafTest): _BaseTcpPingDeafTest.tearDown(self) +class TestFormatAddress(unittest.TestCase): + """Testcase for FormatAddress""" + + def testFormatAddressUnixSocket(self): + res1 = netutils.FormatAddress(socket.AF_UNIX, ("12352", 0, 0)) + self.assertEqual(res1, "pid=12352, uid=0, gid=0") + + def testFormatAddressIP4(self): + res1 = netutils.FormatAddress(socket.AF_INET, ("127.0.0.1", 1234)) + self.assertEqual(res1, "127.0.0.1:1234") + res2 = netutils.FormatAddress(socket.AF_INET, ("192.0.2.32", None)) + self.assertEqual(res2, "192.0.2.32") + + def testFormatAddressIP6(self): + res1 = netutils.FormatAddress(socket.AF_INET6, ("::1", 1234)) + self.assertEqual(res1, "[::1]:1234") + res2 = netutils.FormatAddress(socket.AF_INET6, ("::1", None)) + self.assertEqual(res2, "[::1]") + res2 = netutils.FormatAddress(socket.AF_INET6, ("2001:db8::beef", "80")) + self.assertEqual(res2, "[2001:db8::beef]:80") + + def testInvalidFormatAddress(self): + self.assertRaises(errors.ParameterError, + netutils.FormatAddress, None, ("::1", None)) + self.assertRaises(errors.ParameterError, + netutils.FormatAddress, socket.AF_INET, "127.0.0.1") + self.assertRaises(errors.ParameterError, + netutils.FormatAddress, socket.AF_INET, ("::1")) + + if __name__ == "__main__": testutils.GanetiTestProgram() -- GitLab