Skip to content
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

Error URI for Rate Limiting Purposes #510

Open
ecorm opened this issue Nov 21, 2023 · 25 comments
Open

Error URI for Rate Limiting Purposes #510

ecorm opened this issue Nov 21, 2023 · 25 comments

Comments

@ecorm
Copy link
Contributor

ecorm commented Nov 21, 2023

There is currently no standard mechanism to allow the rate limiting of WAMP messages to mitigate DoS attacks, or just to prevent memory exhaustion during heavy loads.

HTTP has the 429 Too Many Requests response status code, but WAMP does not have an equivalent error URI. WAMP router implementations wanting to reject messages due to rate limiting have to come up with their own non-standard error URI.

While HTTP reverse proxies can do rate limiting for HTTP requests, they don't understand the WAMP protocol over Websocket, so they're of no help to us in that respect.

WAMP client libraries wanting to support message rate limiting would need to understand a wamp.error.too_many_requests (or similar) error URI and implement a backoff-and-retry mechanism (or otherwise pass that burden up to the app developer).

The client-initiated messages that would be considered "requests" for rate limiting purposes would be:

  • SUBSCRIBE
  • UNSUBSCRIBE
  • PUBLISH (acknowledged)
  • REGISTER
  • UNREGISTER
  • CALL

A "request" that's rejected due to rate limiting would receive an ERROR response with an URI such as wamp.error.too_many_requests.

Note that an unacknowledged PUBLISH would be "lost" if it got rejected due to rate limiting.

I'm not asking for a specific rate-limiting algorithm to be standardized. I think all that's needed in the WAMP standard is a new error URI for this purpose.


I am aware of issue #36 , but it's more about the router not spamming clients too quickly with pub/sub events. The issue I'm raising here is to prevent clients DoS'ing routers.

@oberstet
Copy link
Member

oberstet commented Nov 21, 2023

Giving this some love definitely makes sense, in particular as I guess it popped up in real world usage, and is about closing small gaps which improve on practical use.

In my eyes, these are the elements we need for a complete solution (and I might still have missed sth .. not sure):

  1. feature announcement (client and router)
  2. how are imposed limits (the current limits) announced
  3. how are imposed limits controlled: is there dynamic control from client, or is this a pure static router side setting)
  4. how to communicate once limits are exceeded: standard error URIs
  5. what happens once limits are exceeded: client and router, specifically ..
  6. how do clients deal with once a limit is exceeded: backoff-and-retry mechanism (which is purely a client side mechanism I guess)

The title of this issue suggests to me it is only about 4., and agreeing on one specific string as a standard error URI is good, easy and required, but the stuff around - the semantics - is what it makes the string into something of actual use in my eyes. Just my 2cts at least;) Let's discuss.

@KSDaemon
Copy link
Collaborator

While I agree that it would be good to discuss the 1-6 pts @oberstet named, even this small URI introduction - is already a good improvement.

Let's introduce it. And start the discussion of other things in parallel.

@oberstet
Copy link
Member

oberstet commented Nov 21, 2023

Let's introduce it.

sure! good idea to split it up into smaller pieces.

even though: the error URI should use a suggestive name, capturing/pointing to the right semantics, and thus the name cannot be fully separated from the other pieces

The client-initiated messages that would be considered "requests" for rate limiting purposes would be:

the spec talks only about WAMP actions I think .. at least for "publish" and "call" .. not sure.

we definitely should use a consistent wording ... and "request" doesn't IMO ...

is "publish" about limits of number of PUBLISH messages originally sent by a client, or are the limits about number of EVENT message resulting

in the latter case, this only underlines even more the problem with using "request" ..

similar for CALL ... in the presence of partitioned/parallel/distributed calls ..

the REGISTER, SUBSCRIBE etc messages are different .. the load resulting in a router is more tied to the overall size of the set of registrations/subscriptions for the originating client, and from the effective resulting load later on

eg 1 SUBSCRIBE to 1 wildcard . URI is just 1 "request" / action, but it will result in a potentially massive downlink traffic as the router will forward every published event due to the "fit-any" prefix/wildcard

but I think we can nevertheless follow your suggestion and intro only the URI in a first piece after we have generally agreed about the rough scope ... at least for me, this is pretty unclear at this point still ... is it about limiting client load (protecting clients, and routers only secondary), is it about limiting router load (not necessarily the same as the previous), is it about limiting total set size on registered URIs by clients or about load resulting from the former, is it about turnover of such registered/subscribed URI by client per period of time, and so on ...

@oberstet
Copy link
Member

to give an idea of what I mean .. couple of URIs alternative/adding to wamp.error.too_many_requests .. this is just a quick unpolished brain dump:

  • wamp.error.per_uri_pattern_limit_exceeded - this could cover both new regs/subs per second, total number of regs/subs per originating client and so on
  • wamp.error.calls_per_second_exceeded and similar - quite specific ..
  • wamp.error.registrations_exceeded and similar - could only apply on a per client (authid or role) basis rgd number of registered procs and such

@KSDaemon
Copy link
Collaborator

@oberstet I think you are talking about more fine-grained limit control, while the original @ecorm idea is about a generalized error URI.

And actually, they don't contradict each other.

So In my eyes wamp.error.too_many_requests is about limiting all wamp actions initiated by the peer, which include publish, subscribe, unsubscribe, call, register, unregister (again - almost any WAMP message initiated by the peer). Kind of general DDoS protection.

While wamp.error.registrations_exceeded and Co - are about more specific limits.

One WAMP Router implementation may do just a general limiter - it is fine, while the other one decides to go beyond and implement all fine-grained limits.

@oberstet
Copy link
Member

oberstet commented Nov 21, 2023

And actually, they don't contradict each other. ... Kind of general DDoS protection.

Ok, makes sense!

You are right, I was more thinking in WAMP higher level terms.

If this issue is more about lower level WAMP terms, then we might think of it more in terms of WAMP transport limits and protection?

If so, we should have a clear picture how that in turn differentiates/relates to the (still to be done) upper level WAMP limits (in general), and to limits below WAMP transports - namely pure network level limits, like from your standard IP or TCP level traffic limiter or DoS protection.

In this case, rgd naming, here is a quick brain dump of options that come to my mind:

  • wamp.error.trafficlimit.* as prefix for all such errors
  • wamp.error.trafficlimit.uplink_messages_per_second_exceeded: whenever the router receives ANY message from the (client) peer that exceeds the number of messages per second allowed. and "ANY" includes CALLS, but also call results (sent from client), progressive results, publish ACKs, new SUBSCRIBEs, any WAMP message
  • wamp.error.trafficlimit.uplink_bytes_per_second_exceeded: this limit can hit even within a single message

or similar. why? pls let me explain my thinking.

first, I can't think of any other metrics than "number of messages per second" and "message data volume per second" which would make sense without reaching into error conditions which are better treated in the higher level limits (not treated in this issue).

The main differentiator to lower levels (a general IP or TCP or TLS limiter): we can deal with such limits on a

  • per realm
  • per client authid or authrole
  • ..

basis, which a lower level limiter can not. the latter in fact will usually only see an encrypted TLS connection on top of a TCP connection on top of IP .. it doesn't have any idea about "individual WAMP messages", realms, authid or authroles ..

following this perspective, the question then becomes: should the error URIs name errors differently depending on the subject the limit applies to?

that is realm, session, authid or authrole.

the object of error is always a limit violation (wamp.error.trafficlimit.*), and there are exactly 2 kinds:

  • messages/sec ( wamp.error.trafficlimit.uplink_messages_per_second_exceeded) and
  • bytes/sec (wamp.error.trafficlimit.uplink_bytes_per_second_exceeded)

writing this, I see above misses one final dimension: the direction (uplink and downlink).

do we want the limits to cover only 1 direction?

