Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSR-356 WebSockets with Tomcat - How to limit connections within single IP address?

I made a JSR-356 @ServerEndpoint in which I want to limit alive connections from single IP address, to prevent simple DDOS attacks.

Note that I'm search for Java solution (JSR-356, Tomcat or Servlet 3.0 specs).

I have tried custom endpoint configurer but I don't have access to IP address even in HandshakeRequest object.

How to limit JSR-356 connection count from single IP address without external software like iptables?

like image 689
Piotr Müller Avatar asked Apr 05 '14 11:04

Piotr Müller


People also ask

How many WebSockets can Tomcat handle?

The default installation of Tomcat sets the maximum number of HTTP servicing threads at 200. Effectively, this means that the system can handle a maximum of 200 simultaneous HTTP requests.

Does Tomcat support WebSockets?

Tomcat implements the Java WebSocket 1.1 API defined by JSR-356. There are several example applications that demonstrate how the WebSocket API can be used. You will need to look at both the client side HTML and the server side code.

What is jsr356?

JSR 356, Java API for WebSocket, specifies the API that Java developers can use when they want to integrate WebSockets into their applications—both on the server side as well as on the Java client side.


2 Answers

According to Tomcat developer @mark-thomas client IP is not exposed via JSR-356 thus it is impossible to implement such a function with pure JSR-356 API-s.

You have to use a rather ugly hack to work around the limitation of the standard.

What needs to be done boils down to:

  1. Generate each user a token that contains their IP on initial request (before websocket handshake)
  2. Pass the token down the chain until it reaches endpoint implementation

There are at least two hacky options to achieve that.

Use HttpSession

  1. Listen to incoming HTTP requests with a ServletRequestListener
  2. Call request.getSession() on incoming request to ensure it has a session and store client IP as a session attribute.
  3. Create a ServerEndpointConfig.Configurator that lifts client IP from HandshakeRequest#getHttpSession and attaches it to EndpointConfig as a user property using the modifyHandshake method.
  4. Get the client IP from EndpointConfig user properties, store it in map or whatever and trigger cleanup logic if the number of sessions per IP exceeds a threshold.

You can also use a @WebFilter instead of ServletRequestListener

Note that this option can have a high resource consumption unless your application already uses sessions e.g. for authentication purposes.

Pass IP as an encrypted token in the URL

  1. Create a servlet or a filter that attaches to a non websocket entry point. e.g. /mychat
  2. Get client IP, encrypt it with a random salt and a secret key to generate a token.
  3. Use ServletRequest#getRequestDispatcher to forward the request to /mychat/TOKEN
  4. Configure your endpoint to use path parameters e.g. @ServerEndpoint("/mychat/{token}")
  5. Lift the token from @PathParam and decrypt to get client IP. Store it in map or whatever and trigger cleanup logic if the number of sessions per IP exceeds a threshold.

For ease of installation you may wish to generate encryption keys on application startup.

Please note that you need to encrypt the IP even if you are doing an internal dispatch that is not visible to the client. There is nothing that would stop an attacker from connecting to /mychat/2.3.4.5 directly thus spoofing the client IP if it's not encrypted.

See also:

  • apache tomcat 8 websocket origin and client address
  • Find number of active sessions created from a given client IP
  • Accessing HttpSession from HttpServletRequest in a Web Socket @ServerEndpoint
  • https://tyrus.java.net/documentation/1.4/index/websocket-api.html
  • http://docs.oracle.com/javaee/7/tutorial/doc/websocket010.htm#BABJAIGH
like image 75
anttix Avatar answered Oct 09 '22 15:10

anttix


the socket object is hidden in WsSession, so you can use reflection to got the ip address. the execution time of this method is about 1ms. this solution is not prefect but useful.

public static InetSocketAddress getRemoteAddress(WsSession session) {
    if(session == null){
        return null;
    }

    Async async = session.getAsyncRemote();
    InetSocketAddress addr = (InetSocketAddress) getFieldInstance(async, 
            "base#sos#socketWrapper#socket#sc#remoteAddress");

    return addr;
}

private static Object getFieldInstance(Object obj, String fieldPath) {
    String fields[] = fieldPath.split("#");
    for(String field : fields) {
        obj = getField(obj, obj.getClass(), field);
        if(obj == null) {
            return null;
        }
    }

    return obj;
}

private static Object getField(Object obj, Class<?> clazz, String fieldName) {
    for(;clazz != Object.class; clazz = clazz.getSuperclass()) {
        try {
            Field field;
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field.get(obj);
        } catch (Exception e) {
        }            
    }

    return null;
}

and the pom config is

<dependency>
  <groupId>javax.websocket</groupId>
  <artifactId>javax.websocket-all</artifactId>
  <version>1.1</version>
  <type>pom</type>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.apache.tomcat</groupId>
  <artifactId>tomcat-websocket</artifactId>
  <version>8.0.26</version>
  <scope>provided</scope>
</dependency>
like image 37
eric rao Avatar answered Oct 09 '22 15:10

eric rao