8fecbc79c11e974d1755041ae9b07fe7880270e6
[sliver-openvswitch.git] / python / ovs / socket_util.py
1 # Copyright (c) 2010, 2012 Nicira, Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at:
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import errno
16 import os
17 import select
18 import socket
19 import sys
20
21 import ovs.fatal_signal
22 import ovs.poller
23 import ovs.vlog
24
25 vlog = ovs.vlog.Vlog("socket_util")
26
27
28 def make_unix_socket(style, nonblock, bind_path, connect_path):
29     """Creates a Unix domain socket in the given 'style' (either
30     socket.SOCK_DGRAM or socket.SOCK_STREAM) that is bound to 'bind_path' (if
31     'bind_path' is not None) and connected to 'connect_path' (if 'connect_path'
32     is not None).  If 'nonblock' is true, the socket is made non-blocking.
33
34     Returns (error, socket): on success 'error' is 0 and 'socket' is a new
35     socket object, on failure 'error' is a positive errno value and 'socket' is
36     None."""
37
38     try:
39         sock = socket.socket(socket.AF_UNIX, style)
40     except socket.error, e:
41         return get_exception_errno(e), None
42
43     try:
44         if nonblock:
45             set_nonblocking(sock)
46         if bind_path is not None:
47             # Delete bind_path but ignore ENOENT.
48             try:
49                 os.unlink(bind_path)
50             except OSError, e:
51                 if e.errno != errno.ENOENT:
52                     return e.errno, None
53
54             ovs.fatal_signal.add_file_to_unlink(bind_path)
55             sock.bind(bind_path)
56
57             try:
58                 if sys.hexversion >= 0x02060000:
59                     os.fchmod(sock.fileno(), 0700)
60                 else:
61                     os.chmod("/dev/fd/%d" % sock.fileno(), 0700)
62             except OSError, e:
63                 pass
64         if connect_path is not None:
65             try:
66                 sock.connect(connect_path)
67             except socket.error, e:
68                 if get_exception_errno(e) != errno.EINPROGRESS:
69                     raise
70         return 0, sock
71     except socket.error, e:
72         sock.close()
73         if bind_path is not None:
74             ovs.fatal_signal.unlink_file_now(bind_path)
75         return get_exception_errno(e), None
76
77
78 def check_connection_completion(sock):
79     p = ovs.poller.SelectPoll()
80     p.register(sock, ovs.poller.POLLOUT)
81     pfds = p.poll(0)
82     if len(pfds) == 1:
83         revents = pfds[0][1]
84         if revents & ovs.poller.POLLERR:
85             try:
86                 # The following should raise an exception.
87                 socket.send("\0", socket.MSG_DONTWAIT)
88
89                 # (Here's where we end up if it didn't.)
90                 # XXX rate-limit
91                 vlog.err("poll return POLLERR but send succeeded")
92                 return errno.EPROTO
93             except socket.error, e:
94                 return get_exception_errno(e)
95         else:
96             return 0
97     else:
98         return errno.EAGAIN
99
100
101 def inet_parse_active(target, default_port):
102     address = target.split(":")
103     host_name = address[0]
104     if not host_name:
105         raise ValueError("%s: bad peer name format" % target)
106     if len(address) >= 2:
107         port = int(address[1])
108     elif default_port:
109         port = default_port
110     else:
111         raise ValueError("%s: port number must be specified" % target)
112     return (host_name, port)
113
114
115 def inet_open_active(style, target, default_port, dscp):
116     address = inet_parse_active(target, default_port)
117     try:
118         sock = socket.socket(socket.AF_INET, style, 0)
119     except socket.error, e:
120         return get_exception_errno(e), None
121
122     try:
123         set_nonblocking(sock)
124         set_dscp(sock, dscp)
125         try:
126             sock.connect(address)
127         except socket.error, e:
128             if get_exception_errno(e) != errno.EINPROGRESS:
129                 raise
130         return 0, sock
131     except socket.error, e:
132         sock.close()
133         return get_exception_errno(e), None
134
135
136 def get_exception_errno(e):
137     """A lot of methods on Python socket objects raise socket.error, but that
138     exception is documented as having two completely different forms of
139     arguments: either a string or a (errno, string) tuple.  We only want the
140     errno."""
141     if type(e.args) == tuple:
142         return e.args[0]
143     else:
144         return errno.EPROTO
145
146
147 null_fd = -1
148
149
150 def get_null_fd():
151     """Returns a readable and writable fd for /dev/null, if successful,
152     otherwise a negative errno value.  The caller must not close the returned
153     fd (because the same fd will be handed out to subsequent callers)."""
154     global null_fd
155     if null_fd < 0:
156         try:
157             null_fd = os.open("/dev/null", os.O_RDWR)
158         except OSError, e:
159             vlog.err("could not open /dev/null: %s" % os.strerror(e.errno))
160             return -e.errno
161     return null_fd
162
163
164 def write_fully(fd, buf):
165     """Returns an (error, bytes_written) tuple where 'error' is 0 on success,
166     otherwise a positive errno value, and 'bytes_written' is the number of
167     bytes that were written before the error occurred.  'error' is 0 if and
168     only if 'bytes_written' is len(buf)."""
169     bytes_written = 0
170     if len(buf) == 0:
171         return 0, 0
172     while True:
173         try:
174             retval = os.write(fd, buf)
175             assert retval >= 0
176             if retval == len(buf):
177                 return 0, bytes_written + len(buf)
178             elif retval == 0:
179                 vlog.warn("write returned 0")
180                 return errno.EPROTO, bytes_written
181             else:
182                 bytes_written += retval
183                 buf = buf[:retval]
184         except OSError, e:
185             return e.errno, bytes_written
186
187
188 def set_nonblocking(sock):
189     try:
190         sock.setblocking(0)
191     except socket.error, e:
192         vlog.err("could not set nonblocking mode on socket: %s"
193                  % os.strerror(get_exception_errno(e)))
194
195
196 def set_dscp(sock, dscp):
197     if dscp > 63:
198         raise ValueError("Invalid dscp %d" % dscp)
199     val = dscp << 2
200     sock.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, val)