is it possible that a router fires a limit without the client sending anything?

eg. when a subscribed client would receives an excessive amount of EVENTs if the router would honor the subscription?

@oberstet
Copy link
Member

oberstet commented Nov 21, 2023

IOW, how about

  1. one error namespace wamp.error.trafficlimit (and I don't care much about the names .. more about the structure of semantics we assign),
  2. four error objects (message rate or volume in uplink or downlink direction), and possibly
  3. four error subjects (realm, authrole, authid, session).

So 16 in total.

rgd "subjects", besides the four .. "per-procedure" or "per-topic" errors like "message volume uplink exceeded" would classify higher level? just asking;) but I think this is really the complete design space to think about .. pretty sure at least;)

Do we want to have 16 error URIs wamp.error.trafficlimit.* for these, or do we want one for all of the 16 cases (which exist whatever names we choose)?

@ecorm
Copy link
Contributor Author

ecorm commented Nov 21, 2023

As @KSDaemon emphasized, I'm looking more for a generic rate limiting mechanism that doesn't care about message types, as long as they originate from the client. The algorithm I was contemplating is the one provided by Nginx (https://www.nginx.com/blog/rate-limiting-nginx/) with burst mode enabled. It is also called sliding window in HAProxy.

Having this simple mechanism has an immediate practical effect in my router code: I can have a fixed-capacity per-session queue of client-initiated WAMP messages, and can prevent a client crashing the router by simply exhausting memory by spamming "requests". By also limiting the number of client connections and the maximum WAMP message length, I can put an upper bound to memory usage. Instead of number of messages, the per-session queue limit could also be based on the sum of queued message lengths. But that's an implementation detail and how it's done should not be mandated in the WAMP spec.

But if you want to consider finer-grained limits, that's fine too, as long as it doesn't preclude the generic one. Even finer than per-message-type would be individual API endpoints (i.e. individual RPC URIs).

  1. how do clients deal with once a limit is exceeded: backoff-and-retry mechanism (which is purely a client side mechanism I guess)

I would suggest exponential backoff, but I don't think this should be mandated by the WAMP standard.

we definitely should use a consistent wording ... and "request" doesn't IMO ...

Why not "request"? It is a message originated by the client for which a response is expected (request-response). Those familiar with HTTP/REST would immediately understand its meaning. Instead of GET, POST, etc, the verbs in WAMP are SUBSCRIBE, CALL, etc. I'm open to other suggestions, but I can't think of a better term myself.

Do we want to have 16 error URIs wamp.error.trafficlimit.* for these, or do we want one for all of the 16 cases (which exist whatever names we choose)?

Unless the client needs to react differently per URI, they don't need to be distinct error URIs and more details could be added to the ERROR message as payload arguments.

Let's introduce it. And start the discussion of other things in parallel.

I agree, but I don't mind that we first discuss an error URI "namespace" (as suggested by oberstet) under which all future rate limiting errors would be put.

@ecorm
Copy link
Contributor Author

ecorm commented Nov 21, 2023

is it about limiting router load

This has to be the first priority in our discussions (but it doesn't have to be to the exclusion of others). Crashing an entire server is more damaging than crashing a single client.

@ecorm
Copy link
Contributor Author

ecorm commented Nov 22, 2023

In my eyes, these are the elements we need for a complete solution (and I might still have missed sth .. not sure):

  1. feature announcement (client and router)

It should just be one item, and further details on limits/quotas would be communicated via HELLO/WELCOME details or meta procedure.

  1. how are imposed limits (the current limits) announced

Perhaps a meta procedure? I'm already finding the HELLO/WELCOME messages way too verbose with the role/feature dictionaries.

  1. how are imposed limits controlled: is there dynamic control from client, or is this a pure static router side setting)

I'm not sure I understand the question, but the router should be in control of its own limits via configuration.

If clients have their own limits (e.g. EVENTs per second), I'm not sure what the router can do to respect them. There is no notion of QoS in WAMP. Does the router simply discard events that cannot be delivered to clients due to client limits?

  1. how to communicate once limits are exceeded: standard error URIs

This was the goal of my original post.

  1. what happens once limits are exceeded: client and router, specifically ..

I don't think the exact algorithms should be baked into the WAMP spec, just the ERROR URIs and message options.

  1. how do clients deal with once a limit is exceeded: backoff-and-retry mechanism (which is purely a client side mechanism I guess)

Again, the exact algorithm should not be specified by the WAMP spec. Same way as it's not specified for HTTP when a 429 Too Many Requests status code is returned.

@ecorm
Copy link
Contributor Author

ecorm commented Nov 22, 2023

OW, how about

one error namespace wamp.error.trafficlimit (and I don't care much about the names .. more about the structure of semantics > we assign),
four error objects (message rate or volume in uplink or downlink direction), and possibly
four error subjects (realm, authrole, authid, session).

So 16 in total.

Instead of a combinatorial explosion of error URIs, there could be a single error URI with two payload arguments:

wamp.error.limit_exceeded

Argument limit:

  • request_rate,
  • subscription_rate,
  • subscription_quota, (number of active subscriptions)
  • publication_rate
  • registration_rate,
  • registration_quota, (number of active registrations)
  • call_rate
  • call_quota (pending calls that that have not yet returned)
  • bandwidth

Argument scope:

  • router,
  • realm,
  • authrole,
  • authid,
  • session

@oberstet
Copy link
Member

oberstet commented Nov 23, 2023

Instead of a combinatorial explosion of error URIs

yeah ... but what's the problem?

URI components are just arguments .. of a flavor different from regular args/kwargs ... that happen to allow to filter by URI pattern matching - which args/kwargs do not. so URI components are strictly more powerful than regular arguments.

having said that: I also feel a threat of "combinatorial explosion". but it's not tied to use of URI components vs args/kwargs. it is tied to the inherent complexity of subject matter (routing) that needs structuring. you cannot escape by using args/kwargs instead of URI components. that would be a following a chimera. in my mind/thinking at least;)


I agree, but I don't mind that we first discuss an error URI "namespace" (as suggested by oberstet) under which all future rate limiting errors would be put.

ok, yes, agreed! let's nail that first.

rgd the URI namespace wamp.error.limit_exceeded you suggested.

this pretty generic .. and fuzzy .. I'm just saying .. maybe that's what we want ;) how about wamp.error.houston_we_have_a_problem then? just a joke! ;)

  • a limit on what resource where? (e.g. in the client? or in the router? main line power consumption of router server? maximum operating temperature? router memory consumption? CPU cycles available for the router in total? in sum on the machine? etc),
  • a limit exceedance _caused by whom? (e.g. other stuff running next to the router on the same server? other WAMP clients using the same router, but in a different realm? etc)
  • a limit controlled/set by whom?
    if we don't want "whom" (origin of limit exceedance) encoded in namespace, we should at least think about the "what" .. some alternative names for namespace:

e.g. for a thermal limit exceeded: it cannot be set, but is given by the system hardware. it may or may not be caused/originating from a user. if not, why report to the user?

basically, the only difference of wamp.error.limit_exceeded from wamp.error.houston_we_have_a_problem is:

the problem is related to a resource which is necessary for router operation, but is "limited" in amount available

how about: wamp.error.router_resource_exceeded with the understanding that "router resource" is one of CPU cycles, main memory or network interface?

or wamp.error.resource_exceeded.router.* and wamp.error.resource_exceeded.client.* ?

note that for my ears (and I'm not a native speaker), "resource exceeded" is more clear than "limit exceeded" .. the former implies there is some "resource" (assumed necessary for operation), and it's "empty/low". the latter only implies numbers with ordering and size (a metric) and a "threshold smaller/bigger"


By also limiting the number of client connections and the maximum WAMP message length, I can put an upper bound to memory usage.

really? are you sure? ;) there are many ways. don't get me wrong. it would be highly useful to have a systematic analysis of this!!

e.g. if I can connect to a router at all, and if I can trigger any non-fatal error like "no such procedure", what about amplification attacks? e.g. can I trigger an error bigger than my request? like https://www.cloudflare.com/learning/ddos/dns-amplification-ddos-attack/

@ecorm
Copy link
Contributor Author

ecorm commented Nov 23, 2023

URI components are just arguments .. of a flavor different from regular args/kwargs ... that happen to allow to filter by URI pattern matching - which args/kwargs do not. so URI components are strictly more powerful than regular arguments.

Having a large number of error URIs that is multiplied every time someone adds a new component choice is very problematic to me, because I map each of the known WAMP error URIs to a C++ std::error_code in my API. I seem to remember a similar string-to-numeric error code conversion being done in Autobahn/Crossbar as well -- or perhaps it was the other way around.

If an implementation wanted to do string-based pattern matching on the ERROR, couldn't they just generate a string by concatenating the URI with each payload argument and a separator?

rgd the URI namespace wamp.error.limit_exceeded you suggested.
this pretty generic .. and fuzzy .. I'm just saying
a limit on what resource where?
a limit exceedance _caused by whom?
a limit controlled/set by whom?

wamp.error.limit_exceeded was intentionally generic because the answers to (some of) your other questions were part of the payload arguments I proposed. Those two arguments were directly inspired by your 16 error URI suggestions, where I factored out the "multipliers".

e.g. for a thermal limit exceeded: it cannot be set, but is given by the system hardware. it may or may not be caused/originating from a user. if not, why report to the user?

I don't know where you're going with this question.

how about: wamp.error.router_resource_exceeded with the understanding that "router resource" is one of CPU cycles, main memory or network interface?

"Exceeding a resource" does not make semantic sense in English. You exceed the limits of a resource in its usage. Let's take fuel usage as an example of a resource. You can't say "you exceeded fuel", nor "you exceeded fuel usage", but you can say "you exceeded the fuel usage limit". Or consider the English phrase that is well-known to even non-native speakers: "exceeding the speed limit". Don't tell me you never heard the phrase "exceeding the speed limit". 😁

Also, the limits are not necessarily tied to a single specific resource. If I impose a "request rate limit" of 100 requests per 10 seconds, it affects both memory and CPU resources.

how about: wamp.error.router_resource_exceeded with the understanding that "router resource" is one of CPU cycles, main memory or network interface?

No, because of the English semantics I explained above (exceeding a resource), but I would be okay with wamp.error.router_limit_exceeded or wamp.error.limit_exceeded.router, with more specifics available under payload arguments.

@ecorm
Copy link
Contributor Author

ecorm commented Nov 23, 2023

Another approach I'm toying with in my mind is that the error URIs prescribe how the client should react:

  • back off and retry (rate limiting)
  • unsubscribe or unregister and retry (quota exceeded)

Details on the limit exceeded and scope would be in payload arguments.

Sorry, too tired to elaborate more right now, so I'm just dumping the idea.

@ecorm
Copy link
Contributor Author

ecorm commented Nov 23, 2023

Another approach I'm toying with in my mind is that the error URIs prescribe how the client should react

I gave this some more thought, and this is not a good idea because there would be applications where the client is in the best position to know how it should react. For some operations, the client could decide that data loss is acceptable (to avoid overloading the server) and not bother retrying. For example, a client could be reporting a sensor value every minute. If an acked PUBLISH operation fails due to rate limiting, the client could decide to abandon the failed request and just wail until the next sensor update tick to publish the latest sensor value.

@ecorm
Copy link
Contributor Author

ecorm commented Nov 24, 2023

Here is a big brain dump of me stepping back and looking at the big picture. I do not claim to be an expert in any of the computing fields mentioned below. I do not have time to become an expert, and only need a simplistic, generic rate limiting mechanism like the one provided by Nginx. The products I'm working on will be mainly accessible via local networks and will not be exposed directly to the Internet.

