wamp-proto icon indicating copy to clipboard operation
wamp-proto copied to clipboard

RawSocket: improve spec text rgd PING/PONG behavior

Open FGasper opened this issue 8 years ago • 16 comments

The description of RawSocket in the protocol doesn’t specify some details of ping/pong behavior; for example:

  • What should be made of unsolicited PONG messages?

  • If multiple PINGs are received before there is a chance to respond, is it necessary to send a PONG for each received PING, or will just the most recent PONG suffice?

I’m assuming that the relevant patterns from WebSocket are meant to carry over here—i.e., that unsolicited PONG is acceptable and that only the most recent PING needs a response. It would still be useful to reiterate those patterns here since, in theory, a consumer of the WAMP specification need not be familiar with WebSocket.

FGasper avatar Mar 23 '17 04:03 FGasper

I’m assuming that the relevant patterns from WebSocket are meant to carry over here

No, not exactly. RawSocket is explicitly designed to be simpler, and more tight. See below.

What should be made of unsolicited PONG messages?

Ideally, receiving an unsolicited PONG should be a protocol error.

However, deciding whether a PONG is unsolicited may not be always possible for a peer.

  1. peer did not ever send a PING => it can decide (any PONG received is necessarily unsolicited)
  2. peer sends PINGs with a eg a sequence number in the payload => it can decide
  3. peer sends PINGs with no payload => ??

Not sure. I am leaning towards "ignored PONGs deemed unsolicited by a peer".

RFC6455:

A Pong frame MAY be sent unsolicited. This serves as a unidirectional heartbeat. A response to an unsolicited Pong frame is not expected.

Ah yes? "not expected", right;) So what is the peer supposed to do? This is lacking also IMO.

So I think we should add: PONGs deemed or identified as unsolicited by a peer MUST be ignored. This may server as a unidirectional hearbeat.

So yeah, rgd this aspect, we need to add text to the spec.

If multiple PINGs are received

The WAMP spec is crystal clear here:

"A Peer MUST reply to each PING by sending exactly one PONG immediately, and the PONG MUST echo back the payload of the PING exactly."


Rgd the latter, and rgd RFC6455: I think this was a slight mistake in WebSocket.

Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in response, unless it already received a Close frame. It SHOULD respond with Pong frame as soon as is practical.

What should "as soon a practical" even mean? Am I allowed to wait 2 hours to reply?

FWIW, I am only aware of a single WS implementation, that at least at one point took the freedom of NOT replying to each and every PING: IE10.

I have never seen it anywhere else .. WS implementation take the easy path: simply reply immediately, and reply every PING.

Autobahn (abbr. AB) certainly does it like this, and we have no plans to change that. Crossbar.io (abbr. CB) with "auto ping/pong" expects every PING to be replied, as it has a sequence number in the PING payload, and it will check that (the PING+seq is attached to a timer in CB). This logic comes from AB itself.

Mozilla is using Autobahn as a WebSocket server on a massive scale - we've implemented a sophisticated batched/chunked logic under the hood to make that work (if you have 50 mio WS connections, the PONG wave will kill you if you do it naively).

oberstet avatar Mar 23 '17 06:03 oberstet

I think WebSocket’s approach makes sense. The way I’ve implemented it is: as soon as the endpoint receives any pong that matches a recognized ping, the “ping count” is cleared.

It certainly makes sense for the receiver of a ping to send a pong right away.

If “Bert” send pings A, B, and C to “Ernie”, though, Bert doesn’t really care about receiving pongs for all of them; all he cares about is whether Ernie is still responsive. Receipt of any of those three pings should confirm that—so it’s not necessary for Ernie to send pongs for all of them … though, of course, it’s much simpler for Ernie to do so, so he might as well do it that way.


Regarding unsolicited/unrecognized pongs, I agree with your assessment: the receiver MUST ignore unsolicited or unrecognized pongs.

FGasper avatar Mar 23 '17 16:03 FGasper

I think WebSocket’s approach makes sense.

No, I disagree. We won't change the WAMP spec for RawSocket in this respect.

oberstet avatar Mar 23 '17 16:03 oberstet

Is there to be any definition of behavior in the case where Bert sends pings A, B, and C, but Ernie responds to C without responding to B or A?

So drop the connection if Bert receives pong-C prior to A or B? It might be good to clarify that.

-FG

On Mar 23, 2017, at 12:26 PM, Tobias Oberstein [email protected] wrote:

I think WebSocket’s approach makes sense.

No, I disagree. We won't change the WAMP spec for RawSocket in this respect.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or mute the thread.

FGasper avatar Mar 23 '17 19:03 FGasper

As a baseline, WAMP requires an ordered transport. This aspect, combined with the requirement of RawSocket to reply each and every PING immediately with a PONG logically makes sending PONGs out-of-order a protocol violation.

Not replying a PING immediately, but eg first answering a CALL that comes in after the PING is formally not protocol violation, as there are no ordering guarantees between CALL_RESULT for the CALL and the PONG expected - would make no sense, as the invocation of course might take long.

Given a Client is sending

 PING 1, CALL 1, PING 2, CALL 2

all of these are a valid Router responses

 PONG 1, CALL_RESULT 1, PONG 2, CALL_RESULT 2, 
 PONG 1, PONG 2, CALL_RESULT 2, CALL_RESULT 1 

whereas all of the following are NOT

 PONG 2, CALL_RESULT 2, CALL_RESULT 1  [missing PONG 1 reply]
 PONG 2, PONG 1, CALL_RESULT 2, CALL_RESULT 1  [order guarantee breakage]
 CALL_RESULT 1, PONG 1, PONG 2, CALL_RESULT 2  [PONG 1 not sent immediately]

I can summarize as follows:

  • PING/PONG messages flow between peers of role Client and role Router
  • every PING received MUST be immediately answered by a PONG
  • the ordering guarantee is: PING 1 then PING 2 MUST be answered PONG 1 then PONG 2

These semantics are deliberately stricter than WebSocket.

The behavior rgd protocol violation is described in the spec (drop the connection).

Note 1: that there is an outstanding issue to at least optionally allow for a less rude behavior when a protocol violation is detected ..

Note 2: in the future, WAMP AP might add a transport that relaxes the fully ordered requirement. I expect this to make sense when we add WAMP-over-UDP. But this is a totally different matter now.

oberstet avatar Mar 24 '17 06:03 oberstet

Text needed in spec with essence of above ..

oberstet avatar Feb 21 '18 11:02 oberstet

Please educate me on the rationale for using PING/PONG messages over the SO_KEEPALIVE socket option? Is it to have an interval that's configurable on a per-socket basis?

ecorm avatar Jun 14 '22 21:06 ecorm

Also, it's not clear from the spec whether the PING payload may be empty.

ecorm avatar Jun 14 '22 21:06 ecorm

@ecorm Well, my thoughts are this: ping/pong messages allow to have an application level control over the connection state. For example, when you're on mobile, real tcp connection can be in the opened state, but due to lags and low signal, it's not enough for application logic, may be there is an rpc registered on mobile client that can be called. In wiola router, I added a configurable timeout for this.

Also, it can be useful when you can not set SO_KEEPALIVE for some reason.

KSDaemon avatar Jun 14 '22 22:06 KSDaemon

rationale for using PING/PONG messages over the SO_KEEPALIVE socket option

SO_KEEPALIVE isn't under app control and might be unset (or set) by IP intermediaries. btw, similar for WebSocket ping/pong: those are at WebSocket level, and WebSocket intermediaries might drop (or add or reorder) such messages without any app control.

for WAMP, I often use a dead simply "echo(msg)" procedure that is called periodically from the other peer - this provides keep alive, heart-beating plus testing actual WAMP level functionality. also, when running over TLS, this traffic cannot be read or modified by network gear or other parties.

WAMP may run over non-TCP (eg a serial port or pipes or what), and then there is no SO_KEEPALIVE.

having said that: I think it is a nice feature of a WAMP impl. supporting TCP and providing a knob to control SO_KEEPALIVE - in addition to eg built-in WebSocket ping/pong or even WAMP-level ping/pong ...

Also, it's not clear from the spec whether the PING payload may be empty.

yes, it is only "implied": the above proposal ("strictly ordered pongs for every ping") requires pongs to be recognizable as associated with a given ping.

for websocket, this is different, and hence a zero-length ping payload makes sense.

from a practical application level perspective, I personally used 12 bytes: 8 bytes sent time plus 4 bytes ping sequence + N (optional) random bytes (up to max of 125 bytes total for websocket)

fwiw, here is the relevant auto-ping sending code of autobahn https://github.com/crossbario/autobahn-python/blob/b00c4d1cf24ca5ffe599bed5c8a6129266ce725b/autobahn/websocket/protocol.py#L1936

oberstet avatar Jun 14 '22 23:06 oberstet

Thanks @KSDaemon and @oberstet ! Up until now, our TCP connections were through the loopback interface, so the problem of dropped TCP connections never came up.

ecorm avatar Jun 14 '22 23:06 ecorm

@oberstet wrote:

Ideally, receiving an unsolicited PONG should be a protocol error.

Unsolicited Pong frames are permitted in Websocket:

A Pong frame MAY be sent unsolicited. This serves as a unidirectional heartbeat. A response to an unsolicited Pong frame is not expected.

Rawsockets should therefore also allow unsolicited PONGs to be consistent with Websocket.

ecorm avatar Oct 23 '23 23:10 ecorm

Unsolicited Pong frames are permitted in Websocket:

ah, right, thanks for reminding me!

Rawsockets should therefore also allow unsolicited PONGs to be consistent with Websocket.

ok, this one option. The other option is:

RawSocket is explicitly designed to be simpler, and more tight.

"consistency" with WebSocket is not a goal per-se.

btw, I never understood what " A response to an unsolicited Pong frame is not expected." is supposed to mean. If it is not expected, does that mean it is still allowed or not? if it is allowed, what will the other peer do with this "unexpected reply" then again? What is a "unidirectional heartbeat"? If I never get to notice that my heartbeats don't reach the other peer, or the other peer is dead/unresponsive, what's the point?

For me, a heartbeat is actually both a "keep alive" signal, and a "report lifeness" request that must be answered (I expect it to be answered, and if not, sth is going wrong). and a request to report lifeness must allow me to expect exactly one response, otherwise it's pointless to request at all.

Having said (ranted;) all that: if you guys think "consistency with websocket" should be a design goal for rawsocket, let's discuss, I am not totally stuck on my view/perspective. what does "consistency with websocket" mean, why is it a worthy goal, why not simply use websocket rather than rawsocket if one wants exact websocket semantics (that is what rfc6455 says)?

oberstet avatar Oct 31 '23 11:10 oberstet

btw, I never understood what " A response to an unsolicited Pong frame is not expected." is supposed to mean. If it is not expected, does that mean it is still allowed or not?

A response is not expected, but is technically allowed because unsolicited Pongs are allowed in general. Both peers should not respond to unsolicited Pongs because it would become an infinite loop of rapid pongs.

A ping/pong sequence allows both peers to know the other end is still alive. A unidirectional pong allows only the destination to know that the sender is still alive.

Let's consider the unidirectional pong use cases. For example, a server may want to know which clients are unresponsive so that it can drop them to conserve connection resources. In this same example, the client does not need to know continuously about the server responsiveness. Perhaps the client is a battery operated IoT device that wants to go into a sleep state as soon as possible after sending an unsolicited pong, whereas a ping/pong exchange would require the client to wait for a response after sending a ping.

There may be situations where the link between client and server is asymmetrical, and bandwidth needs to be preserved in one direction (like radio telemetry). Rawsocket is more likely to be used in this scenario than Websocket, but I admit that a datagram-oriented transport may be even more likely (versus a connection-oriented one).

As for consistency with Websocket, the advantages are:

  • Less cognitive load for the programmer in remembering the differences between Websocket and Rawsocket
  • More common code can be consolidated in implementing both Websocket and Rawsocket transports if they behave more similarly.

We should explore the rationale for Websocket allowing unidirectional Pongs. They might have discussed this in the IETF mailing list. Their discussion on this matter would help us decide if we want the same for Rawsocket.

p.s. I know the spec calls it "raw sockets", but I use Rawsocket as the transport name in my code and my fingers always want to type it that way. :-)

ecorm avatar Oct 31 '23 17:10 ecorm

We should explore the rationale for Websocket allowing unidirectional Pongs.

It proved helpful for me in an API I wrote once that streams a MySQL dump over a WebSocket connection. We used it as a means of duplicating MySQL data from one install to another. The receiver would, near the end of the stream, sometimes enable indexes. With a large backup it can take a long time to enable indexes. During this time the source’s send & target’s receive buffers are full, so we can’t ping. We can, though, send unsolicited pongs, which prevents routers from thinking the connection has gone dead.

FGasper avatar Oct 31 '23 17:10 FGasper

fantastic, thank you both for discussing this from a use case, practical perspective! that's the "right way" for finding good motivations of design aspects/decisions.

this might have been discussed in the websocket WG at that time ... not sure ... long ago, and the WG was ultra high mailing list volume, so I couldn't keep up with all discussions that happened in parallel. anyways;)

@FGasper may I ask rgd

During this time the source’s send & target’s receive buffers are full, so we can’t ping.

does that mean, the message buffers / queues within the websocket implementations of both websocket peers still had already received (and thus queued) messages that it didn't process yet?

and in such a situation, both peers want to make sure the websocket / underlying TCP connection is not timed out while messages are still being processed?

if so, that's a valid, understandable goal of course!

now, if that is the goal, what are the means to achieve that? for me, the first stop would be: while websocket requires strict in-order processing of websocket data frames (regular websocket messages), it does not require the same for control frames, or for the ordering relation between control and data frames.

most websocket implementations will process all frame types in-order - and hence would run into a head-of-line blocking situation like described - no ping/pong exchanges possible, since unprocessed data frames need to be consumed first before being able to reply to a new ping frame

was that the source of your itch?

what I am aiming at is: if a websocket implementation doesn't impose a data-control frame ordering relation, it could still answer ping frames with pong even though there are still data frames unprocessed at the app level

also: I am usually doing bidirectional ping/pong heartbeating. this allows both ends of a WS/TCP connections recognize dead peers quickly even in WAN scenarios. actually, this is just what TCP on WAN requires in general ... not specific to WS.

oberstet avatar Oct 31 '23 19:10 oberstet