-
Notifications
You must be signed in to change notification settings - Fork 101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Describe Raw transport #63
Comments
I'm not particularly satisfied with this for a couple reasons:
For the first, instead of using 4 bytes, WAMP could use a chaining approach (if the first byte is 0xff, there's one more byte following). This results in a variable-length big-endian number. The max number of bytes can be limited for now, but extended later. Or the WebSocket framing could be used (page 13), which has 7-bit length specifier (if == 126, the next 16 bits are used, if == 127, the next 64 are used). I'm sure there are other chaining strategies that could be considered. I'm more worried about negotiating an encoding. Once of the stated benefits of RawSocket is that it can be run behind a firewall (e.g. "misused" 443). This means the server must choose a single encoding for all clients. One possible solution is that clients can send a JSON I'm not sure if the complexity is currently worth it, but it seems like some kind of protocol negotiation is a better long-term strategy. From what I've seen, WAMP is designed for long-lived connections, so having a sizable connection overhead for negotiation is not a big deal. I'm not as worried about the message length overhead as I am about extensibility later. |
@beatgammit this reinvent half of WebSocket.
If one wants the features above, there is WebSocket. So we won't change RawSocket. It's already deployed on the street. |
The problem with websockets is that implementing it requires implementing HTTP, websocket handshaking and message bloat per packet due to the framing. The two features I mentioned only require a small subset of what WebSocket provides, and at least the second only requires a single message before the rest of the WAMP conversation. I see the benefit of the fixed 4-byte size (though I think 2 bytes is more than sufficient), but I still think a simple encoding negotiation is a good idea.
I thought that since the Advanced Profile is still in alpha it can still be changed. Perhaps a middleground is a way to change the encoding in an existing session? This way you don't have to listen on multiple ports, but you can still support multiple encodings. I think the common use-case for this is negotiate over JSON (easier debugging) and later switch to MsgPack for normal operation. I'm mostly worried about allowing clients that can't support MsgPack communicate on the same port as those that can. If you're set on not changing the RawSocket spec, I can bring this idea up on the mailing list for discussion. |
Sure, I excaggerated with "half of WS". Regarding "alpha" status of AP: yes, that right in general (for most of AP), but there are some features marked "stable". RawSocket isn't marked - which is a bug. Rgd: "mostly worried": I can see this point. Definitely. How about: The client initially sends a 4 octet magic that signals the encoding it wants to speak. If the router supports that encoding it'll go on and MUST speak that encoding. If the router does not support the encoding, it hard drops the TCP. Of course you could also propose a whole new transport for WAMP that does as you like. And gather feedback on the list. No problem. Given enough support, we might add it to the AP. What I can say is that I won't implement it in Autobahn or Crossbar.io. I don't need it. |
I've put up a discussion on the google group: https://groups.google.com/forum/#!topic/wampws/bSWpvAYbFDY |
Ah, great! I think an informal "open community" / rough consensus + working code approach is a good thing. Rgd the concrete concern "running on 1 port": are you also concerned about running WebSocket and RawSocket on 1 port? Might be doable also, since WebSocket starts with |
I'm honestly more concerned about the requirement to have a port per encoding than running multiple "transports" on the same port. For example, I may have 80, 443 and 8080 at my disposal, and if I have 80 and 443 tied up by their appropriate uses, that leaves me only one port to run on. I can run ws and wss on 80 and 443 respectively, but I can't (currently) run both JSON and MsgPack RawSockets over 8080. I think trying to run two "transports" on the same port is overly complicated. Just having a way of specifying the encoding at startup would satisfy me. I just think if this gets put in, there should be room for extensibility later should another connection-time feature be requested (e.g. encryption). WebSockets has this, but I don't want to be using WebSockets for a browserless client (i.e. arduino, desktop application). Each client has it's own limitations, so I think the spec should allow for some level of configuration. Honestly, a magic 4-octet header would be nice, but it also might be nice for there to be a required 4 octet response to allow for extensibility later. Just like the rest of WAMP, this will allow different implementations to support different features, but at least the basic format must be specified before RawSockets is "stable". |
running on port 8080 will limit the usefulness of RawSocket on restricted networks encryption: TLS. no need for WAMP to do anything special here. what should a 4 octet response magic signal or negotiate? this is still at WAMP transport level preflight .. no WAMP level features can be negotiated here. |
Sure, on some networks, but some restricted networks just block the port numbers without inspecting traffic. Obviously networks will differ, but I don't think that's particularly relevant to my main concern: future extensibility. From what I've seen, the rest of WAMP is very flexible, except for RawSocket. I don't think that should be the case.
Mostly transport-level features like serialization format, compression, encryption, etc. None of this is relevant to WAMP. I'm imagining some sort of bit-field where the client would request certain features (e.g. MsgPack encrypted with AES), and the server would respond with the subset of requested features that is supported (i.e. MsgPack, but not encrypted). If the server can't honor the request, the socket will be closed immediately (though responding with a 4-byte response is encouraged). Enough of the 4-byte request/response should be left open for possible extension later. This style of negotiation is very minimal, extensible, and uses feature-detection to connect to the server. Clients can be upgraded to support newer WAMP features independently of the server. With the current specification, if my server was to stop supporting JSON and only support MsgPack, the only way to signal this would be to reject connections on a dedicated JSON port. I'd also have to open a new port for every combination of features that could be supported (e.g. an AES-encrypted MsgPack port, non-encrypted MsgPack port, etc). This gets very tedious very fast and can be easily solved via a simple negotiation. I'm not saying that the meaning of the 4-byte packet needs to be specified immediately, just that putting that in place now will allow for extension later without having to introduce a new Transport. |
Flexbility comes at a price: complexity. RawSocket should be dead simple. Regarding the concrete future extensibility features you have in mind:
It all gets more complicated quite quickly. Regarding networks compatibility: for me, this is one of the most important concerns. Many mobile and enterprise networks are very restricted. In summary, what I'd like to see is this: being able to run WebSocket and RawSocket and Long-Poll with all possible serializers on 1 TLS server port 443. This is a goal I can subscribe to. And I am ready for the simplest change that allows above. The simplest change would require the client to send a 4 octet magic (just an unstructured bunch of random octets fixed in the spec) depending on the serializer it wants. Those 4 octets must not be equal to The concerns I have with the former are:
In other words: if we specify a reply by server, I'd like to see the ability to signal "positive OK" as well as specific error reasons for rejections rather than negotiation. Those could be magic values also. No need to have structure inside the 4 octet request/reply. Should we have an (unstructured) 4 octet magic reply, those magic values must be different from In a way, using magic values already provides some kind of extensiblity: e.g. new magic values for new serializers. New magic values for new error signals. |
Apparently there hasn't been any discussion on the google group yet, so I'll describe my proposal here.
If this is the direction it will go, then it's best to completely avoid anything that might be mistaken for HTTP or TLS. After reviewing the relevant specs, it looks like HTTP's first byte is ASCII minus control characters (look at So, I propose that we do something like this:
The response could look something like this:
Only one bit on each of bytes 2, 3 and 4 would be set on the response. If an encryption format is accepted, the specification would dictate how that is to be set-up. Once encryption is set up, the socket is ready for WAMP. In the case of byte2 being an error, bytes 3 and 4 can be used for signifying what the error is. For future-proofing this, the MSB of byte2 will be reserved for future use (set to 0 always until the meaning of setting to 1 is defined). I think this gets us both what we want:
Would this style of "negotiation" work for what you'd like to use it for? Note: I'm mostly just concerned about serialization formats, but since there's available space, I felt these are reasonable things that could be specified at the transport level. |
@beatgammit we're getting closer;) rgd. "it's best to completely avoid anything that might be mistaken for HTTP": good idea! the first octet should be fixed to avoid that altogether. rgd. TLS:
In summary, you convinced me of the following - and it's my understanding that we two reached consensus on this:
plus
In particular the latter is a real value-added proposition. I think this is important, since implementers that already have RawSocket will have to change their implementations. Where we don't yet have consensus is the following. Encryption
I am fixed on this. I don't think there is an argument that would convince me (see above). Compression Prenote: doing compression at the underyling transport level: I guess you are talking about TLS compression here? If so: please not. This can be a severe security issue (side channel attacks). If you can somehow trick the peer into sending/receiving known plaintext, you can measure the compressed length from outside and draw conclusions about the session key in use. If we are talking about TCP: there is no compression avail at that level. Then, from my experience implementing On the other hand, having compression on RawSocket: that would be an additional value proposition. If it is worth the complexity added is another question. But on this (and only this) point I'd be ready to continue a discussion. Here is my KISS proposal:
and replies
That is all unstructured magic values (I just used some random stuff), but first octet fixed. It is extensible in two dimensions:
It does not allow compression. It does not provide extensibility for yet unknown dimensions. However: YAGNI! ;) |
Here are a couple reasons why you may not want TLS but still need encryption (I'm sure there are others):
I understand that TLS is basically another transport since it's basically a black-box (data goes in, same data comes out), but there have been very real problems with popular TLS implementations. When it comes to security, there should always be multiple options in case one is compromised.
This is exactly what I'm talking about and why I think it should be part of the RawTransport spec. If a transport meets the preconditions (message-based, bidirectional, etc), then it can be used for RawTransport and features like encryption and compression can be done on top of that, just like TLS does.
And mine is extensible in 4:
It's also KISS since an implementation that only does MsgPack and JSON only needs to check two "magic" values as well. Just reserving the last two bytes on request and response (in non-error case) is enough to allow the above to be added later. If you're really adamant about your proposal, I'm okay with that and I consider it a net win from the current spec, and exactly what I originally hoped to achieve. |
@beatgammit To make compression effective, it needs to cross WAMP message boundaries. Compressing each WAMP message individually will be of very, very limited gain. I have measured this. If you turn off "compression context takeover" on Compression does need negotiation in practice, since there is a tradeoff between compression effectivness and memory consumption (among other costs like CPU cycles of course). With TLS itself or TLS implementation being broken: sure. The solution is to update a system regularily. The solution is not to reinvent TLS or write a transport level encryption from scratch. You likely will get it wrong. "adamant": I look at that last proposal not as "being mine", but as the culmination of a fruitful discussion, that has elements brought in by both of us, and which seems to solve the original itch you felt. It's just that I need to be able to defend it publicly .. means I need to be convinced of it .. since implementers might (rightfully) object that we are adding and changing stuff all the time;) I honestly see it as the simplest change that fits the bill. With a clear value prop: able to run all transports mentioned on 1 port. If you agree, could you take the proposal to the WAMP list? Shouting for a consensus call with implementers. Maybe some guys wake up .. =) Another note: if you care about TLS-independent encryption and/or end-to-end encryption, there is #81 . I'd highly welcome if you would take part in that (should you care) - you've been taking part and helping improving WAMP for so long .. |
I think this may alleviate my main concerns. The question of compression (and batching) seems like it should be a separate thing altogether since there's no gain according to your tests. I still think it's wise to leave the last two bytes untouched just in case a valid use for them is thought up later (e.g. automatic connection re-establishment, message batching, max message length, etc). That being said, I'm satisfied for now with your proposal and I'll try the mailing list again to see if anyone else has any other input. |
The "batched modes" of WAMP transports: I came up with this as a conclusion from this: http://tavendo.com/blog/post/dissecting-websocket-overhead/ I don't have a blog post on |
@beatgammit One more thing: besides the "one port" value prop. you mention "microcontrollers" on the mailing list. This is interesting. Can we bring WAMP to a 8KB RAM Arduino? RawSocket is much easier to implement than WebSocket, and can be implemented efficiently (length prefix, no fragmentation). There is one concern: memory. A hard length limit on the maximum serialized message length. That would allow use of a statically allocated memory buffer. I'd be interested in continuing a discussion on this. There are 3 concerns:
My current view:
What do you think? |
I don't see why not. At one time I had a project on Arduino that used WebSocket framing and JSON to control a servo. It didn't do HTTP and the full WebSocket negotiation because I didn't want that overhead. It wasn't real-time or anything, but I think WAMP is light enough that it would work quite well, especially with a faster serializer. I'm sure a C++ WAMP client could be easily adapted for the purpose.
I think this is the way to go. Keep RawSocket a general transport and make a more custom transport if necessary. I think RawSocket will be more than sufficient for my projects and I can use the same implementation for desktop applications and embedded. |
I think this is a good improvement to the RawSocket proposal. I would argue for reserving the last two bytes until we have a concrete use for them though. One byte should be enough to specify the serialization, and if we add anything else to the four byte message later this will keep things simpler (by not having to create magic 3-byte numbers for every combination of features we want to specify) There are two possible formats for this:
Both methods are simple to implement, but the second one would have less overhead for establishing a connection (where RawSocket is meant for low overhead). I think either method would be acceptable though. |
@beatgammit rgd AutobahnCpp: that will be very hard to bring to an Arduino. one reason is the use of C++11. I'd probably rather write a AutobahnC thing that uses pure C89 for maximum reach to MCUs. @jcelliott 8 bit field for serializations: yes, the client can signals all serializers it supports, but it can't prioritize which one to speak. how would the server select if it supports all of the client one's? WAMP-WebSocket has full priorization/negotiation support on this - but we should not start reinvent WebSocket .. as that would render RawSocket pointless: we should keep it really simple IMO Ok. You both seem to lean towards having "structure inside the 4 octets". How about this: Client-to-Router request (first 4 octets sent):
Router-to-Client response (first 4 octets sent):
Example:
|
Besides above change (whatever version we reach consensus on), I'd like to discuss also the option of adding ping/pong to RawSocket. Related to #90 In a way, ping/pong for connection loss detection and keep alive is really a transport level feature. I somehow feel uneasy adding this at the WAMP level. Here is one option to add ping/pong for RawSocket: Now: A message is serialized, and prefixed with the length of the serialized message as an unsigned integer (4 octets). Proposal: The upper (MSB) 2 bits of the unsigned integer are taken out as an opcode:
In case of PING or PONG, the length (after taking out the opcode) provides the length of the subsequently following byte string which is to be echo'ed back exactly. With the proposal, a serialized WAMP message can have at most 2^30 = 1GB length. I still think this is total overkill in practice - more than enough. |
Yeah, that could cause problems. And I think Arduino's C++ is stripped-down anyway. I honestly much prefer C to C++ anyway, especially since it's easier to write bindings to in other languages.
That sounds great! The more I think about it, the less I like the bitfield idea. A client can try with the preferred encoding, and if it fails, try the next. For short-lived clients (e.g. micro-controller that can't sustain connections), the accepted encoding can be stored in memory for the next connection attempt.
I guess this depends on how much WebSocket functionality RawSocket should have. Do you think there's any sense in doing interleaving partial messages? If so, the whole first byte could be reserved. This would still allow for 16M messages (just so happens to be max size for MongoDB/BSON documents), which should be more than sufficient for WAMP. Large messages could be broken up into smaller chunks just like in WebSocket transport. I don't personally have a need for documents > Perhaps this is getting too complicated though, since RawTransport is meant to be simple. |
Hi, I just stumbled over this while trying on collecting some more info on WAMP. Serialization negotiation Yes, that would make the initial handshake also variable length and probably 2 asynchronous reads. But I don't think that's a big problem. As the connection is anyways long-running and connection establishment is only a small part of it I don't see the need why these packets must be limited to 4bytes. Actual message header Pings/Pongs Transmitting the maximal message length |
No. This would make RawSocket more complicated than WebSocket. For WebSocket, multiplexing or priorization are not part of RFC6455. There are multiple, unfinished proposals to add that as a WebSocket extension. E.g. here is mine: https://github.com/oberstet/permessage-priority/blob/master/draft-oberstein-hybi-permessage-priority.txt Anything in this direction will likely make it impossible to run RawSocket from statically allocated memory - which is what I am after on MCUs. I'd like to be able to do the following in C on a MCU:
and use
You can run RawSocket with different serializers today. It's just not possible to run all serializers on 1 server port. Which is the itch that we want to address.
Adding full negotiation makes it too complex. Which is the whole point of RawSocket: make it dead simple. If we drop that goal, it's pointless altogether, since then we can simply use WebSocket.
Why? Mask out the top 2 bits to get the length. If the opcode == 0, process as today. If opcode == 1, it's a ping - echo back. If opcode == 2, it's a pong.
No. RawSocket is so much simpler than WebSocket because you always exactly know how many octets you must receive before you can go on processing.
The maximum length a peer is able to receive is communicated in the handshake. It's just a power of 2, instead of an arbitrary integer value. If the peer send a longer message, the receiver will hard drop the connection. That is a transport level violation. Same goes when serialized value is invalid. Hard drop. A non-conforming peer is either a buggy implementation or an attacker. In both cases, the sane way is to hard drop, not to handle it (which might introduce even more problems). |
Ok, I didn't recognized that, thought RawSocket was MsgPack only (because of AutobahnCpp). So the problem is that currently you need to know on client-side which serialization the remote uses? That's no big problem. And binding to 2 ports on server-side isn't a big problem either.
Yes - extracting the opcode is easy. But to handle it properly you need a statemachine in the Transport. If you received a ping you might not immediatly need to send a PONG when you already have one queued. And when you are currently sending something else you can't immediatly send the reponse. In my experience from implementing websockets such things also introduce lot's of coupling between sending and receiving, which is complicated if you do that in multiple threads.
I would still know that. There could be a fixed length part of the header which indicates how long the optional variable-length part is. You could even make it completly the same as the remaining message by making the handshake also a [
My question was that if the receiver will hard-drop the connection on too-big-messages what is the advantage of telling the length to the transmitter? The receiver can do this also without exchanging the maximum length. |
Yes, exactly. Both need to have implicit knowledge about which serializer to use. AutobahnCpp only implements MsgPack (currently) mainly because I hadn't time to do JSON as well.
That is how WebSocket does it (in fact, WebSocket doesn't require to respond at all. The spec says: "respond as soon as practical" - which might be never). We should do better and simpler with RawSocket: each and every ping must be replied with exactly one pong, and unsolicited pongs are forbidden. Then you don't need a state machine.
Don't use threads;) Ok, sometimes it's required, e.g. AutobahnAndroid uses background reader and writer threads. But it's a non-issue there, since it employs queues: the receiving leg that gets pinged can just enqueue the pong onto the sending leg. With RawSocket, my main two use cases are:
Why would we need a variable length header?
The transmitter e.g. a Router could things like, e.g. for PubSub
Yes, that's possible today. E.g. for production use, Crossbar can be configured to hard-drop WebSocket upon exceeding a configured message (or WebSocket fragment) length. DoS protection. |
That makes sense. I don't think I'll need it (main use-case was streaming large binary assets without blocking other messages) because I can just use multiple connections, just wanted to know your thoughts about it. I'm on board with the current proposal then.
👍
You can get the same effect by reconnecting with different requested encodings until one is accepted. You get the same effect without requiring additional complexity, and you only sacrifice a few reconnects (once; the accepted encoding can be cached if reconnects are expensive).
Also, some non-essential fields could be dropped for clients that can't receive the whole payload. This information isn't currently available, but I can see that being specified later. |
I have written up the proposal on this branch: https://github.com/tavendo/WAMP/blob/rawsocket/spec/advanced.md#rawsocket-transport This is very close to what we have discussed above, but allowing for more extensibility. 2 octets in both the handshake request and reply are reserved. The opcode in messages goes into the upper octet of the message prefix. That leaves 16M for a message maximum (which I agree with @beatgammit is already more than enough/sane in practice anyway). The max msg. length now MUST be announced by both peers (can be between 512 - 16M). I announce that on the list. To all interested: please review if you have time. I'd like to merge to trunk, mark the feature as stable, and then update AutobahnPython and AutobahnCpp. |
You are missing 1byte in both the request and response for the handshake. Regarding ping/pong: I just saw in the advanced specification that also heartbeat messages are supported. Wouldn't that make the a ping/pong superfluos (or the other way around)? As long as something is regularly sent the TCP stack itself will care about the connection beeing open or report errors when data (or an ACK) could not be transmitted. |
@Matthias247 missing reserved octet: ah, thanks! fixed. rgd reconnecting for finding a serializer the router speaks: in practice, most routers might simply speak all serializers. whereas a client might only speak one often. and even if the former doesn't hold: a client can remember what serializer worked. chaning this would require a handshake request of variable size (if priorization should be taken into account). rgd "hearbeat": yes. in fact, heartbeat was mixing 2 features, one of them being ping/pong like. we will remove heartbeat completely (for now). see my mail on the WAMP list. |
And if that's not possible (e.g. microcontroller w/o writable persistent state), msgpack is in the basic profile so it most be supported by a WAMP-compliant router, so it's probably the one that'd be used. A microcontroller like this would probably only be able to support one serialization anyway, so it doesn't really matter. Other clients would probably be able to remember some kind of state, so this is only matters for the initial connection. @Matthias247 - Can you think of a use-case where this would not be acceptable? |
this is in the spec for some time now |
@oberstet I just saw this on Stackoverflow.
Shouldn't have RawSockets a mask as well? |
no, because a) browser cannot talk rawsocket, and b) everything should run over TLS anyways |
WAMP can run over Raw transports instead of WebSocket.
Each message is prefixed with a
uint32
(big endian) that provides the (serialized) length of the following WAMP message.This works for both JSON and MsgPack serialization.
The text was updated successfully, but these errors were encountered: