This is roughly socket2.diff from issue 1378, with a few changes applied
to ssl.py (no need to test whether we can dup any more).
Regular sockets no longer have a _base, but we still have explicit
reference counting of socket objects for the benefit of makefile();
using duplicate sockets won't work for SSLSocket.
diff --git a/Lib/socket.py b/Lib/socket.py
index 6a9a381..62eb82d 100644
--- a/Lib/socket.py
+++ b/Lib/socket.py
@@ -79,28 +79,14 @@
     __all__.append("errorTab")
 
 
-# True if os.dup() can duplicate socket descriptors.
-# (On Windows at least, os.dup only works on files)
-_can_dup_socket = hasattr(_socket.socket, "dup")
-
-if _can_dup_socket:
-    def fromfd(fd, family=AF_INET, type=SOCK_STREAM, proto=0):
-        nfd = os.dup(fd)
-        return socket(family, type, proto, fileno=nfd)
-
 class socket(_socket.socket):
 
     """A subclass of _socket.socket adding the makefile() method."""
 
     __slots__ = ["__weakref__", "_io_refs", "_closed"]
-    if not _can_dup_socket:
-        __slots__.append("_base")
 
     def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None):
-        if fileno is None:
-            _socket.socket.__init__(self, family, type, proto)
-        else:
-            _socket.socket.__init__(self, family, type, proto, fileno)
+        _socket.socket.__init__(self, family, type, proto, fileno)
         self._io_refs = 0
         self._closed = False
 
@@ -114,23 +100,29 @@
                                 s[7:])
         return s
 
+    def dup(self):
+        """dup() -> socket object
+
+        Return a new socket object connected to the same system resource.
+        """
+        fd = dup(self.fileno())
+        sock = self.__class__(self.family, self.type, self.proto, fileno=fd)
+        sock.settimeout(self.gettimeout())
+        return sock
+
     def accept(self):
-        """Wrap accept() to give the connection the right type."""
-        conn, addr = _socket.socket.accept(self)
-        fd = conn.fileno()
-        nfd = fd
-        if _can_dup_socket:
-            nfd = os.dup(fd)
-        wrapper = socket(self.family, self.type, self.proto, fileno=nfd)
-        if fd == nfd:
-            wrapper._base = conn  # Keep the base alive
-        else:
-            conn.close()
-        return wrapper, addr
+        """accept() -> (socket object, address info)
+
+        Wait for an incoming connection.  Return a new socket
+        representing the connection, and the address of the client.
+        For IP sockets, the address info is a pair (hostaddr, port).
+        """
+        fd, addr = self._accept()
+        return socket(self.family, self.type, self.proto, fileno=fd), addr
 
     def makefile(self, mode="r", buffering=None, *,
                  encoding=None, newline=None):
-        """Return an I/O stream connected to the socket.
+        """makefile(...) -> an I/O stream connected to the socket
 
         The arguments are as for io.open() after the filename,
         except the only mode characters supported are 'r', 'w' and 'b'.
@@ -184,21 +176,18 @@
 
     def close(self):
         self._closed = True
-        if self._io_refs < 1:
-            self._real_close()
-
-    # _real_close calls close on the _socket.socket base class.
-
-    if not _can_dup_socket:
-        def _real_close(self):
+        if self._io_refs <= 0:
             _socket.socket.close(self)
-            base = getattr(self, "_base", None)
-            if base is not None:
-                self._base = None
-                base.close()
-    else:
-        def _real_close(self):
-            _socket.socket.close(self)
+
+
+def fromfd(fd, family, type, proto=0):
+    """ fromfd(fd, family, type[, proto]) -> socket object
+
+    Create a socket object from a duplicate of the given file
+    descriptor.  The remaining arguments are the same as for socket().
+    """
+    nfd = dup(fd)
+    return socket(family, type, proto, nfd)
 
 
 class SocketIO(io.RawIOBase):
diff --git a/Lib/ssl.py b/Lib/ssl.py
index c2cfa31..9d63d12 100644
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -78,8 +78,8 @@
 from socket import socket, AF_INET, SOCK_STREAM, error
 from socket import getnameinfo as _getnameinfo
 from socket import error as socket_error
+from socket import dup as _dup
 import base64        # for DER-to-PEM translation
-_can_dup_socket = hasattr(socket, "dup")
 
 class SSLSocket(socket):
 
@@ -99,20 +99,11 @@
         if sock is not None:
             # copied this code from socket.accept()
             fd = sock.fileno()
-            nfd = fd
-            if _can_dup_socket:
-                nfd = os.dup(fd)
-            try:
-                socket.__init__(self, family=sock.family, type=sock.type,
-                                proto=sock.proto, fileno=nfd)
-            except:
-                if nfd != fd:
-                    os.close(nfd)
-            else:
-                if fd != nfd:
-                    sock.close()
-                    sock = None
-
+            nfd = _dup(fd)
+            socket.__init__(self, family=sock.family, type=sock.type,
+                            proto=sock.proto, fileno=nfd)
+            sock.close()
+            sock = None
         elif fileno is not None:
             socket.__init__(self, fileno=fileno)
         else:
diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py
index 82eb6e7..c01d998 100644
--- a/Lib/test/test_socket.py
+++ b/Lib/test/test_socket.py
@@ -575,6 +575,15 @@
     def _testFromFd(self):
         self.serv_conn.send(MSG)
 
+    def testDup(self):
+        # Testing dup()
+        sock = self.cli_conn.dup()
+        msg = sock.recv(1024)
+        self.assertEqual(msg, MSG)
+
+    def _testDup(self):
+        self.serv_conn.send(MSG)
+
     def testShutdown(self):
         # Testing shutdown()
         msg = self.cli_conn.recv(1024)