diff --git a/authserv/app_common.py b/authserv/app_common.py
index 58677d2d813e9f3a3bffb4adc0cab1f5cec89b4f..7e316c43828afe5400f720c7eece16f98fda7572 100644
--- a/authserv/app_common.py
+++ b/authserv/app_common.py
@@ -16,7 +16,7 @@ def check_ratelimit(request, username, source_ip):
                 period=current_app.config.get('RATELIMIT_USER_PERIOD', 60)):
             abort(503)
         if (source_ip
-            and not whitelisted(source_ip, current_app.config.get('SOURCE_IP_WHITELIST'))
+            and not whitelisted(source_ip)
             and not ratelimit_http_request(
                 request, source_ip, tag='ip',
                 count=current_app.config.get('RATELIMIT_SOURCEIP_COUNT', 10),
@@ -66,8 +66,8 @@ def _do_auth(mc, username, service, shard, password, otp_token, source_ip,
     if current_app.config.get('ENABLE_BLACKLIST'):
         if bl.is_blacklisted('u', username):
             return _user_blacklisted
-        if (source_ip
-            and not whitelisted(source_ip, current_app.config.get('SOURCE_IP_WHITELIST'))
+        is_whitelisted = whitelisted(source_ip)
+        if (source_ip and not is_whitelisted
             and bl.is_blacklisted('ip', source_ip)):
             return _ip_blacklisted
 
@@ -94,8 +94,7 @@ def _do_auth(mc, username, service, shard, password, otp_token, source_ip,
         if user:
             if bl.auth_failure('u', username):
                 current_app.logger.info('blacklisted user %s', username)
-        if (source_ip
-            and not whitelisted(source_ip, current_app.config.get('SOURCE_IP_WHITELIST'))):
+        if source_ip and not is_whitelisted:
             if bl.auth_failure('ip', source_ip):
                 current_app.logger.info('blacklisted IP %s', source_ip)
 
diff --git a/authserv/ratelimit.py b/authserv/ratelimit.py
index d549ccacf4482ab33402343909d972f9d1a14d46..267706bf5878d439a1e143202a7774690e459a11 100644
--- a/authserv/ratelimit.py
+++ b/authserv/ratelimit.py
@@ -1,4 +1,4 @@
-import functools
+import ipaddr
 import re
 import threading
 from flask import abort, request, current_app
@@ -10,36 +10,37 @@ key_sep = '/'
 DEFAULT_WHITELIST = [
     '127.0.0.1',
     '::1',
-    '172.16.1.*',
+    '172.16.1.0/24',
 ]
 
 
-def _tostr(s):
-    if isinstance(s, unicode):
-        return s.encode('utf-8')
-    return s
+def init_whitelist(app):
+    wl = app.config.get('SOURCE_IP_WHITELIST', DEFAULT_WHITELIST)
+    app.whitelist = [ipaddr.IPNetwork(x) for x in wl]
 
 
-_rxcache = {}
-_rxcache_lock = threading.Lock()
+_fake_v6_re = re.compile(r'^::\d+\.\d+\.\d+\.\d+$')
 
-
-def _rxcompile(s):
-    with _rxcache_lock:
-        if s not in _rxcache:
-            _rxcache[s] = re.compile('^%s$' % s.replace('.', r'\.').replace('*', '.*'))
-        return _rxcache[s]
-
-
-def whitelisted(value, wl):
-    if not wl:
-        wl = DEFAULT_WHITELIST
-    for rx in wl:
-        if _rxcompile(rx).search(value):
+def whitelisted(s):
+    if not s:
+        return False
+    # Some endpoints like to pass IPv4 addresses as ::addr, which
+    # isn't really entirely correct and confuses ipaddr.
+    if _fake_v6_re.match(s):
+        s = s[2:]
+    ip = ipaddr.IPAddress(s)
+    for net in current_app.whitelist:
+        if (ip in net) or (hasattr(ip, 'ipv4_mapped') and ip.ipv4_mapped and ip.ipv4_mapped in net):
             return True
     return False
 
 
+def _tostr(s):
+    if isinstance(s, unicode):
+        return s.encode('utf-8')
+    return s
+
+
 class RateLimit(object):
     """Keep track of request rates using Memcache."""
 
@@ -95,6 +96,7 @@ class BlackList(object):
         return result is None
 
     def incr(self, mc, key):
+        # Returns True if the key was added to the blacklist.
         key = _tostr(self.prefix + key)
         if not self.rl.check(mc, key):
             mc.set(key, 'true', time=self.ttl)
@@ -111,11 +113,13 @@ class AuthBlackList(object):
     def is_blacklisted(self, tag, value):
         if not value:
             return False
-        key = key_sep.join([tag, value])
+        key = key_sep.join((tag, value))
         return not self.blacklist.check(self.mc, key)
 
     def auth_failure(self, tag, value):
+        # Returns True if the key was added to the blacklist.
         if not value:
-            return
-        key = key_sep.join([tag, value])
+            return False
+        key = key_sep.join((tag, value))
         return self.blacklist.incr(self.mc, key)
+
diff --git a/authserv/server.py b/authserv/server.py
index 7a18c8618ad556d174dc0a7ff2a6bd90a2274a2b..d7955b968a298bdb864f0e4525eea3c79ef0a272 100644
--- a/authserv/server.py
+++ b/authserv/server.py
@@ -12,10 +12,13 @@ import signal
 import sys
 from authserv import app_main
 from authserv import app_nginx
+from authserv import ratelimit
 
 
-def create_app(app, userdb=None, mc=None):
+def create_app(app, userdb=None, mc=None, extra_config=None):
     app.config.from_envvar('APP_CONFIG', silent=True)
+    if extra_config:
+        app.config.update(extra_config)
 
     if not userdb:
         from authserv import ldap_model
@@ -33,6 +36,8 @@ def create_app(app, userdb=None, mc=None):
         mc = pylibmc.ThreadMappedPool(client)
     app.memcache = mc
 
+    ratelimit.init_whitelist(app)
+
     return app
 
 
diff --git a/authserv/test/test_app_main.py b/authserv/test/test_app_main.py
index bb746259b145e99c5f243fb406a73f2676abfe61..4877502b544e2040594b4444f2e86bc20ae24e7c 100644
--- a/authserv/test/test_app_main.py
+++ b/authserv/test/test_app_main.py
@@ -21,13 +21,15 @@ class ServerTest(unittest.TestCase):
             }
         app = server.create_app(app_main.app,
                                 userdb=FakeUserDb(self.users),
-                                mc=FakeMemcachePool(_time))
-        app.config.update({
-                'TESTING': True,
-                'DEBUG': True,
-                'ENABLE_BLACKLIST': True,
-                'ENABLE_RATELIMIT': True,
-                })
+                                mc=FakeMemcachePool(_time),
+                                extra_config={
+                                    'TESTING': True,
+                                    'DEBUG': True,
+                                    'ENABLE_BLACKLIST': True,
+                                    'ENABLE_RATELIMIT': True,
+                                    'BLACKLIST_COUNT': 5,
+                                    'BLACKLIST_PERIOD': 10,
+                                })
         self.app = app.test_client()
 
     def test_auth_simple_ok(self):
diff --git a/authserv/test/test_app_nginx.py b/authserv/test/test_app_nginx.py
index 1021f051a6f9182a0f80a6f852f5e36b2ef8c1da..20ccb25e5b528c3a581222bafe15d1e5db3eac9b 100644
--- a/authserv/test/test_app_nginx.py
+++ b/authserv/test/test_app_nginx.py
@@ -17,11 +17,11 @@ class ServerTest(unittest.TestCase):
             }
         app = server.create_app(app_nginx.app,
                                 userdb=FakeUserDb(self.users),
-                                mc=FakeMemcachePool(_time))
-        app.config.update({
-                'TESTING': True,
-                'DEBUG': True,
-                })
+                                mc=FakeMemcachePool(_time),
+                                extra_config={
+                                    'TESTING': True,
+                                    'DEBUG': True,
+                                })
         self.app = app.test_client()
 
     def test_nginx_http_auth_ok(self):
diff --git a/authserv/test/test_integration.py b/authserv/test/test_integration.py
index 03203341d5c3e325afdb67579d90184f16af0c1e..4047a7cf6c1521bdac5c1a1bf38cfc67fcd97341 100644
--- a/authserv/test/test_integration.py
+++ b/authserv/test/test_integration.py
@@ -63,11 +63,11 @@ class SSLServerTest(unittest.TestCase):
         def _runserver():
             app = server.create_app(app_main.app,
                                     userdb=FakeUserDb(cls.users),
-                                    mc=FakeMemcachePool(time.time))
-            app.config.update({
-                'TESTING': True,
-                'DEBUG': True,
-                })
+                                    mc=FakeMemcachePool(time.time),
+                                    extra_config={
+                                        'TESTING': True,
+                                        'DEBUG': True,
+                                    })
 
             print >>sys.stderr, 'starting HTTP server on port', cls.port
             server.run(
diff --git a/authserv/test/test_ratelimit.py b/authserv/test/test_ratelimit.py
index 78536369dac73c08a8092389119ee849f4e23d68..780d6b6668ad5d49a1c73ea9a4097fc4c37f70ac 100644
--- a/authserv/test/test_ratelimit.py
+++ b/authserv/test/test_ratelimit.py
@@ -1,5 +1,7 @@
 from authserv.test import *
 from authserv.ratelimit import *
+from authserv import server
+from authserv import app_main
 
 
 class RateLimitTest(unittest.TestCase):
@@ -55,9 +57,32 @@ class BlackListTest(unittest.TestCase):
 
 class WhitelistTest(unittest.TestCase):
 
-    def test_default_whitelist(self):
-        self.assertTrue(whitelisted('127.0.0.1', None))
-        self.assertTrue(whitelisted('172.16.1.5', None))
-        self.assertTrue(whitelisted('::1', None))
+    def setUp(self):
+        app = server.create_app(app_main.app,
+                                userdb=FakeUserDb({}),
+                                mc=FakeMemcachePool(),
+                                extra_config={
+                                    'TESTING': True,
+                                    'DEBUG': True,
+                                    'SOURCE_IP_WHITELIST': [
+                                        '127.0.0.1',
+                                        '::1',
+                                        '172.16.1.0/24',
+                                    ],
+                                })
+        #self.app = app.test_client()
+        self.app = app
+
+    def test_whitelist(self):
+        with self.app.app_context():
+            self.assertTrue(whitelisted('127.0.0.1'))
+            self.assertTrue(whitelisted('::127.0.0.1'))
+            self.assertTrue(whitelisted('::ffff:127.0.0.1'))
+            self.assertTrue(whitelisted('::1'))
+            self.assertTrue(whitelisted('172.16.1.5'))
+            self.assertTrue(whitelisted('::172.16.1.5'))
+            self.assertTrue(whitelisted('::ffff:172.16.1.5'))
 
-        self.assertFalse(whitelisted('1.2.3.4', None))
+            self.assertFalse(whitelisted('1.2.3.4'))
+            self.assertFalse(whitelisted('::1.2.3.4'))
+            self.assertFalse(whitelisted('2001:abcd:ef01::14'))
diff --git a/debian/control b/debian/control
index 20e223e42c02b3826487072d3b8a4e52af471e1a..6e76b9b28971a49311f55ed5d26ace7b37634a78 100644
--- a/debian/control
+++ b/debian/control
@@ -16,6 +16,6 @@ Description: PAM module for the A/I authentication protocol.
 Package: ai-auth-server
 Architecture: all
 Depends: ${python:Depends}, ${misc:Depends}, python-gevent, python-pylibmc,
- memcached
+ python-ipaddr, memcached
 Description: Auth server package.
  Centralized authentication server with OTP support.
diff --git a/setup.py b/setup.py
index 5671e434a9713819cb2265b8ffbf58a8825601eb..e6e7fd87ba4fdebd4f6bedac63cab5a5f6178778 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@ setup(
     author="Autistici/Inventati",
     author_email="info@autistici.org",
     url="https://git.autistici.org/ai/authserv",
-    install_requires=["gevent", "python-ldap", "Flask", "pylibmc"],
+    install_requires=["gevent", "python-ldap", "Flask", "pylibmc", "ipaddr"],
     setup_requires=[],
     zip_safe=False,
     packages=find_packages(),