kinnay / NintendoClients

Python package to communicate with Switch, Wii U and 3DS servers

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

PRUDP connection skips sequence IDs on the server if the client ACKs a packet it has not received

jonbarrow opened this issue · comments

There seems to be some undocumented behavior about how sequence IDs work with PRUDP, at least with PRUDPv1. We have observed several cases where a client will send an acknowledgment packet for sequence IDs it has not received from the server, and in doing so the server then skips those IDs in it's counter

This example was found using Steeldiver Subwars by @DaniElectra. We have verified this happens both using Wireshark and a homebrew app which dumps PRUDP traffic right from the console, so this is not a matter of dropped/lost packets it seems?

The example is as follows:

  • The client has sent packets up to sequence ID 0xD to the server
  • The server has sent packets 0xA and 0xB to the client
  • The server sends a multi-ACK packet to the client ACKing up to 0xD (no issues so far)
  • The client sends a multi-ACK packet to the server ACKing up to 0xD (the server has not sent packets 0xC or 0xD)
  • The next packet the server sends is sequence ID 0xE, skipping 2 places on the servers sequence ID counter

I have not provided a network dump of this behavior here as it was made using Dani's account, and I am not sure he is comfortable posting his NEX credentials on here

Interestingly, the behavior described above has only shown when using Matchmaking-related protocols so far. Also, the acknowledgement skip isn't consistent (it can be one packet, it can be 8).

The example from above is on Steeldiver Subwars, but this also appears on other games like on Minecraft for the Wii U and Wii Sports Club.

It is also unclear if this skipping only goes one way. We have not observed any client sending ACKs for packets it has already sent an ACK for, so we do not know if this would send the servers counter backwards or not (I highly doubt it would, but just making it clear that we haven't seen it happen or tried to force it). We also have not seen a case where the server forces the client to skip sequence IDs (to my knowledge), so it's unclear if this also works in that case

That sounds weird. I just looked at the PRUDP implementation in DKC:TF and can't figure out how this is possible. It looks like the outgoing sequence id is only ever incremented by 1. I also know that the server uses the same implementation as the client.

I also can't reproduce it with a custom client. The server uses the expected sequence id even if I acknowledge a future packet. I tried both regular ack and aggregate ack packets on the Wii Sports Club server.

If you would like me take a look at your network dump, you can send it to me in private.

Here's the script that I used. It sends an aggregate acknowledgement packet that skips 10 sequence ids.

from nintendo.nex import common, prudp, rmc, settings
import anyio
import struct

import logging
logging.basicConfig(level=logging.DEBUG)


async def send_ack(self, packet):
	ack = prudp.PRUDPPacket(prudp.TYPE_DATA, prudp.FLAG_MULTI_ACK | prudp.FLAG_HAS_SIZE)
	ack.substream_id = 1
	ack.payload = struct.pack("<BBH", 0, 0, packet.packet_id + 10)
	await self.send_packet(ack)
prudp.PRUDPClient.send_ack = send_ack


async def main():
	s = settings.default()
	s.configure("4d324052", 30400)
	async with rmc.connect(s, "34.208.166.202", 43480) as client:
		await client.request(10, 5, struct.pack("<I", 1))
		await anyio.sleep(3)
		await client.request(10, 5, struct.pack("<I", 1))
		await anyio.sleep(3)
		await client.disconnect()

anyio.run(main)

And this is the debug output. The server does not skip any sequence id.

DEBUG:asyncio:Using selector: EpollSelector
DEBUG:nintendo.nex.rmc:Connecting RMC client to 34.208.166.202:43480:1
DEBUG:nintendo.nex.prudp:Connecting PRUDP transport to 34.208.166.202:43480
DEBUG:anynet.udp:Connecting UDP client to 34.208.166.202:43480
DEBUG:nintendo.nex.prudp:[CLI] Sending packet: <PRUDPPacket type=TYPE_SYN flags=NEED_ACK seq=0 frag=0>
DEBUG:nintendo.nex.prudp:[CLI] Received packet: <PRUDPPacket type=TYPE_SYN flags=ACK,HAS_SIZE seq=0 frag=0>
DEBUG:nintendo.nex.prudp:[CLI] Sending packet: <PRUDPPacket type=TYPE_CONNECT flags=NEED_ACK,RELIABLE,HAS_SIZE seq=1 frag=0>
DEBUG:nintendo.nex.prudp:[CLI] Received packet: <PRUDPPacket type=TYPE_CONNECT flags=ACK,HAS_SIZE seq=1 frag=0>
DEBUG:nintendo.nex.prudp:[CLI] Sending packet: <PRUDPPacket type=TYPE_DATA flags=NEED_ACK,RELIABLE,HAS_SIZE seq=2 frag=0>
DEBUG:nintendo.nex.prudp:[CLI] Received packet: <PRUDPPacket type=TYPE_DATA flags=NEED_ACK,RELIABLE,HAS_SIZE seq=1 frag=0>
DEBUG:nintendo.nex.prudp:[CLI] Sending packet: <PRUDPPacket type=TYPE_DATA flags=MULTI_ACK,HAS_SIZE seq=0 frag=0>
DEBUG:nintendo.nex.prudp:[CLI] Received packet: <PRUDPPacket type=TYPE_DATA flags=MULTI_ACK,HAS_SIZE seq=0 frag=0>
DEBUG:nintendo.nex.rmc:Received RMC response: protocol=10 method=5 call=1
DEBUG:nintendo.nex.prudp:[CLI] Sending packet: <PRUDPPacket type=TYPE_DATA flags=NEED_ACK,RELIABLE,HAS_SIZE seq=3 frag=0>
DEBUG:nintendo.nex.prudp:[CLI] Received packet: <PRUDPPacket type=TYPE_DATA flags=NEED_ACK,RELIABLE,HAS_SIZE seq=2 frag=0>
DEBUG:nintendo.nex.prudp:[CLI] Sending packet: <PRUDPPacket type=TYPE_DATA flags=MULTI_ACK,HAS_SIZE seq=0 frag=0>
DEBUG:nintendo.nex.rmc:Received RMC response: protocol=10 method=5 call=2
DEBUG:nintendo.nex.prudp:[CLI] Received packet: <PRUDPPacket type=TYPE_DATA flags=MULTI_ACK,HAS_SIZE seq=0 frag=0>
DEBUG:nintendo.nex.prudp:[CLI] Sending packet: <PRUDPPacket type=TYPE_PING flags=NEED_ACK,RELIABLE seq=4 frag=0>
DEBUG:nintendo.nex.prudp:[CLI] Received packet: <PRUDPPacket type=TYPE_PING flags=ACK,HAS_SIZE seq=4 frag=0>
DEBUG:nintendo.nex.prudp:[135] Closing PRUDP connection
DEBUG:nintendo.nex.prudp:[CLI] Sending packet: <PRUDPPacket type=TYPE_DISCONNECT flags=NEED_ACK,RELIABLE seq=5 frag=0>
DEBUG:nintendo.nex.prudp:[CLI] Received packet: <PRUDPPacket type=TYPE_DISCONNECT flags=ACK,HAS_SIZE seq=5 frag=0>
DEBUG:nintendo.nex.prudp:[CLI] Received packet: <PRUDPPacket type=TYPE_DISCONNECT flags=ACK,HAS_SIZE seq=5 frag=0>
DEBUG:nintendo.nex.prudp:PRUDP connection is closed
DEBUG:nintendo.nex.prudp:[CLI] Received packet: <PRUDPPacket type=TYPE_DISCONNECT flags=ACK,HAS_SIZE seq=5 frag=0>
DEBUG:nintendo.nex.rmc:RMC client is close

If you would like me take a look at your network dump, you can send it to me in private.

Sounds good. Should I send the dumps over your email on your profile or over a Discord account?

Email is fine.

I found out what is happening. When PRUDP packets are sent quickly after each other, they are sent in a single UDP packet. Your parser probably does not take this into account.

That was it! Thank you so much!