Bug 1310197 - Implement fast open nspr part. r=mcmanus,bagder
authorDragana Damjanovic <dd.mozilla@gmail.com>
Tue, 09 May 2017 13:33:18 +0200
changeset 4725 f39f28732459374ed4fa5a5a5b5b03d81c5d5aac
parent 4724 8cc8b8314f99d6e2edb04801faeb18ba89ab8618
child 4726 4341e6258d2c4a3e8421618a6f41f09f554c78d9
push id244
push userkaie@kuix.de
push dateTue, 09 May 2017 11:33:02 +0000
reviewersmcmanus, bagder
bugs1310197
Bug 1310197 - Implement fast open nspr part. r=mcmanus,bagder
configure.in
pr/include/md/_win95.h
pr/include/private/primpl.h
pr/src/io/prio.c
pr/src/io/prsocket.c
pr/src/md/windows/w95sock.c
pr/src/pthreads/ptio.c
--- a/configure.in
+++ b/configure.in
@@ -1379,16 +1379,21 @@ case "$target" in
     ;;
 
 *-darwin*)
     AC_DEFINE(XP_UNIX)
     AC_DEFINE(DARWIN)
     AC_DEFINE(HAVE_BSD_FLOCK)
     AC_DEFINE(HAVE_SOCKLEN_T)
     AC_DEFINE(HAVE_POINTER_LOCALTIME_R)
+    changequote(,)
+    HOST_DARWIN_MAJOR=`echo "$build_os" | sed -E -e 's/^darwin([0-9]+).*$/\1/'`
+    changequote([,])
+    if test "$HOST_DARWIN_MAJOR" -ge 15 ; then
+        AC_DEFINE(HAS_CONNECTX)
     AS='$(CC) -x assembler-with-cpp'
     CFLAGS="$CFLAGS -Wall -fno-common"
     case "${target_cpu}" in
         arm*)
             CPU_ARCH=arm
             ;;
         i*86*|x86_64)
             if test -n "$USE_64"; then
--- a/pr/include/md/_win95.h
+++ b/pr/include/md/_win95.h
@@ -285,16 +285,19 @@ extern void _MD_MakeNonblock(PRFileDesc 
 #define _MD_MAKE_NONBLOCK             _MD_MakeNonblock
 #define _MD_INIT_FD_INHERITABLE       _PR_MD_INIT_FD_INHERITABLE
 #define _MD_QUERY_FD_INHERITABLE      _PR_MD_QUERY_FD_INHERITABLE
 #define _MD_SHUTDOWN                  _PR_MD_SHUTDOWN
 #define _MD_LISTEN                    _PR_MD_LISTEN
 extern PRInt32 _MD_CloseSocket(PROsfd osfd);
 #define _MD_CLOSE_SOCKET              _MD_CloseSocket
 #define _MD_SENDTO                    _PR_MD_SENDTO
+#ifdef _WIN64
+#define _MD_TCPSENDTO                 _PR_MD_TCPSENDTO
+#endif
 #define _MD_RECVFROM                  _PR_MD_RECVFROM
 #define _MD_SOCKETPAIR(s, type, proto, sv) -1
 #define _MD_GETSOCKNAME               _PR_MD_GETSOCKNAME
 #define _MD_GETPEERNAME               _PR_MD_GETPEERNAME
 #define _MD_GETSOCKOPT                _PR_MD_GETSOCKOPT
 #define _MD_SETSOCKOPT                _PR_MD_SETSOCKOPT
 #define _MD_SET_FD_INHERITABLE        _PR_MD_SET_FD_INHERITABLE
 #define _MD_SELECT                    select
--- a/pr/include/private/primpl.h
+++ b/pr/include/private/primpl.h
@@ -1220,16 +1220,23 @@ extern PRInt32 _PR_MD_RECVFROM(
     PRNetAddr *addr, PRUint32 *addrlen, PRIntervalTime timeout);
 #define    _PR_MD_RECVFROM _MD_RECVFROM
 
 extern PRInt32 _PR_MD_SENDTO(
     PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
     const PRNetAddr *addr, PRUint32 addrlen, PRIntervalTime timeout);
 #define    _PR_MD_SENDTO _MD_SENDTO
 
+#if defined(_WIN64) && defined(WIN95)
+extern PRInt32 _PR_MD_TCPSENDTO(
+    PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+    const PRNetAddr *addr, PRUint32 addrlen, PRIntervalTime timeout);
+#define    _PR_MD_TCPSENDTO _MD_TCPSENDTO
+#endif
+
 extern PRInt32 _PR_MD_SOCKETPAIR(int af, int type, int flags, PROsfd *osfd);
 #define    _PR_MD_SOCKETPAIR _MD_SOCKETPAIR
 
 extern PROsfd _PR_MD_SOCKET(int af, int type, int flags);
 #define    _PR_MD_SOCKET _MD_SOCKET
 
 extern PRInt32 _PR_MD_SOCKETAVAILABLE(PRFileDesc *fd);
 #define    _PR_MD_SOCKETAVAILABLE _MD_SOCKETAVAILABLE
@@ -1742,16 +1749,28 @@ struct PRFilePrivate {
                          * append mode.  See Bugzilla 4090, 276330. */
 #endif
     _MDFileDesc md;
 #ifdef _PR_NEED_SECRET_AF
     PRUint16 af;        /* If the platform's implementation of accept()
                          * requires knowing the address family of the 
 			 * socket, we save the address family here. */
 #endif
+
+#if defined(_WIN64)
+    /* This is necessary for TCP Fast Open. TCP Fast Open in windows must
+     * use ConnectEx function which uses OVERLAPPED. TCPSendTo will call
+     * ConnectEx to send fast open data. If ConnectEx returns
+     * ERROR_IO_PENDING we need to save OVERLAPPED structure and we will
+     * use it in ConnectContinue to get the final result of ConnectEx.
+     */
+    PRBool alreadyConnected;
+    PRBool overlappedActive;
+    OVERLAPPED ol;
+#endif
 };
 
 #ifdef _WIN64
 #define PR_PRIdOSFD "lld"       /* for printing PROsfd */
 #define PR_PRIxOSFD "llx"
 #define PR_SCNdOSFD "lld"       /* for scanning PROsfd */
 #define PR_SCNxOSFD "llx"
 #else
--- a/pr/src/io/prio.c
+++ b/pr/src/io/prio.c
@@ -114,16 +114,20 @@ PR_IMPLEMENT(PRFileDesc*) PR_AllocFileDe
 	PR_ASSERT(osfd < FD_SETSIZE);
 #endif
     fd = _PR_Getfd();
     if (fd) {
         /* Initialize the members of PRFileDesc and PRFilePrivate */
         fd->methods = methods;
         fd->secret->state = _PR_FILEDESC_OPEN;
 	fd->secret->md.osfd = osfd;
+#if defined(_WIN64)
+        fd->secret->alreadyConnected = PR_FALSE;
+        fd->secret->overlappedActive = PR_FALSE;
+#endif
         _PR_MD_INIT_FILEDESC(fd);
     } else {
 	    PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
     }
 
     return fd;
 }
 
--- a/pr/src/io/prsocket.c
+++ b/pr/src/io/prsocket.c
@@ -2,16 +2,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "primpl.h"
 
 #include <string.h>
 
+#if defined(_WIN64)
+#ifndef SO_UPDATE_CONNECT_CONTEXT
+#define SO_UPDATE_CONNECT_CONTEXT 0x7010
+#endif
+#endif
+
 /************************************************************************/
 
 /* These two functions are only used in assertions. */
 #if defined(DEBUG)
 
 PRBool IsValidNetAddr(const PRNetAddr *addr)
 {
     if ((addr != NULL)
@@ -299,16 +305,58 @@ static PRStatus PR_CALLBACK SocketConnec
             _PR_MD_MAP_CONNECT_ERROR(err);
         } else {
             PR_SetError(PR_UNKNOWN_ERROR, 0);
         }
         return PR_FAILURE;
     }
 
     PR_ASSERT(out_flags & PR_POLL_WRITE);
+
+#if defined(_WIN64)
+    if (fd->secret->alreadyConnected) {
+        fd->secret->alreadyConnected = PR_FALSE;
+    }
+    // TCP Fast Open on Windows must use ConnectEx, which uses overlapped
+    // input/output.
+    // To get result we need to use GetOverlappedResult.
+    if (fd->secret->overlappedActive) {
+        PR_ASSERT(fd->secret->nonblocking);
+        PRInt32 rvSent;
+        if (GetOverlappedResult(osfd, &fd->secret->ol, &rvSent, FALSE) == TRUE) {
+            fd->secret->overlappedActive = FALSE;
+            PR_LOG(_pr_io_lm, PR_LOG_MIN,
+               ("SocketConnectContinue GetOverlappedResult succeeded\n"));
+            // When ConnectEx is used, all previously set socket options and
+            // property are not enabled and to enable them
+            // SO_UPDATE_CONNECT_CONTEXT option need to be set.
+            if (setsockopt((SOCKET)osfd, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, NULL, 0) != 0) {
+                err = WSAGetLastError();
+                PR_LOG(_pr_io_lm, PR_LOG_MIN,
+                       ("SocketConnectContinue setting SO_UPDATE_CONNECT_CONTEXT failed %d\n", err));
+                _PR_MD_MAP_SETSOCKOPT_ERROR(err);
+                return PR_FAILURE;
+            }
+            return PR_SUCCESS;
+        } else {
+            err = WSAGetLastError();
+            PR_LOG(_pr_io_lm, PR_LOG_MIN,
+               ("SocketConnectContinue GetOverlappedResult failed %d\n", err));
+            if (err != ERROR_IO_PENDING) {
+                _PR_MD_MAP_CONNECT_ERROR(err);
+                fd->secret->overlappedActive = FALSE;
+                return PR_FAILURE;
+            } else {
+                PR_SetError(PR_IN_PROGRESS_ERROR, 0);
+                return PR_FAILURE;
+            }
+        }
+    }
+#endif
+
     return PR_SUCCESS;
 
 #elif defined(XP_OS2)
 
     err = _MD_os2_get_nonblocking_connect_error(osfd);
     if (err != 0) {
         _PR_MD_MAP_CONNECT_ERROR(err);
         return PR_FAILURE;
@@ -763,16 +811,66 @@ static PRInt32 PR_CALLBACK SocketSendTo(
 			break;
 		}
 		buf = (const void*) ((const char*)buf + temp);
 		amount -= temp;
 	} while (amount > 0);
 	return count;
 }
 