I took the time to write this analysis so that we can establish an initial naming convention for error URIs related to rate limiting and resource exhaustion. I have no intention of implementing all the ideas I write below.

Problems

There are several networking problems being addressed here, and the rabbit hole can go deep for all of them.

  • DoS mitigation
  • Abuse mitigation
  • Fairness
  • Load shedding
  • Graceful degradation
  • Congestion control
  • API usage limitation

Usage Limits vs Resource Exhaustion

We should distinguish between two broad categories of errors:

  1. The exceeding of configurable usage limits that exist to mitigate DoS attacks, mitigate abuse, limit server operation costs, enforce fairness, and enforce limitations based on service tiers
  2. The exhaustion of resources such as CPU, memory, sockets, file descriptors, etc.

Note that usage limits can be established to prevent the exhaustion of resources. And current resource usage can be used to dynamically set the usage rate limits.

Usage Limits

Usage limits to mitigate the above listed problems can be configured/enforced at the router level, but it could also be done at the application level. For example, API usage limits could be directly enforced by a callee, or the router could be directed to enforce it on the callee's behalf. Limitation mechanisms can be generic (requests per second per user), or they can be API-specific (customer Bob can only call com.myapp.order_pizza 3 times per day with his service plan).

Generic limitations that could be configured/enforced at the router include:

  • simultaneous connections
  • authentication attempts and cooldown/lockout
  • number of logged in users
  • duplicate user connections
  • request rate
  • subscribe rate
  • subscription quota
  • publish rate
  • register rate
  • registration quota
  • call rate
  • call quota (pending calls that that have not yet returned)
  • bandwidth (bytes per second)
  • memory allotment (for queued messages)
  • message size (that is stricter than the transport limit)
  • read/write timeouts
  • read/write minimum rates (Slowloris attacks)
  • idle time
  • connection time

Note that there need not be separate URIs for subscribe/publish/register/call limits, because the context already determines which action triggered the limit. For example, if a wamp.error.action_rate_limit_exceeded error is returned for a SUBSCRIBE request, then we know from the context that it's the subscribe_rate_limit that has been exceeded. On the other hand, having the action name be part of the ERROR message could simplify error logging.

Scope of Limits

The scope of these limits can be (as suggested by oberstet):

  • session-wide
  • user-wide (authid)
  • role-wide (authrole)
  • realm-wide

I would also add:

  • router-wide
  • client-IP-wide
  • per-API (in combination with one of the above)

Resource Exhaustion

Resources that can become exhausted on the server include:

  • CPU
  • memory
  • bandwidth
  • sockets
  • file descriptors
  • heat dissipation (as suggested by oberstet)

The feedback loop for avoiding resource exhaustion does not need to directly involve the client. Instead, the server limits (e.g. request rate) could be dynamically adjusted based on current resource usage.

Instead of rate-limiting clients to mitigate resource exhaustion, they could also be dropped (load shedding). A load balancer could take care of forwarding a dropped client to a less busy server when it attempts to reconnect. Unless I'm mistaken, there is currently no ABORT error URI for this purpose.

Client Reactions

When considering error URIs that are returned to the client, the client must make a decision on how to deal with the error. It can:

  • Retry immediately
  • Back off (i.e. wait) and then retry, with the wait time either fixed or growing with each consecutive failure. Retrying can fail after a number of attempts.
  • Adjust the rate of the action that failed (graceful degradation)
  • Use an alternate action that is less resource intensive (graceful degradation)
  • Give up the operation entirely and report the failure to the layer above
  • Disconnect and bail out, possibly reconnecting to a less busy server via a load balancer

The prescribed reaction to a particular error can either be mandated by the application or micro-service, or be decided independently by the client. Some services could be safe to degrade, while others are not (prioritization).

The reaction could also be performed by the server by dropping the client (e.g. DoS detection), in which case the error URI could be returned via a server-initiated ABORT message just before disconnection.

