1# -*- coding: utf-8 -
2#
3# This file is part of gunicorn released under the MIT license.
4# See the NOTICE for more information.
5
6import errno
7import os
8import socket
9import stat
10import sys
11import time
12
13from gunicorn import util
14from gunicorn.six import string_types
15
16
17class BaseSocket(object):
18
19    def __init__(self, address, conf, log, fd=None):
20        self.log = log
21        self.conf = conf
22
23        self.cfg_addr = address
24        if fd is None:
25            sock = socket.socket(self.FAMILY, socket.SOCK_STREAM)
26            bound = False
27        else:
28            sock = socket.fromfd(fd, self.FAMILY, socket.SOCK_STREAM)
29            os.close(fd)
30            bound = True
31
32        self.sock = self.set_options(sock, bound=bound)
33
34    def __str__(self):
35        return "<socket %d>" % self.sock.fileno()
36
37    def __getattr__(self, name):
38        return getattr(self.sock, name)
39
40    def set_options(self, sock, bound=False):
41        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
42        if (self.conf.reuse_port
43            and hasattr(socket, 'SO_REUSEPORT')):  # pragma: no cover
44            try:
45                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
46            except socket.error as err:
47                if err.errno not in (errno.ENOPROTOOPT, errno.EINVAL):
48                    raise
49        if not bound:
50            self.bind(sock)
51        sock.setblocking(0)
52
53        # make sure that the socket can be inherited
54        if hasattr(sock, "set_inheritable"):
55            sock.set_inheritable(True)
56
57        sock.listen(self.conf.backlog)
58        return sock
59
60    def bind(self, sock):
61        sock.bind(self.cfg_addr)
62
63    def close(self):
64        if self.sock is None:
65            return
66
67        try:
68            self.sock.close()
69        except socket.error as e:
70            self.log.info("Error while closing socket %s", str(e))
71
72        self.sock = None
73
74
75class TCPSocket(BaseSocket):
76
77    FAMILY = socket.AF_INET
78
79    def __str__(self):
80        if self.conf.is_ssl:
81            scheme = "https"
82        else:
83            scheme = "http"
84
85        addr = self.sock.getsockname()
86        return "%s://%s:%d" % (scheme, addr[0], addr[1])
87
88    def set_options(self, sock, bound=False):
89        sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
90        return super(TCPSocket, self).set_options(sock, bound=bound)
91
92
93class TCP6Socket(TCPSocket):
94
95    FAMILY = socket.AF_INET6
96
97    def __str__(self):
98        (host, port, _, _) = self.sock.getsockname()
99        return "http://[%s]:%d" % (host, port)
100
101
102class UnixSocket(BaseSocket):
103
104    FAMILY = socket.AF_UNIX
105
106    def __init__(self, addr, conf, log, fd=None):
107        if fd is None:
108            try:
109                st = os.stat(addr)
110            except OSError as e:
111                if e.args[0] != errno.ENOENT:
112                    raise
113            else:
114                if stat.S_ISSOCK(st.st_mode):
115                    os.remove(addr)
116                else:
117                    raise ValueError("%r is not a socket" % addr)
118        super(UnixSocket, self).__init__(addr, conf, log, fd=fd)
119
120    def __str__(self):
121        return "unix:%s" % self.cfg_addr
122
123    def bind(self, sock):
124        old_umask = os.umask(self.conf.umask)
125        sock.bind(self.cfg_addr)
126        util.chown(self.cfg_addr, self.conf.uid, self.conf.gid)
127        os.umask(old_umask)
128
129
130def _sock_type(addr):
131    if isinstance(addr, tuple):
132        if util.is_ipv6(addr[0]):
133            sock_type = TCP6Socket
134        else:
135            sock_type = TCPSocket
136    elif isinstance(addr, string_types):
137        sock_type = UnixSocket
138    else:
139        raise TypeError("Unable to create socket from: %r" % addr)
140    return sock_type
141
142
143def create_sockets(conf, log, fds=None):
144    """
145    Create a new socket for the configured addresses or file descriptors.
146
147    If a configured address is a tuple then a TCP socket is created.
148    If it is a string, a Unix socket is created. Otherwise, a TypeError is
149    raised.
150    """
151    listeners = []
152
153    # get it only once
154    laddr = conf.address
155
156    # check ssl config early to raise the error on startup
157    # only the certfile is needed since it can contains the keyfile
158    if conf.certfile and not os.path.exists(conf.certfile):
159        raise ValueError('certfile "%s" does not exist' % conf.certfile)
160
161    if conf.keyfile and not os.path.exists(conf.keyfile):
162        raise ValueError('keyfile "%s" does not exist' % conf.keyfile)
163
164    # sockets are already bound
165    if fds is not None:
166        for fd in fds:
167            sock = socket.fromfd(fd, socket.AF_UNIX, socket.SOCK_STREAM)
168            sock_name = sock.getsockname()
169            sock_type = _sock_type(sock_name)
170            listener = sock_type(sock_name, conf, log, fd=fd)
171            listeners.append(listener)
172
173        return listeners
174
175    # no sockets is bound, first initialization of gunicorn in this env.
176    for addr in laddr:
177        sock_type = _sock_type(addr)
178        sock = None
179        for i in range(5):
180            try:
181                sock = sock_type(addr, conf, log)
182            except socket.error as e:
183                if e.args[0] == errno.EADDRINUSE:
184                    log.error("Connection in use: %s", str(addr))
185                if e.args[0] == errno.EADDRNOTAVAIL:
186                    log.error("Invalid address: %s", str(addr))
187                if i < 5:
188                    msg = "connection to {addr} failed: {error}"
189                    log.debug(msg.format(addr=str(addr), error=str(e)))
190                    log.error("Retrying in 1 second.")
191                    time.sleep(1)
192            else:
193                break
194
195        if sock is None:
196            log.error("Can't connect to %s", str(addr))
197            sys.exit(1)
198
199        listeners.append(sock)
200
201    return listeners
202
203
204def close_sockets(listeners, unlink=True):
205    for sock in listeners:
206        sock_name = sock.getsockname()
207        sock.close()
208        if unlink and _sock_type(sock_name) is UnixSocket:
209            os.unlink(sock_name)
210