# Copyright (c) 2010, 2012, 2014, 2015 Nicira, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import errno import os import os.path import random import socket import sys import ovs.fatal_signal import ovs.poller import ovs.vlog if sys.platform == 'win32': import ovs.winutils as winutils import win32file vlog = ovs.vlog.Vlog("socket_util") def make_short_name(long_name): if long_name is None: return None long_name = os.path.abspath(long_name) long_dirname = os.path.dirname(long_name) tmpdir = os.getenv('TMPDIR', '/tmp') for x in range(0, 1000): link_name = \ '%s/ovs-un-py-%d-%d' % (tmpdir, random.randint(0, 10000), x) try: os.symlink(long_dirname, link_name) ovs.fatal_signal.add_file_to_unlink(link_name) return os.path.join(link_name, os.path.basename(long_name)) except OSError as e: if e.errno != errno.EEXIST: break raise Exception("Failed to create temporary symlink") def free_short_name(short_name): if short_name is None: return link_name = os.path.dirname(short_name) ovs.fatal_signal.unlink_file_now(link_name) def make_unix_socket(style, nonblock, bind_path, connect_path, short=False): """Creates a Unix domain socket in the given 'style' (either socket.SOCK_DGRAM or socket.SOCK_STREAM) that is bound to 'bind_path' (if 'bind_path' is not None) and connected to 'connect_path' (if 'connect_path' is not None). If 'nonblock' is true, the socket is made non-blocking. Returns (error, socket): on success 'error' is 0 and 'socket' is a new socket object, on failure 'error' is a positive errno value and 'socket' is None.""" try: sock = socket.socket(socket.AF_UNIX, style) except socket.error as e: return get_exception_errno(e), None try: if nonblock: set_nonblocking(sock) if bind_path is not None: # Delete bind_path but ignore ENOENT. try: os.unlink(bind_path) except OSError as e: if e.errno != errno.ENOENT: return e.errno, None ovs.fatal_signal.add_file_to_unlink(bind_path) sock.bind(bind_path) try: os.fchmod(sock.fileno(), 0o700) except OSError: pass if connect_path is not None: try: sock.connect(connect_path) except socket.error as e: if get_exception_errno(e) != errno.EINPROGRESS: raise return 0, sock except socket.error as e: sock.close() if (bind_path is not None and os.path.exists(bind_path)): ovs.fatal_signal.unlink_file_now(bind_path) eno = ovs.socket_util.get_exception_errno(e) if (eno == "AF_UNIX path too long" and os.uname()[0] == "Linux"): short_connect_path = None short_bind_path = None connect_dirfd = None bind_dirfd = None # Try workaround using /proc/self/fd if connect_path is not None: dirname = os.path.dirname(connect_path) basename = os.path.basename(connect_path) try: connect_dirfd = os.open(dirname, os.O_DIRECTORY | os.O_RDONLY) except OSError as err: return get_exception_errno(err), None short_connect_path = "/proc/self/fd/%d/%s" % (connect_dirfd, basename) if bind_path is not None: dirname = os.path.dirname(bind_path) basename = os.path.basename(bind_path) try: bind_dirfd = os.open(dirname, os.O_DIRECTORY | os.O_RDONLY) except OSError as err: return get_exception_errno(err), None short_bind_path = "/proc/self/fd/%d/%s" % (bind_dirfd, basename) try: return make_unix_socket(style, nonblock, short_bind_path, short_connect_path) finally: if connect_dirfd is not None: os.close(connect_dirfd) if bind_dirfd is not None: os.close(bind_dirfd) elif (eno == "AF_UNIX path too long"): if short: return get_exception_errno(e), None short_bind_path = None try: short_bind_path = make_short_name(bind_path) short_connect_path = make_short_name(connect_path) except: free_short_name(short_bind_path) return errno.ENAMETOOLONG, None try: return make_unix_socket(style, nonblock, short_bind_path, short_connect_path, short=True) finally: free_short_name(short_bind_path) free_short_name(short_connect_path) else: return get_exception_errno(e), None def check_connection_completion(sock): if sys.platform == "win32": p = ovs.poller.SelectPoll() event = winutils.get_new_event(None, False, True, None) # Receive notification of readiness for writing, of completed # connection or multipoint join operation, and of socket closure. win32file.WSAEventSelect(sock, event, win32file.FD_WRITE | win32file.FD_CONNECT | win32file.FD_CLOSE) p.register(event, ovs.poller.POLLOUT) else: p = ovs.poller.get_system_poll() p.register(sock, ovs.poller.POLLOUT) pfds = p.poll(0) if len(pfds) == 1: revents = pfds[0][1] if revents & ovs.poller.POLLERR or revents & ovs.poller.POLLHUP: try: # The following should raise an exception. sock.send("\0".encode(), socket.MSG_DONTWAIT) # (Here's where we end up if it didn't.) # XXX rate-limit vlog.err("poll return POLLERR but send succeeded") return errno.EPROTO except socket.error as e: return get_exception_errno(e) else: return 0 else: return errno.EAGAIN def is_valid_ipv4_address(address): try: socket.inet_pton(socket.AF_INET, address) except AttributeError: try: socket.inet_aton(address) except socket.error: return False except socket.error: return False return True def inet_parse_active(target, default_port): address = target.split(":") if len(address) >= 2: host_name = ":".join(address[0:-1]).lstrip('[').rstrip(']') port = int(address[-1]) else: if default_port: port = default_port else: raise ValueError("%s: port number must be specified" % target) host_name = address[0] if not host_name: raise ValueError("%s: bad peer name format" % target) return (host_name, port) def inet_create_socket_active(style, address): try: is_addr_inet = is_valid_ipv4_address(address[0]) if is_addr_inet: sock = socket.socket(socket.AF_INET, style, 0) family = socket.AF_INET else: sock = socket.socket(socket.AF_INET6, style, 0) family = socket.AF_INET6 except socket.error as e: return get_exception_errno(e), None return family, sock def inet_connect_active(sock, address, family, dscp): try: set_nonblocking(sock) set_dscp(sock, family, dscp) error = sock.connect_ex(address) if error not in (0, errno.EINPROGRESS, errno.EWOULDBLOCK): sock.close() return error return 0 except socket.error as e: sock.close() return get_exception_errno(e) def inet_open_active(style, target, default_port, dscp): address = inet_parse_active(target, default_port) family, sock = inet_create_socket_active(style, address) if sock is None: return family, sock error = inet_connect_active(sock, address, family, dscp) if error: return error, None return 0, sock def get_exception_errno(e): """A lot of methods on Python socket objects raise socket.error, but that exception is documented as having two completely different forms of arguments: either a string or a (errno, string) tuple. We only want the errno.""" if isinstance(e.args, tuple): return e.args[0] else: return errno.EPROTO null_fd = -1 def get_null_fd(): """Returns a readable and writable fd for /dev/null, if successful, otherwise a negative errno value. The caller must not close the returned fd (because the same fd will be handed out to subsequent callers).""" global null_fd if null_fd < 0: try: # os.devnull ensures compatibility with Windows, returns # '/dev/null' for Unix and 'nul' for Windows null_fd = os.open(os.devnull, os.O_RDWR) except OSError as e: vlog.err("could not open %s: %s" % (os.devnull, os.strerror(e.errno))) return -e.errno return null_fd def write_fully(fd, buf): """Returns an (error, bytes_written) tuple where 'error' is 0 on success, otherwise a positive errno value, and 'bytes_written' is the number of bytes that were written before the error occurred. 'error' is 0 if and only if 'bytes_written' is len(buf).""" bytes_written = 0 if len(buf) == 0: return 0, 0 if not isinstance(buf, bytes): buf = bytes(buf, 'utf-8') while True: try: retval = os.write(fd, buf) assert retval >= 0 if retval == len(buf): return 0, bytes_written + len(buf) elif retval == 0: vlog.warn("write returned 0") return errno.EPROTO, bytes_written else: bytes_written += retval buf = buf[:retval] except OSError as e: return e.errno, bytes_written def set_nonblocking(sock): try: sock.setblocking(0) except socket.error as e: vlog.err("could not set nonblocking mode on socket: %s" % os.strerror(get_exception_errno(e))) def set_dscp(sock, family, dscp): if dscp > 63: raise ValueError("Invalid dscp %d" % dscp) val = dscp << 2 if family == socket.AF_INET: sock.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, val) elif family == socket.AF_INET6: sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_TCLASS, val) else: raise ValueError('Invalid family %d' % family)