@@ -1031,8 +1031,72 @@ proc accept*(server: Socket, client: var owned(Socket),
10311031 var addrDummy = " "
10321032 acceptAddr (server, client, addrDummy, flags)
10331033
1034- proc close * (socket: Socket ) =
1034+ when defined (posix):
1035+ from posix import Sigset , sigwait, sigismember, sigemptyset, sigaddset,
1036+ sigprocmask, pthread_sigmask, SIGPIPE , SIG_BLOCK , SIG_UNBLOCK
1037+
1038+ template blockSigpipe (body: untyped ): untyped =
1039+ # # Temporary block SIGPIPE within the provided code block. If SIGPIPE is
1040+ # # raised for the duration of the code block, it will be queued and will be
1041+ # # raised once the block ends.
1042+ # #
1043+ # # Within the block a `selectSigpipe()` template is provided which can be
1044+ # # used to remove SIGPIPE from the queue. Note that if SIGPIPE is **not**
1045+ # # raised at the time of call, it will block until SIGPIPE is raised.
1046+ # #
1047+ # # If SIGPIPE has already been blocked at the time of execution, the
1048+ # # signal mask is left as-is and `selectSigpipe()` will become a no-op.
1049+ # #
1050+ # # For convenience, this template is also available for non-POSIX system,
1051+ # # where `body` will be executed as-is.
1052+ when not defined (posix):
1053+ body
1054+ else :
1055+ template sigmask (how: cint , set, oset: var Sigset ): untyped {.gensym .} =
1056+ # # Alias for pthread_sigmask or sigprocmask depends on the status
1057+ # # of --threads flag
1058+ when compileOption (" threads" ):
1059+ pthread_sigmask (how, set , oset)
1060+ else :
1061+ sigprocmask (how, set , oset)
1062+
1063+ var oldSet, watchSet: Sigset
1064+ if sigemptyset (oldSet) == - 1 :
1065+ raiseOSError (osLastError ())
1066+ if sigemptyset (watchSet) == - 1 :
1067+ raiseOSError (osLastError ())
1068+
1069+ if sigaddset (watchSet, SIGPIPE ) == - 1 :
1070+ raiseOSError (osLastError (), " Couldn't add SIGPIPE to Sigset" )
1071+
1072+ if sigmask (SIG_BLOCK , watchSet, oldSet) == - 1 :
1073+ raiseOSError (osLastError (), " Couldn't block SIGPIPE" )
1074+
1075+ let alreadyBlocked = sigismember (oldSet, SIGPIPE ) != 1
1076+
1077+ template selectSigpipe (): untyped {.used .} =
1078+ if not alreadyBlocked:
1079+ var signal: cint
1080+ let err = sigwait (watchSet, signal)
1081+ if err != 0 :
1082+ raiseOSError (err.OSErrorCode , " Couldn't select SIGPIPE" )
1083+ assert signal == SIGPIPE
1084+
1085+ try :
1086+ body
1087+ finally :
1088+ if not alreadyBlocked:
1089+ if sigmask (SIG_UNBLOCK , watchSet, oldSet) == - 1 :
1090+ raiseOSError (osLastError (), " Couldn't unblock SIGPIPE" )
1091+
1092+ proc close * (socket: Socket , flags = {SocketFlag .SafeDisconn }) =
10351093 # # Closes a socket.
1094+ # #
1095+ # # If `socket` is an SSL/TLS socket, this proc will also send a closure
1096+ # # notification to the peer. If `SafeDisconn` is in `flags`, failure to do so
1097+ # # due to disconnections will be ignored. This is generally safe in
1098+ # # practice. See
1099+ # # `here <https://security.stackexchange.com/a/82044>`_ for more details.
10361100 try :
10371101 when defineSsl:
10381102 if socket.isSsl and socket.sslHandle != nil :
@@ -1044,12 +1108,33 @@ proc close*(socket: Socket) =
10441108 # it is valid, under the TLS standard, to perform a unidirectional
10451109 # shutdown i.e not wait for the peers "close notify" alert with a second
10461110 # call to SSL_shutdown
1047- ErrClearError ()
1048- let res = SSL_shutdown (socket.sslHandle)
1049- if res == 0 :
1050- discard
1051- elif res != 1 :
1052- socketError (socket, res)
1111+ blockSigpipe:
1112+ ErrClearError ()
1113+ let res = SSL_shutdown (socket.sslHandle)
1114+ if res == 0 :
1115+ discard
1116+ elif res != 1 :
1117+ let
1118+ err = osLastError ()
1119+ sslError = SSL_get_error (socket.sslHandle, res)
1120+
1121+ # If a close notification is received, failures outside of the
1122+ # protocol will be returned as SSL_ERROR_ZERO_RETURN instead
1123+ # of SSL_ERROR_SYSCALL. This fact is deduced by digging into
1124+ # SSL_get_error() source code.
1125+ if sslError == SSL_ERROR_ZERO_RETURN or
1126+ sslError == SSL_ERROR_SYSCALL :
1127+ when defined (posix) and not defined (nimdoc):
1128+ if err == EPIPE .OSErrorCode :
1129+ # Clear the SIGPIPE that's been raised due to
1130+ # the disconnection.
1131+ selectSigpipe ()
1132+ else :
1133+ discard
1134+ if not flags.isDisconnectionError (err):
1135+ socketError (socket, res, lastError = err, flags = flags)
1136+ else :
1137+ socketError (socket, res, lastError = err, flags = flags)
10531138 finally :
10541139 when defineSsl:
10551140 if socket.isSsl and socket.sslHandle != nil :
@@ -1470,7 +1555,7 @@ proc recvFrom*(socket: Socket, data: var string, length: int,
14701555 var addrLen = sizeof (sockAddress).SockLen
14711556 result = recvfrom (socket.fd, cstring (data), length.cint , flags.cint ,
14721557 cast [ptr SockAddr ](addr (sockAddress)), addr (addrLen))
1473-
1558+
14741559 if result != - 1 :
14751560 data.setLen (result )
14761561 address = getAddrString (cast [ptr SockAddr ](addr (sockAddress)))
0 commit comments