+#if defined(_WIN64) && defined(WIN95)
+static PRInt32 PR_CALLBACK SocketTCPSendTo(
+    PRFileDesc *fd, const void *buf, PRInt32 amount,
+    PRIntn flags, const PRNetAddr *addr, PRIntervalTime timeout)
+{
+    PRInt32 temp, count;
+    const PRNetAddr *addrp = addr;
+#if defined(_PR_INET6)
+    PRNetAddr addrCopy;
+#endif
+    PRThread *me = _PR_MD_CURRENT_THREAD();
+
+    if (_PR_PENDING_INTERRUPT(me)) {
+        me->flags &= ~_PR_INTERRUPT;
+        PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
+        return -1;
+    }
+    if (_PR_IO_PENDING(me)) {
+        PR_SetError(PR_IO_PENDING_ERROR, 0);
+        return -1;
+    }
+
+    PR_ASSERT(IsValidNetAddr(addr) == PR_TRUE);
+#if defined(_PR_INET6)
+    if (addr->raw.family == PR_AF_INET6) {
+        addrCopy = *addr;
+        addrCopy.raw.family = AF_INET6;
+        addrp = &addrCopy;
+    }
+#endif
+
+    count = 0;
+    while (amount > 0) {
+        temp = _PR_MD_TCPSENDTO(fd, buf, amount, flags,
+                                addrp, PR_NETADDR_SIZE(addr), timeout);
+        if (temp < 0) {
+            count = -1;
+            break;
+        }
+        count += temp;
+        if (fd->secret->nonblocking) {
+            break;
+        }
+        buf = (const void*) ((const char*)buf + temp);
+        amount -= temp;
+    }
+    return count;
+}
+#endif
+
 static PRInt32 PR_CALLBACK SocketRecvFrom(PRFileDesc *fd, void *buf, PRInt32 amount,
 PRIntn flags, PRNetAddr *addr, PRIntervalTime timeout)
 {
 	PRInt32 rv;
 	PRUint32 al;
 	PRThread *me = _PR_MD_CURRENT_THREAD();
 
 	if (_PR_PENDING_INTERRUPT(me)) {
@@ -1061,16 +1159,25 @@ static PRStatus PR_CALLBACK SocketGetPee
 	PR_ASSERT(IsValidNetAddrLen(addr, addrlen) == PR_TRUE);
 	return PR_SUCCESS;
 }
 
 static PRInt16 PR_CALLBACK SocketPoll(
     PRFileDesc *fd, PRInt16 in_flags, PRInt16 *out_flags)
 {
     *out_flags = 0;
+
+#if defined(_WIN64)
+    if (in_flags & PR_POLL_WRITE) {
+        if (fd->secret->alreadyConnected) {
+            out_flags = PR_POLL_WRITE;
+            return PR_POLL_WRITE;
+        }
+    }
+#endif
     return in_flags;
 }  /* SocketPoll */
 
 static PRIOMethods tcpMethods = {
 	PR_DESC_SOCKET_TCP,
 	SocketClose,
 	SocketRead,
 	SocketWrite,
@@ -1085,17 +1192,21 @@ static PRIOMethods tcpMethods = {
 	SocketConnect,
 	SocketAccept,
 	SocketBind,
 	SocketListen,
 	SocketShutdown,
 	SocketRecv,
 	SocketSend,
 	(PRRecvfromFN)_PR_InvalidInt,
+#if defined(_WIN64) && defined(WIN95)
+	SocketTCPSendTo, // This is for fast open. We imitate Linux interface.
+#else
 	(PRSendtoFN)_PR_InvalidInt,
+#endif
 	SocketPoll,
 	SocketAcceptRead,
 	SocketTransmitFile,
 	SocketGetName,
 	SocketGetPeerName,
 	(PRReservedFN)_PR_InvalidInt,
 	(PRReservedFN)_PR_InvalidInt,
 	_PR_SocketGetSocketOption,
--- a/pr/src/md/windows/w95sock.c
+++ b/pr/src/md/windows/w95sock.c
@@ -325,16 +325,177 @@ PRInt32
             {
                 return -1;
             }
         }
     } while(bytesSent < amount);
     return bytesSent;
 }
 
