• How to enable geographic locking (ModuleGeoIPLock)

    GeoIP AddOn enables IP addresses to be checked against IP geolocation (GeoIP) databases so that you can lock RTMP, HTTP, and RTSP connections to Wowza Media Server from IP addresses in specified countries. The AddOn works with downloadable GeoIP country databases from MaxMind, Inc. The GeoIP Country database is available for one-time purchase or paid subscription. A less accurate version of the GeoIP Country database (the GeoLite Country database) is available free-of-charge. Developers can download an open-source LGPL-licensed GeoIP Java library and integrate it with GeoIP AddOn for custom implementations.

    Note: Wowza Media Server™ 3.5.2 or later is required.

    Configuration


    The AddOn package includes a server listener and an application module. You must configure both for the IP geolocation functionality to work correctly.

    Download the GeoIP AddOn package: GeoIP.zip

    Server listener configuration

    The following server listener code loads a MaxMind IP geolocation country database and makes it available to multiple Wowza Media Server applications.
    package com.wowza.wms.plugin.geoip;
    
    import java.io.File;
    
    import com.maxmind.geoip.LookupService;
    import com.wowza.wms.bootstrap.Bootstrap;
    import com.wowza.wms.logging.WMSLoggerFactory;
    import com.wowza.wms.server.*;
    
    
    public class ServerListenerGeographicSelector implements IServerNotify2
    {
    
    	// internal class
    	public class GeographicReloader extends Thread 
    	{
    		private boolean running = true;
    		private boolean quit = false;
    		private int sleepTime = 10000;
    		private boolean geoReloadFind  = false;
    		private long lastModifiedLoad = 0L;
    		private IServer server = null;
    		
    		public GeographicReloader(IServer server)
    		{
    			this.server = server;
    		}
    
    		public synchronized boolean running()
    		{
    			return this.running;
    		}
    		
    		public synchronized void quit()
    		{
    			this.quit = true;
    			notify();
    		}
    		
    		public void run()
    		{
    			while (true)
    			{
    				String databaseLocation = server.getProperties().getPropertyStr("geoipDatabaseLocation", Bootstrap.getServerHome(Bootstrap.CONFIGHOME)+File.separatorChar+"conf"+File.separatorChar+"GeoIP.dat");
    
    				File geoIPReload = new File(databaseLocation);		
    				
    				long lastModified = geoIPReload.lastModified();
    				lastModified = lastModified/1000;
    				long epoch = System.currentTimeMillis()/1000;
    				
    				long calc = epoch - ( (this.sleepTime /1000)*6 );
    
    				if (this.geoReloadFind == true && lastModified == this.lastModifiedLoad)
    				{
    					ServerListenerGeographicSelector.reloadGeoDatabase(this.server);
    				}
    				
    				if (this.geoReloadFind == true)
    				{
    					this.geoReloadFind = false;
    				}
    				
    				if (lastModified > calc) 
    				{
    					this.lastModifiedLoad = lastModified;
    					this.geoReloadFind = true;
    				}
    				
    				synchronized(this)
    				{
    					try
    					{
    						Thread.currentThread().wait(this.sleepTime);
    					}
    					catch (Exception e)
    					{
    						//Log.error("WorkerClass.run: wait: "+e.toString());
    					}
    				}
    
    				synchronized(this)
    				{
    					if (this.quit)
    					{
    						this.running = false;
    						break;
    					}
    				}
    			}
    		}
    	}
    
    	
    	
    	private static boolean geoIPLoaded = false;
    	private static LookupService maxMindGeoIPObject = null;
    	private static GeographicReloader serverMonitor = null;
    
    	public void onServerCreate(IServer server) 
    	{
    	}
    
    	public void onServerInit(IServer server) 
    	{
    		reloadGeoDatabase(server);
    		serverMonitor = new GeographicReloader(server);
    		serverMonitor.setDaemon(true);
    		serverMonitor.start();
    	}
    
    	public void onServerShutdownComplete(IServer server) 
    	{
    	}
    
    	public void onServerShutdownStart(IServer server) 
    	{
    		if (serverMonitor!=null)
    		{
    			serverMonitor.quit();
    		}
    	}
    
    	public void onServerConfigLoaded(IServer server) 
    	{
    	}
    	
    	private static boolean loadGeoIPDatabase(IServer server)
    	{
    		boolean Loaded = false;
    		String databaseLocation = server.getProperties().getPropertyStr("geoipDatabaseLocation", Bootstrap.getServerHome(Bootstrap.CONFIGHOME)+File.separatorChar+"conf"+File.separatorChar+"GeoIP.dat");
    
    		try 
    		{
    			maxMindGeoIPObject = new LookupService(databaseLocation, LookupService.GEOIP_MEMORY_CACHE);
    			Loaded = true;
    			server.getProperties().setProperty("geoipLocationLoaded", databaseLocation);
    			WMSLoggerFactory.getLogger(null).info("ServerListenerGeographicSelector.loadGeoIPDatabase: Loaded requested database from "+databaseLocation);
    		} 
    		catch (Exception e) 
    		{ 
    			WMSLoggerFactory.getLogger(null).warn("ServerListenerGeographicSelector.loadGeoIPDatabase: Could not load requested database; reason "+e.toString()+" from location "+databaseLocation);
    			Loaded = false; 
    		}
    		return Loaded;
    	}
    	
    	public static LookupService getGeographicObject()
    	{
    		return maxMindGeoIPObject;
    	}
    	
    	public static boolean getGeographicReady()
    	{
    		return geoIPLoaded;
    	}
    	
    	public static boolean reloadGeoDatabase(IServer server)
    	{
    		if (maxMindGeoIPObject != null && geoIPLoaded == true)
    		{
    			maxMindGeoIPObject.close();
    			maxMindGeoIPObject = null;
    			geoIPLoaded = false;
    		}
    		geoIPLoaded = loadGeoIPDatabase(server);
    		return geoIPLoaded;
    	}
    }
    To enable the server listener, open the [install-dir]/conf/Server.xml file in a text editor and add the following <ServerListener> to the <ServerListeners> container in the file:
    <ServerListener>
         <BaseClass>com.wowza.wms.plugin.geoip.ServerListenerGeographicSelector</BaseClass>
    </ServerListener>
    The server listener loads a MaxMind IP geolocation database file named GeoIP.dat. By default, this file is located in [install-dir]/conf on the Wowza Media Server. If you store the GeoIP.dat in a different location on the server, you must specify the non-default location in the server configuration. Open the [install-dir]/conf/Server.xml file in a text editor and add the following <Property> to the <Properties> container at the end of the file:
    <Property>
         <Name>geoipDatabaseLocation</Name>
         <Value>/usr/local/WowzaMediaServer/lib/</Value>
    </Property>
    After you start Wowza Media Server, you'll see the following message that indicates if the GeoIP.dat file was loaded successfully:
    ServerListenerGeographicSelector.loadGeoIPDatabase: Loaded requested database from: <location of GeoIP.dat>
    Or the following message if the file couldn't be loaded:
    ServerListenerGeographicSelector.loadGeoIPDatabase: Could not load requested database; reason <reason> from location <location checked>

    Application Module

    The application module uses the server listener to access the MaxMind IP geolocation database. It enables multiple applications to access the database without the need to load it multiple times.
    package com.wowza.wms.plugin.geoip;
    
    import com.maxmind.geoip.LookupService;
    import com.wowza.wms.amf.AMFDataList;
    import com.wowza.wms.application.IApplicationInstance;
    import com.wowza.wms.client.IClient;
    import com.wowza.wms.httpstreamer.model.IHTTPStreamerSession;
    import com.wowza.wms.module.ModuleBase;
    import com.wowza.wms.request.RequestFunction;
    import com.wowza.wms.rtp.model.RTPSession;
    
    public class ModuleGeographicSelection extends ModuleBase {
    	
    	private LookupService maxMindObject = null;
    	private boolean geographicReady = false;
    	private boolean geoGraphicInclude = true;
    	private boolean debug = false;
    	private boolean blockByDefault = true;
    	private String allowedEncoder[] = {};
    	private String allowedIP[] = {};
    	private String appContext = "";
    	private String countries = "";
    
    	public void onAppStart(IApplicationInstance appInstance) 
    	{
    		this.appContext = appInstance.getApplication().getName() + "/" + appInstance.getName();
    		getLogger().info("ModuleGeographicSelection.onAppStart["+ appContext+ "]");
    
    		this.maxMindObject = ServerListenerGeographicSelector.getGeographicObject();
    		this.geographicReady = ServerListenerGeographicSelector.getGeographicReady();
    		this.allowedEncoder = appInstance.getProperties().getPropertyStr("geoipAllowEncoder", "").toLowerCase().split(",");
    		this.allowedIP = appInstance.getProperties().getPropertyStr("geoipAllowIP", "").toLowerCase().split(",");
    		this.debug = appInstance.getProperties().getPropertyBoolean("geoipDebug", debug);
    		this.geoGraphicInclude = appInstance.getProperties().getPropertyBoolean("geoipCountriesInclude", geoGraphicInclude);
    		this.countries = appInstance.getProperties().getPropertyStr("geoipCountries", countries);
    		this.blockByDefault = appInstance.getProperties().getPropertyBoolean("geoipBlockByDefault", blockByDefault);
    	}
    
    	public void onConnect(IClient client, RequestFunction function,	AMFDataList params) 
    	{
    		getLogger().info("ModuleGeographicSelection.onConnect["+appContext+"]: clientID:" + client.getClientId());
    		
    		String flashver = client.getFlashVer().toLowerCase();
    		if (debug)
    		{
    			getLogger().info("ModuleGeographicSelection.onConnect["+appContext+"]: flashver: " + flashver);
    		}
    		
    		try
    		{ 
    			if (this.allowedEncoder != null)
    			{ 
    				for (int i = 0; i < this.allowedEncoder.length; i++) 
    				{  
    					if (flashver.startsWith(this.allowedEncoder[i].trim()) && this.allowedEncoder[i].length()>0) 
    					{ 
    						if (this.debug)
    						{
    							getLogger().info("ModuleGeographicSelection.onConnect["+appContext+"]: GeoIP Encoder Allowed: "+flashver+" matches "+allowedEncoder[i]);
    						}
    						return;
    					} 
    				} 
    			}
    			if (checkAllowedIP(allowedIP, client.getIp()))
    			{
    				return;
    			}
    
    			if (this.geographicReady)
    			{
    				if (this.countries.contains(this.maxMindObject.getCountry(client.getIp()).getCode()) == this.geoGraphicInclude)
    				{
    					client.acceptConnection();
    					return;
    				}
    			}
    			else
    			{
    				if (this.blockByDefault)
    				{
    					client.rejectConnection("Geographic blocking not available, blocking connection");
    				}
    				else
    				{
    					client.acceptConnection();
    					return;
    				}
    			}
    		} 
    		catch (Exception e) 
    		{
    			getLogger().warn("ModuleGeographicSelection.onConnect["+appContext+"]: Exception: " + e.getMessage()); 
    		}
    
    		client.shutdownClient();
    		return;
    	}
    
    	public void onHTTPSessionCreate(IHTTPStreamerSession httpSession) 
    	{
    		getLogger().info("ModuleGeographicSelection.onHTTPSessionCreate["+appContext+"]: sessionID:" + httpSession.getSessionId());
    
    		try
    		{
    			if (checkAllowedIP(allowedIP, httpSession.getIpAddress()))
    			{
    				return;
    			}
    			
    			if (this.geographicReady)
    			{
    				if (this.countries.contains(this.maxMindObject.getCountry(httpSession.getIpAddress()).getCode()) == this.geoGraphicInclude)
    				{
    					return;
    				}
    			}
    			else
    			{
    				if (!this.blockByDefault)
    				{
    					return;
    				}
    			}
    		}
    		catch (Exception e) 
    		{
    			getLogger().warn("ModuleGeographicSelection.onHTTPSessionCreate["+appContext+"]: Exception: " + e.getMessage()); 
    		}
    		httpSession.rejectSession();
    		httpSession.shutdown();
    	}
    
    	public void onRTPSessionCreate(RTPSession rtpSession) 
    	{
    		getLogger().info("ModuleGeographicSelection.onRTPSessionCreate["+appContext+"]: sessionID:" + rtpSession.getSessionId());
    
    		try
    		{
    			if (checkAllowedIP(allowedIP, rtpSession.getIp()))
    			{
    				return;
    			}
    			
    			if (this.geographicReady)
    			{
    				if (this.countries.contains(this.maxMindObject.getCountry(rtpSession.getIp()).getCode()) == this.geoGraphicInclude)
    				{
    					return;
    				}
    			}
    			else
    			{
    				if (!this.blockByDefault)
    				{
    					return;
    				}
    			}
    		}
    		catch (Exception e) 
    		{
    			getLogger().warn("ModuleGeographicSelection.onRTPSessionCreate["+appContext+"]: Exception: " + e.getMessage()); 
    		}
    		rtpSession.rejectSession();
    		rtpSession.shutdown();
    	}
    	
    	public boolean checkAllowedIP(String parts[], String IP)
    	{		
    		try
    		{ 
    			if (parts != null)
    			{ 
    				for (int i = 0; i < parts.length; i++) 
    				{  
    					if (IP.startsWith(parts[i].trim()) && parts[i].length()>0) 
    					{ 
    						if (this.debug)
    						{
    							getLogger().info("ModuleGeographicSelection.checkAllowedIP["+appContext+"]: GeoIP Encoder Allowed IP: "+IP+" matches "+parts[i]);
    						}
    						return true;
    					} 
    				} 
    			} 
    		} 
    		catch (Exception e) 
    		{
    			getLogger().warn("ModuleGeographicSelection.checkAllowedIP["+appContext+"]:  Exception: " + e.getMessage()); 
    		}
    		return false;
    	}
    }
    To enable an individual application to access the IP geolocation database, open the [install-dir]/conf/[app-name]/Application.xml file in a text editor and add the following module to the <Modules> container in the file:
    <Module>
         <Name>ModuleGeoIPLock</Name>
         <Description>ModuleGeoIPLock</Description>
        <Class>com.wowza.wms.plugin.geoip.ModuleGeographicSelection</Class>
    </Module>
    The ModuleGeoIPLock geographic locking module allows IP addresses from a defined set of countries to connect. You can bypass the geographic lock for specific encoders and IP addresses by adding the following properties to the <Properties> container at the end of the [install-dir]/conf/[app-name]/Application.xml file.

    Encoder whitelisting


    Specifies encoder names that will bypass the geographic lock.
    <Property>
         <Name>geoipAllowEncoder</Name>
         <Value>FM,Wirecast</Value>
    </Property>

    IP whitelisting


    Specifies IP addresses that will bypass the geographic lock.
    <Property>
         <Name>geoipAllowIP</Name>
         <Value>10.1.1.1,10.1.1.201,8.8.8.8,192.168</Value>
    </Property>
    Note: You can specify a partial IP address and any IP address that starts with that range is allowed to connect.

    Geographic country list


    Specifies that IP addresses in certain countries (specified by ISO 3166 code) bypass the geographic lock:
    <Property>
         <Name>geoipCountries</Name>
         <Value>GB,FR,US</Value>
    </Property>

    Geographic country include/exclude


    The geographic locking module allows IP address in countries that are specified by the geoipCountries properties list to connect. To exclude IP addresses in that list of countries from bypassing the geographic lock, add the following property to the [install-dir]/conf/[app-name]/Application.xml file:
    <Property>
         <Name>geoipCountriesInclude</Name>
         <Value>false</Value>
         <Type>Boolean</Type>
    </Property>

    Originally Published: For Wowza Media Server 3.5.2 on 06-10-2013.

    If you're having problems or want to discuss this article, post in our forum.