views:

474

answers:

2

I'm considering developing an app for Google App Engine, which should not get too much traffic. I'd really rather not pay to exceed the free quotas. However, it seems like it would be quite easy to cause a denial of service attack by overloading the app and exceeding the quotas. Are there any methods to prevent or make it harder to exceed the free quotas? I know I could, for example, limit the number of requests from an IP (making it harder to exceed the CPU quota), but is there any way to make it harder to exceed the requests or bandwidth quotas?

+5  A: 

I'm not sure if it's possible, but the App Engine FAQs indicate that if you can show it's a DOS attack then they'll refund any fees associated with the attack.

fiXedd
Thanks... if I were to pay for it, that would make me feel much better about this issue.
Zifre
Unless you enable billing, exceeding the free quotas will simply take your site offline for a short period (much less than a whole day). It'll only bill you if you've explicitly enabled that, and you can set your own billing cap.
Nick Johnson
+10  A: 

There are no built-in tools to prevent DoS. If you are writing Google Apps using java then you can use the service.FloodFilter filter. The following piece of code will execute before any of your Servlets do.

package service;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;


/**
 * 
 * This filter can protect web server from simple DoS attacks
 * via request flooding.
 * 
 * It can limit a number of simultaneously processing requests
 * from one ip and requests to one page.
 *
 * To use filter add this lines to your web.xml file in a <web-app> section.
 * 
    <filter>
     <filter-name>FloodFilter</filter-name>
        <filter-class>service.FloodFilter</filter-class>
        <init-param>
         <param-name>maxPageRequests</param-name>
         <param-value>50</param-value>
        </init-param>
        <init-param>
         <param-name>maxClientRequests</param-name>
         <param-value>5</param-value>
        </init-param>
        <init-param>
         <param-name>busyPage</param-name>
         <param-value>/busy.html</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>JSP flood filter</filter-name>
        <url-pattern>*.jsp</url-pattern>
    </filter-mapping>
 *  
 *  PARAMETERS
 *  
 *  maxPageRequests:    limits simultaneous requests to every page
 *  maxClientRequests:  limits simultaneous requests from one client (ip)
 *  busyPage:     busy page to send to client if the limit is exceeded
 *          this page MUST NOT be intercepted by this filter
 *  
 */
public class FloodFilter implements Filter
{
    private Map <String, Integer> pageRequests;
    private Map <String, Integer> clientRequests;

    private ServletContext context;
    private int maxPageRequests = 50;
    private int maxClientRequests = 10;
    private String busyPage = "/busy.html";


    public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException
    {
     String page = null;
     String ip = null;

     try {
      if ( request instanceof HttpServletRequest ) {
       // obtaining client ip and page URI without parameters & jsessionid
       HttpServletRequest req = (HttpServletRequest) request;
       page = req.getRequestURI();

       if ( page.indexOf( ';' ) >= 0 )
        page = page.substring( 0, page.indexOf( ';' ) );

       ip = req.getRemoteAddr();

       // trying & registering request
       if ( !tryRequest( page, ip ) ) {
        // too many requests in process (from one client or for this page) 
        context.log( "Flood denied from "+ip+" on page "+page );
        page = null;
        // forwarding to busy page
        context.getRequestDispatcher( busyPage ).forward( request, response );
        return;
       }
      }

      // requesting next filter or servlet
      chain.doFilter( request, response );
     } finally {
      if ( page != null )
       // unregistering the request
       releaseRequest( page, ip );
     }
    }


    private synchronized boolean tryRequest( String page, String ip )
    {
     // checking page requests
     Integer pNum = pageRequests.get( page );

     if ( pNum == null )
      pNum = 1;
     else {
      if ( pNum > maxPageRequests )
       return false;

      pNum = pNum + 1;
     }

     // checking client requests
     Integer cNum = clientRequests.get( ip );

     if ( cNum == null )
      cNum = 1;
     else {
      if ( cNum > maxClientRequests )
       return false;

      cNum = cNum + 1;
     }

     pageRequests.put( page, pNum );
     clientRequests.put( ip, cNum );

     return true;
    }


    private synchronized void releaseRequest( String page, String ip )
    {
     // removing page request
     Integer pNum = pageRequests.get( page );

     if ( pNum == null ) return;

     if ( pNum <= 1 )
      pageRequests.remove( page );
     else
      pageRequests.put( page, pNum-1 );

     // removing client request
     Integer cNum = clientRequests.get( ip );

     if ( cNum == null ) return;

     if ( cNum <= 1 )
      clientRequests.remove( ip );
     else
      clientRequests.put( ip, cNum-1 );
    }


    public synchronized void init( FilterConfig config ) throws ServletException
    {
     // configuring filter
     this.context = config.getServletContext();
     pageRequests = new HashMap <String,Integer> ();
     clientRequests = new HashMap <String,Integer> ();
     String s = config.getInitParameter( "maxPageRequests" );

     if ( s != null ) 
      maxPageRequests = Integer.parseInt( s );

     s = config.getInitParameter( "maxClientRequests" );

     if ( s != null ) 
      maxClientRequests = Integer.parseInt( s );

     s = config.getInitParameter( "busyPage" );

     if ( s != null ) 
      busyPage = s;
    }


    public synchronized void destroy()
    {
     pageRequests.clear();
     clientRequests.clear();
    }
}

If you are using python, then you may have to roll your own filter.

Achille
I'm probably going to use Java for the extra speed, so this might help.
Zifre