+#if defined(_WIN64)
+
+static PRCallOnceType _pr_has_connectex_once;
+typedef BOOL (WINAPI *_pr_win_connectex_ptr)(SOCKET, const struct sockaddr *, int, PVOID, DWORD, LPDWORD, LPOVERLAPPED);
+#ifndef WSAID_CONNECTEX
+#define WSAID_CONNECTEX \
+  {0x25a207b9,0xddf3,0x4660,{0x8e,0xe9,0x76,0xe5,0x8c,0x74,0x06,0x3e}}
+#endif
+#ifndef SIO_GET_EXTENSION_FUNCTION_POINTER
+#define SIO_GET_EXTENSION_FUNCTION_POINTER 0xC8000006
+#endif
+#ifndef TCP_FASTOPEN
+#define TCP_FASTOPEN 15
+#endif
+
+#ifndef SO_UPDATE_CONNECT_CONTEXT
+#define SO_UPDATE_CONNECT_CONTEXT 0x7010
+#endif
+
+static _pr_win_connectex_ptr _pr_win_connectex;
+
+static PRStatus PR_CALLBACK _pr_set_connectex(void)
+{
+    _pr_win_connectex = NULL;
+    SOCKET sock;
+    PRInt32 dwBytes;
+    int rc;
+
+    /* Dummy socket needed for WSAIoctl */
+    sock = socket(AF_INET, SOCK_STREAM, 0);
+    if (sock == INVALID_SOCKET)
+        return PR_SUCCESS;
+
+    GUID guid = WSAID_CONNECTEX;
+    rc = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER,
+                  &guid, sizeof(guid),
+                  &_pr_win_connectex, sizeof(_pr_win_connectex),
+                  &dwBytes, NULL, NULL);
+    if (rc != 0) {
+        _pr_win_connectex = NULL;
+        return PR_SUCCESS;
+    }
+
+    rc = closesocket(sock);
+    return PR_SUCCESS;
+}
+
+PRInt32
+_PR_MD_TCPSENDTO(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+                 const PRNetAddr *addr, PRUint32 addrlen, PRIntervalTime timeout)
+{
+    if (PR_CallOnce(&_pr_has_connectex_once, _pr_set_connectex) != PR_SUCCESS) {
+        PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+        return PR_FAILURE;
+    }
+
+    if (_pr_win_connectex == NULL) {
+        PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+        return PR_FAILURE;
+    }
+
+    PROsfd osfd = fd->secret->md.osfd;
+    PRInt32 rv, err;
+    PRInt32 bytesSent = 0;
+    DWORD rvSent;
+
+    BOOL option = 1;
+    rv = setsockopt((SOCKET)osfd, IPPROTO_TCP, TCP_FASTOPEN, (char*)&option, sizeof(option));
+    if (rv != 0) {
+        err = WSAGetLastError();
+        PR_LOG(_pr_io_lm, PR_LOG_MIN,
+               ("_PR_MD_TCPSENDTO error set opt TCP_FASTOPEN failed %d\n", err));
+        if (err == WSAENOPROTOOPT) {
+            PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+        } else {
+            _PR_MD_MAP_SETSOCKOPT_ERROR(err);
+        }
+        return -1;
+    }
+
+    // ConnectEx requires the socket to be initially bound. We will use INADDR_ANY
+    PRNetAddr bindAddr;
+    memset(&bindAddr, 0, sizeof(bindAddr));
+    if (addr->raw.family == PR_AF_INET) {
+        bindAddr.inet.family = PR_AF_INET;
+    } else if (addr->raw.family == PR_AF_INET6) {
+        bindAddr.ipv6.family = PR_AF_INET6;
+    }
+    rv = bind((SOCKET)osfd, (SOCKADDR*) &bindAddr, sizeof(bindAddr));
+    if (rv != 0) {
+        err = WSAGetLastError();
+        PR_LOG(_pr_io_lm, PR_LOG_MIN,
+               ("_PR_MD_TCPSENDTO error bind failed %d\n", err));
+        _PR_MD_MAP_SETSOCKOPT_ERROR(err);
+        return -1;
+    }
+
+    PR_LOG(_pr_io_lm, PR_LOG_MIN,
+           ("_PR_MD_TCPSENDTO calling _pr_win_connectex  %d %p\n", amount, (char*)buf));
+
+    rvSent = 0;
+    memset(&fd->secret->ol, 0, sizeof(fd->secret->ol));
+    // ConnectEx return TRUE on a success and FALSE on an error.
+    if (_pr_win_connectex( (SOCKET)osfd, (struct sockaddr *) addr,
+                           addrlen, buf, amount,
+                           &rvSent, &fd->secret->ol) == TRUE) {
+        // When ConnectEx is used, all previously set socket options and
+        // property are not enabled and to enable them
+        // SO_UPDATE_CONNECT_CONTEXT option need to be set.
+        rv = setsockopt((SOCKET)osfd, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, NULL, 0);
+        if (rv != 0) {
+            err = WSAGetLastError();
+            PR_LOG(_pr_io_lm, PR_LOG_MIN,
+               ("_PR_MD_TCPSENDTO setting SO_UPDATE_CONNECT_CONTEXT failed %d\n", err));
+            _PR_MD_MAP_SETSOCKOPT_ERROR(err);
+            return -1;
+        }
+        // We imitate Linux here. SendTo will return number of bytes send but
+        // it can not return connection success at the same time, so we return
+        // number of bytes send and "connection success" will be return on the
+        // connectcontinue.
+        fd->secret->alreadyConnected = PR_TRUE;
+        return rvSent;
+    } else {
+        err = WSAGetLastError();
+        PR_LOG(_pr_io_lm, PR_LOG_MIN,
+               ("_PR_MD_TCPSENDTO error _pr_win_connectex failed %d\n", err));
+        if (err != ERROR_IO_PENDING) {
+            _PR_MD_MAP_CONNECT_ERROR(err);
+            return -1;
+        } else if (fd->secret->nonblocking) {
+            // Remember that overlapped structure is set. We will neede to get
+            // the final result of ConnectEx call.
+            fd->secret->overlappedActive = PR_TRUE;
+            _PR_MD_MAP_CONNECT_ERROR(WSAEWOULDBLOCK);
+            // ConnectEx will copy supplied data to a internal buffer and send
+            // them during Fast Open or after connect. Therefore we can assumed
+            // this data already send.
+            return amount;
+        }
+        while (err == ERROR_IO_PENDING) {
+            rv = socket_io_wait(osfd, WRITE_FD, timeout);
+            if ( rv < 0 ) {
+                return -1;
+            }
+            rv = GetOverlappedResult(osfd, &fd->secret->ol, &rvSent, FALSE);
+            if ( rv == TRUE ) {
+                return rvSent;
+            } else {
+                err = WSAGetLastError();
+                if (err != ERROR_IO_PENDING) {
+                    _PR_MD_MAP_CONNECT_ERROR(err);
+                    return -1;
+                }
+            }
+        }
+    }
+    return -1;
+}
+#endif
+
 PRInt32
 _PR_MD_RECVFROM(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
                 PRNetAddr *addr, PRUint32 *addrlen, PRIntervalTime timeout)
 {
     PROsfd osfd = fd->secret->md.osfd;
     PRInt32 rv, err;
 
     while ((rv = recvfrom( osfd, buf, amount, 0, (struct sockaddr *) addr,
--- a/pr/src/pthreads/ptio.c
+++ b/pr/src/pthreads/ptio.c
@@ -159,16 +159,19 @@ static ssize_t (*pt_aix_sendfile_fptr)()
 #include <netinet/tcp.h>  /* TCP_NODELAY, TCP_MAXSEG */
 #endif
 
 #ifdef LINUX
 /* TCP_CORK is not defined in <netinet/tcp.h> on Red Hat Linux 6.0 */
 #ifndef TCP_CORK
 #define TCP_CORK 3
 #endif
+#ifndef MSG_FASTOPEN
+#define MSG_FASTOPEN    0x20000000
+#endif
 #endif
 
 #ifdef _PR_IPV6_V6ONLY_PROBE
 static PRBool _pr_ipv6_v6only_on_by_default;
 #endif
 
 #if (defined(HPUX) && !defined(HPUX10_30) && !defined(HPUX11))
 #define _PRSelectFdSetArg_t int *
@@ -2048,16 +2051,109 @@ static PRInt32 pt_SendTo(
         bytes = pt_Continue(&op);
         syserrno = op.syserrno;
     }
     if (bytes < 0)
         pt_MapError(_PR_MD_MAP_SENDTO_ERROR, syserrno);
     return bytes;
 }  /* pt_SendTo */
 
+// Linux uses SendTo to send data during TCP Fast Open. OSX uses connectx, but
+// we will make it imitate the Linux's interface.
+static PRInt32 pt_TCP_SendTo(
+    PRFileDesc *fd, const void *buf,
+    PRInt32 amount, PRIntn flags, const PRNetAddr *addr,
+    PRIntervalTime timeout)
+{
+#if !defined(DARWIN) || HAS_CONNECTX
+    PRInt32 syserrno, bytes = -1;
+    PRBool fNeedContinue = PR_FALSE;
+    pt_SockLen addr_len;
+    const PRNetAddr *addrp = addr;
+#if defined(_PR_HAVE_SOCKADDR_LEN) || defined(_PR_INET6)
+    PRNetAddr addrCopy;
+#endif
+#ifdef _PR_HAVE_SOCKADDR_LEN
+    PRUint16 md_af = addr->raw.family;
+#endif
+
+    if (pt_TestAbort()) return bytes;
+
+    PR_ASSERT(IsValidNetAddr(addr) == PR_TRUE);
+    addr_len = PR_NETADDR_SIZE(addr);
+#if defined(_PR_INET6)
+    if (addr->raw.family == PR_AF_INET6) {
+#ifdef _PR_HAVE_SOCKADDR_LEN
+        md_af = AF_INET6;
+#else
+        // If _PR_INET6 is defined and it is PR_AF_INET6 we set family
+        // to AF_INET6.
+        addrCopy = *addr;
+        addrCopy.raw.family = AF_INET6;
+        addrp = &addrCopy;
+#endif
+    }
+#endif
+
+#ifdef _PR_HAVE_SOCKADDR_LEN
+    // if _PR_HAVE_SOCKADDR_LEN is defined and it is PR_AF_INET6 we set family
+    // to AF_INET6 and we set address length.
+    addrCopy = *addr;
+    ((struct sockaddr*)&addrCopy)->sa_len = addr_len;
+    ((struct sockaddr*)&addrCopy)->sa_family = md_af;
+    addrp = &addrCopy;
+#endif
+
+#ifndef HAS_CONNECTX
+    bytes = sendto(
+        fd->secret->md.osfd, buf, amount, MSG_FASTOPEN,
+        (struct sockaddr*)addrp, addr_len);
+#else
+    sa_endpoints_t endpoints;
+    endpoints.sae_srcif = 0;
+    endpoints.sae_srcaddr = NULL;
+    endpoints.sae_srcaddrlen = 0;
+    endpoints.sae_dstaddr = (struct sockaddr *)addrp;
+    endpoints.sae_dstaddrlen = addr_len;
+    struct iovec iov[1];
+    iov[0].iov_base = buf;
+    iov[0].iov_len = amount;
+    PRInt32 rv = connectx(fd->secret->md.osfd, &endpoints, SAE_ASSOCID_ANY,
+                         CONNECT_DATA_IDEMPOTENT, iov, 1, &bytes, NULL);
+#endif
+    syserrno = errno;
+    if ( (bytes == -1) && (syserrno == EWOULDBLOCK || syserrno == EAGAIN)
+        && (!fd->secret->nonblocking) ) {
+        if (PR_INTERVAL_NO_WAIT == timeout) syserrno = ETIMEDOUT;
+        else fNeedContinue = PR_TRUE;
+    }
+    if (fNeedContinue == PR_TRUE) {
+        pt_Continuation op;
+        op.arg1.osfd = fd->secret->md.osfd;
+        op.arg2.buffer = (void*)buf;
+        op.arg3.amount = amount;
+        op.arg4.flags = flags;
+        op.arg5.addr = (PRNetAddr*)addrp;
+        op.timeout = timeout;
+        op.result.code = 0;  /* initialize the number sent */
+        op.function = pt_sendto_cont;
+        op.event = POLLOUT | POLLPRI;
+        bytes = pt_Continue(&op);
+        syserrno = op.syserrno;
+    }
+    if (bytes < 0) {
+        pt_MapError(_PR_MD_MAP_SENDTO_ERROR, syserrno);
+    }
+    return bytes;
+#else
+    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+    return -1;
+#endif
+}  /* pt_TCP_SendTo */
+
 static PRInt32 pt_RecvFrom(PRFileDesc *fd, void *buf, PRInt32 amount,
     PRIntn flags, PRNetAddr *addr, PRIntervalTime timeout)
 {
     PRBool fNeedContinue = PR_FALSE;
     PRInt32 syserrno, bytes = -1;
     pt_SockLen addr_len = sizeof(PRNetAddr);
 
     if (pt_TestAbort()) return bytes;
@@ -3159,17 +3255,17 @@ static PRIOMethods _pr_tcp_methods = {
     pt_Connect,
     pt_Accept,
     pt_Bind,
     pt_Listen,
     pt_Shutdown,
     pt_Recv,
     pt_Send,
     (PRRecvfromFN)_PR_InvalidInt,
-    (PRSendtoFN)_PR_InvalidInt,
+    pt_TCP_SendTo, // This is for TCP Fast Open. Linux uses SendTo function for this. OSX uses connectx, but we imitate Linux.
     pt_Poll,
     pt_AcceptRead,
     pt_TransmitFile,
     pt_GetSockName,
     pt_GetPeerName,
     (PRReservedFN)_PR_InvalidInt,
     (PRReservedFN)_PR_InvalidInt,
     pt_GetSocketOption,