5

I have a socket proxy written in Python which when it receives a RST from a pair of communicating peers will close the connection to both peers by letting the sockets be garbage collected. This results in the other peer seeing a FIN rather than a RST.

This means the proxy effectively translates RST into FIN, which I don't think is ideal.

I found that in Linux it possible to reset a TCP connnection by calling connect with an address of family AF_UNSPEC. But I haven't found a way to do this from a Python program.

How do I connect to an AF_UNSPEC address in Python?

What I have tried so far

I tried looking at the help output for the relevant connect method and found this:

Help on built-in function connect:

connect(...)
    connect(address)

    Connect the socket to a remote address.  For IP sockets, the address
    is a pair (host, port).

Unfortunately that doesn't tell me what the address argument has to be in order to construct a AF_UNSPEC address.

I attempted to wrap the original socket fd in a new socket object with family AF_UNSPEC like this:

socket.fromfd(s.fileno(), socket.AF_UNSPEC, 0)

The resulting object produce the same help text and any attempt to call connect on the newly constructed socket object results in

socket.error: getsockaddrarg: bad family

So it looks like using socket.fromfd is probably not the answer to my question.

kasperd
  • 1,952
  • 1
  • 20
  • 31

2 Answers2

2

Looking at the current socket package implementation in CPython, there is really no pythonic way (to connect a socket to an AF_UNSPEC address, as of 2019-01 (i.e. to reset the connection on Linux).

The next best thing is to set the SO_LINGER option on the accepted socket (either directly or via inheritance). When lingering is enabled (and set to a zero timeout) closing the socket yields a reset of the connection.

You have to be careful to set the SO_LINGER option on the right sockets API level and to use the right encoding for the option value (it's a struct).

Example:

import socket
import struct
import time

s = socket.socket(socket.AF_INET6)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
# if we want to inherit this option:
#s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
s.bind(('', 2323))
s.listen()
con, addr = s.accept()
con.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
time.sleep(1)
con.close()
time.sleep(3)

Connecting to this port with curl:

$ curl localhost:2323
curl: (56) Recv failure: Connection reset by peer

Connecting to this port without sending anything:

$ socat - tcp:localhost:2323

When dumping the packets with e.g.

$ tshark -i lo -f 'tcp port 2323'

the last packet should be a RST (sent from server to client), in both cases - for example:

39 9758.478140247    127.0.0.1 → 127.0.0.1    TCP 66 2323 → 34494 [RST, ACK]
        Seq=1 Ack=1 Win=43776 Len=0 TSval=2787120418 TSecr=2787119417
maxschlepzig
  • 35,645
  • 14
  • 145
  • 182
  • I had to fix a couple of bugs in your example before it would run `TypeError: listen() takes exactly one argument (0 given)` and `NameError: name 'struct' is not defined`. Once I had fixed those the results I saw was that a RST was produced if there was data on the socket which had not yet been read and a FIN would be produced otherwise. That's the exact same behavior I saw if I did not use `SO_LINGER`. So it appears the reason you got RST was that `curl` sent a request which wasn't read by the server. But I cannot force the peer to send more data to me when I want to produce a RST. – kasperd Jan 06 '19 at 22:30
  • @kasperd, I've updated my answer. I added the missing `struct` import and fixed the `SO_LINGER` option value. With enabled linger and zero timeout I get a RST in both cases. Note that the argument to `listen()` is optional since Python 3.5. – maxschlepzig Jan 07 '19 at 22:52
-1

You can try to use the SO_LINGER socket option ( setsockopt ) with linger time set to 0. close on socket with SO_LINGER set with 0 seconds lingering time will result in RST instead of FIN.

Prabhu
  • 3,443
  • 15
  • 26
  • I tested `c.setsockopt(socket.SOL_IP, socket.SO_LINGER, 0)` and it didn't work. The connection still got closed with `FIN` not `RST` like I wanted. – kasperd Sep 19 '17 at 17:00