Client Overload

When considering how a client can become overloaded, here are the use cases I can think of:

  • excessive event rate or bandwidth
  • excessive invocation rate or bandwidth
  • event payload too large
  • call result payload too large
  • resource exhaustion: CPU, memory, heat dissipation

There are no EVENT acknowledgements, so there is no immediate way to notify the server of overload unless it's via another channel. There is also no notion QoS message delivery in WAMP. The client is responsible for graceful degradation in this case, and this could be achieved by dropping events or via an API that's associated somehow with the publisher.

For excessive invocations, the client can easily return an ERROR that is forwarded back to the caller via the router. For progressive invocations (e.g. streaming), the graceful degradation could be done via progressive results understood by the caller.

Note that only the raw socket transport establishes payload limits. There are no such limits negotiated via Websocket. There is already the wamp.error.payload_size_exceeded error URI that can be leveraged to communicate rejection of a message due to payload length. There is also the Websocket 1009 Message Too Big close code.

@ecorm
Copy link
Contributor Author

ecorm commented Nov 24, 2023

we definitely should use a consistent wording ... and "request" doesn't IMO ...

Why not "request"? It is a message originated by the client for which a response is expected (request-response). Those familiar with HTTP/REST would immediately understand its meaning. Instead of GET, POST, etc, the verbs in WAMP are SUBSCRIBE, CALL, etc. I'm open to other suggestions, but I can't think of a better term myself.

Here's another argument in favor of the term "request": it is already used extensively in the spec as the Request|id field in precisely those message types that I consider a "request". "Request" here means that a response is expected, and the Request|id is how responses are correlated to requests.

@KSDaemon
Copy link
Collaborator

First of all: @ecorm huge research written above! ^^^

Since I didn't have time to respond earlier, there are going to be a lot of different comments from my side now.

In general, I believe that the WAMP protocol specification (which is an application-level protocol) should not describe specific algorithms and implementations of DDoS protection or traffic shaper mechanisms, and certainly not go down to lower transport layers (such as TCP/IP), but should only provide building blocks for such capabilities (by building blocks I mean error URIs, error payload descriptions and so on). Only such an option will allow on the one hand to have a concise and understandable specification of the protocol and provide opportunities for freedom of implementation on the other hand.

Let's take the HTTP specification for comparison: it simply describes the response codes and their brief purpose (perhaps a bit shorter than it should be, but let's not talk about that). So the protocol itself provides the code 429 Too Many Requests but does not describe how the server should be implemented and under what circumstances such a response should be returned. And this allows different web and application servers to implement their own DDoS protection algorithms or traffic-shaper mechanisms. And that's the right thing to do. For a client making a request, it doesn't matter how the traffic shaper is organized on the server. Only the fact that the request cannot be executed is important. I see no reason why we shouldn't take the same approach.

Regarding error URIs and their number: on the one hand I understand the perfectionism and granularity on the part of @oberstet to have a separate URI for each specific type of error. But based on my personal experience I'm sure that few people will implement different variants of handling each error, rather it will be an inconvenience for the client - because in most cases the client doesn't care why his request was rejected (because of exceeding the number of calls from one IP or limiting the number of publishing events at a certain time of day). In the best case, the client will just try to repeat the request after some time (well if not immediately). And it seems to me that from a practical point of view, it is better to leave fewer different error URIs, hiding the details in the payload. This will simplify error handling in most cases without losing details for those rare cases when it will be needed. At least in none of my projects over the last 20 years have I had the need to implement such granular error handling (and sometimes there was simply no time for this). And here I like the @ecorm idea of having a single error URI with two payload arguments. I prefer to have one dictionary argument instead of a few positional arguments as a more self-expressive option, but that's the details.

