shadowsocks-go icon indicating copy to clipboard operation
shadowsocks-go copied to clipboard

Implement sending UDP payload in TCP?

Open arinc9 opened this issue 11 months ago • 4 comments

v2ray's vmess outbound creates a TCP stream for each daddr:dport of the UDP packets received on inbound. As we use MPTCP for the redundancy purpose, having the UDP traffic included in that is very useful.

On a client configuration as below, instead of rejecting UDP traffic from the server object, this functionality could kick in instead.

{
  "servers": [
    {
      "name": "tproxy",
      "protocol": "tproxy",
      "mtu": 1500,
      "tcpListeners": [
        {
          "network": "tcp",
          "address": "127.0.0.1:12345"
        }
      ],
      "udpListeners": [
        {
          "network": "udp",
          "address": "127.0.0.1:12345"
        }
      ]
    }
  ],
  "clients": [
    {
      "name": "ss-2022",
      "protocol": "none",
      "mtu": 1500,
      "endpoint": "102.132.169.121:12348",
      "enableTCP": true,
      "multipathTCP": true,
      "psk": ""
    }
  ]
}

Server:

{
  "servers": [
    {
      "name": "ss-2022",
      "protocol": "none",
      "mtu": 1500,
      "tcpListeners": [
        {
          "network": "tcp",
          "address": ":12348",
          "multipath": true
        }
      ],
      "psk": ""
    }
  ],
  "clients": [
    {
      "name": "direct4",
      "protocol": "direct",
      "network": "ip4",
      "enableTCP": true,
      "enableUDP": true,
      "mtu": 1500
    }
  ]
}

arinc9 avatar Mar 02 '25 19:03 arinc9

v2ray's vmess outbound creates a TCP stream for each daddr:dport of the UDP packets received on inbound.

You just described 2 of the worst things about V2Ray:

  1. Its flagship proxy protocol does not have native UDP support, and forces the use of UDP over TCP.
  2. This is not how you implement NAT for UDP. Each NAT connection should correspond to a saddr:sport of received UDP packets, not daddr:dport.

Implementation details aside, I understand that UoT can be useful in certain scenarios. Unlike VMess, the Shadowsocks "none" protocol does not have a spec for UoT, so we need to come up with a new protocol, or implement one of the existing UoT protocols like https://sing-box.sagernet.org/configuration/shared/udp-over-tcp/.

After getting v1.13.0 out, hopefully within the week, I'll start working on a major refactoring to simplify the TCP abstractions, and implement UDP GRO and GSO support. After all that, I'll see if I can come up with a UoT protocol that can transfer coalesced UDP packets without spitting them up.

database64128 avatar Mar 03 '25 02:03 database64128

Its flagship proxy protocol does not have native UDP support, and forces the use of UDP over TCP.

I believe the reason for that is to provide UDP access in networks where UDP traffic is blocked, such as networks behind the Great Firewall of China. v2ray was initially developed to bypass that, I suppose. My use case is another one.

This is not how you implement NAT for UDP. Each NAT connection should correspond to a saddr:sport of received UDP packets, not daddr:dport.

Certainly. I was rather talking about how the programme running on the client host handles creating a TCP stream for UDP packets.

Implementation details aside, I understand that UoT can be useful in certain scenarios. Unlike VMess, the Shadowsocks "none" protocol does not have a spec for UoT, so we need to come up with a new protocol, or implement one of the existing UoT protocols like https://sing-box.sagernet.org/configuration/shared/udp-over-tcp/.

After getting v1.13.0 out, hopefully within the week, I'll start working on a major refactoring to simplify the TCP abstractions, and implement UDP GRO and GSO support. After all that, I'll see if I can come up with a UoT protocol that can transfer coalesced UDP packets without spitting them up.

That would be crucial for our use case, thanks a lot! Here's what I have in mind:

(I know that vmess sends its header on the first packet with the same daddr-dport pair, and then when needed, so my logic is currently based on top of that behaviour. I don't know if this is the case with the shadowsocks none protocol.)

Client & Server Host TX path:

  1. Client object receives UDP packet from listen port.
  2. The programme shall do UoT if the relevant server object hasn't got UDP enabled and if it supports UoT.
  3. The programme shall save the daddr-dport pair and create a TCP stream for it. It shall terminate the stream if no UDP packets with the pair were received for a certain amount of time.
  4. The programme shall take the payload from the UDP packet and deliver it to the relevant server object as a TCP packet with a metadata information, stating that UoT is to be done on this packet.
  5. The server object shall send the first packet with the daddr-dport pair to the other host with information on the protocol header which states that this is a UDP packet.
  6. For TCP packets which include this information on the protocol header, the client object on the recipient host shall save the daddr-dport pair to remember that this TCP stream is to be sent as UDP on the server object.
  7. The programme shall take the payload from the TCP packet and deliver it to the relevant server object as a UDP packet.

Client & Server Host RX path:

  1. Server object receives UDP packet from listen port.
  2. The programme shall match the saddr and sport on the packet to a daddr-dport pair it stores. For packets that match a daddr-dport pair, the programme shall take the payload from the UDP packet and deliver it to the relevant client object as a TCP packet.
  3. The client object shall send the TCP packet to the relevant host.
  4. The server object on the client host receives TCP packet from listen port.
  5. The programme shall take the payload from the TCP packet and deliver it to the relevant client object as a UDP packet.

There's likely holes in this logic. Let me know.

arinc9 avatar Mar 03 '25 10:03 arinc9

I believe the reason for that is to provide UDP access in networks where UDP traffic is blocked, such as networks behind the Great Firewall of China. v2ray was initially developed to bypass that, I suppose.

From what I heard, the original author of V2Ray made these decisions purely out of personal preferences. You don't need UoT to circumvent the GFW. shadowsocks-go works just fine for the same purpose.

database64128 avatar Mar 03 '25 11:03 database64128

I had no luck with sing-box either regarding UoT. https://github.com/SagerNet/sing-box/issues/2668

arinc9 avatar Mar 03 '25 12:03 arinc9