Regarding error URI, I still prefer wamp.error.too_many_requests over others, even over wamp.error.limit_exceeded for a few reasons:

  • From its name it is clear even without details that I (as a client) am making too many requests.
  • limit_exceeded - immediately raises the question: what is the limit? Is it network? Is it size? What is it?
  • It might look more natural to people who already are familiar with the HTTP World. And that is good from the protocol adoption view.

Regarding the term for request - I like two variants:

  • Request. As @ecorm said - it is already been used across the spec.
  • Action.

Yeah, we should clarify and agree on what we mean by that. And by request or action in this context I mean:

Any WAMP message sent to the WAMP Router by the Peer.

So I don't see any reason why the same wamp.error.too_many_requests URI may not be returned in the ABORT message if the client is really just sending HELLO messages (e.g. trying to guess the password). And this includes almost all WAMP messages: PUBLISH, SUBSCRIBE, UNSUBSCRIBE, CALL, REGISTER, UNREGISTER, YIELD (this one is interesting but in general no difference).

That's my emm... not 50cents, but maybe 1 dollar)

@KSDaemon
Copy link
Collaborator

Oh, and btw, I was also thinking and looking into Nginx traffic shaper implementation as a reference :)

@ecorm
Copy link
Contributor Author

ecorm commented Nov 24, 2023

Regarding error URI, I still prefer wamp.error.too_many_requests over others, even over wamp.error.limit_exceeded for a few reasons:

From its name it is clear even without details that I (as a client) am making too many requests.
limit_exceeded - immediately raises the question: what is the limit? Is it network? Is it size? What is it?
It might look more natural to people who already are familiar with the HTTP World. And that is good from the protocol adoption view.

I'm +1 on wamp.error.too_many_requests, but I would like to add to that something like wamp.error.quota_exceeded. HTTP does not have the latter because it is stateless and doesn't need to deal with persistent things like "registrations" or "subscriptions". If wamp.error.quota_exceeded is too vague, we could have:

  • wamp.error.too_many_registrations
  • wamp.error.too_many_subscriptions
  • wamp.error.too_many_invocations (i.e. too many pending RPC invocations)

which nicely matches the theme established by

  • wamp.error.too_many_requests.

The wamp.error.too_many_invocations error could originate from either the router or the callee.

With those 2 or 4 URIs, implementations would be free to use payload arguments to provide supplemental information, like the limit and scope arguments I suggested above.

For excessive authentication attempts, we can either:

  • Use the existing wamp.error.authentication_denied, but it is no different than the previously denied attempts
  • Use wamp.error.too_many_requests, and we treat authentication as a "request"
  • Add a new URI such as wamp.error.authentication_lockout.

@KSDaemon
Copy link
Collaborator

wamp.error.quota_exceeded - for me looks pretty natural :)
I suggest starting with just two: wamp.error.quota_exceeded and wamp.error.too_many_requests and introducing any more granulated URIs later.

Rgd auth flow: well, a router could return the wamp.error.authentication_denied and that's it. But maybe having a wamp.error.too_many_requests there would be nice too....

@ecorm
Copy link
Contributor Author

ecorm commented Nov 25, 2023

Rgd auth flow: well, a router could return the wamp.error.authentication_denied and that's it. But maybe having a wamp.error.too_many_requests there would be nice too....

The idea is for a client app to know when to display "Too many login attempts" instead of "Incorrect username and/or password". If wamp.error.authentication_denied is used for both, then custom use of payload arguments (or custom options) is needed to distinguish the two.

@KSDaemon
Copy link
Collaborator

Yeah! Sure! I got exactly the same idea!

@ecorm
Copy link
Contributor Author

ecorm commented Nov 26, 2023

The idea is for a client app to know when to display "Too many login attempts" instead of "Incorrect username and/or password". If wamp.error.authentication_denied is used for both, then custom use of payload arguments (or custom options) is needed to distinguish the two.

To be clear, I prefer the error URIs to be distinct for those two use cases.

@oberstet
Copy link
Member

lots of discussion;) fwiw, just a quick note, I'm going to comment etc within the week, currently busy with other stuff

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants