/* * Copyright 2011 NCHOVY * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.krakenapps.firewall.api.impl; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.felix.ipojo.annotations.Component; import org.apache.felix.ipojo.annotations.Invalidate; import org.apache.felix.ipojo.annotations.Provides; import org.apache.felix.ipojo.annotations.Requires; import org.apache.felix.ipojo.annotations.Validate; import org.krakenapps.firewall.api.DefaultFirewallInstanceManagerListener; import org.krakenapps.firewall.api.FirewallController; import org.krakenapps.firewall.api.FirewallGroup; import org.krakenapps.firewall.api.FirewallGroupListener; import org.krakenapps.firewall.api.FirewallInstance; import org.krakenapps.firewall.api.FirewallInstanceManager; import org.krakenapps.firewall.api.FirewallRule; import org.osgi.framework.BundleContext; import org.osgi.service.prefs.BackingStoreException; import org.osgi.service.prefs.Preferences; import org.osgi.service.prefs.PreferencesService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component(name = "firewall-controller") @Provides(specifications = { FirewallController.class }) public class FirewallControllerEngine extends DefaultFirewallInstanceManagerListener implements FirewallController, FirewallGroupListener { private final Logger logger = LoggerFactory.getLogger(FirewallControllerEngine.class.getName()); private static final String RULES = "rules"; private static final String MEMBERS = "members"; private static final String INSTANCES = "instances"; private FirewallInstanceManagerTracker tracker; private Unblocker unblocker; private Thread unblockerThread; private ConcurrentMap<String, FirewallInstanceManager> instanceManagers; private ConcurrentMap<String, FirewallInstance> instances; private ConcurrentMap<String, FirewallGroup> groups; @Requires private PreferencesService prefsvc; public FirewallControllerEngine(BundleContext bc) { instanceManagers = new ConcurrentHashMap<String, FirewallInstanceManager>(); instances = new ConcurrentHashMap<String, FirewallInstance>(); groups = new ConcurrentHashMap<String, FirewallGroup>(); tracker = new FirewallInstanceManagerTracker(bc, this); unblocker = new Unblocker(); } @Validate public void start() { try { Preferences root = prefsvc.getSystemPreferences(); // initialize root.node(MEMBERS); root.node(RULES); root.flush(); root.sync(); loadGroups(); if (getGroup("all") == null) createGroup("all"); } catch (BackingStoreException e) { logger.error("kraken firewall api: cannot initialize preferences"); } // begin monitor tracking tracker.open(); // begin unblocker unblockerThread = new Thread(unblocker, "Firewall Unblocker"); unblockerThread.start(); } private void loadGroups() { try { Preferences memberRoot = getMemberConfig(); Preferences ruleRoot = getRuleConfig(); for (String groupName : memberRoot.childrenNames()) { FirewallGroup group = new FirewallGroup(this, groupName); Preferences memberGroupNode = memberRoot.node(groupName); Preferences ruleGroupNode = ruleRoot.node(groupName); for (String ip : ruleGroupNode.childrenNames()) { try { Preferences ruleNode = ruleGroupNode.node(ip); InetAddress ipAddress = InetAddress.getByName(ip); Date expire = new Date(ruleNode.getLong("expire", 0)); group.blockSourceIp(ipAddress, expire); if (logger.isDebugEnabled()) logger.debug("kraken firewall api: loading group {}, ip {}, expire {}", new Object[] { groupName, ip, expire }); } catch (UnknownHostException e) { logger.error("kraken firewall api: cannot convert ip {}", ip); } } for (String instanceName : memberGroupNode.childrenNames()) { try { group.join(instanceName); } catch (Exception e) { logger.trace("kraken firewall api: cannot join group " + group.getName() + ", instance " + instanceName, e); } } groups.put(group.getName(), group); // add event listener here to skip join event callback group.addEventListener(this); } } catch (BackingStoreException e) { logger.error("kraken firewall api: cannot load groups", e); } } @Invalidate public void stop() { try { if (unblocker != null && unblockerThread != null) { // stop unblocker unblocker.doStop = true; unblockerThread.interrupt(); unblockerThread.join(5000); } } catch (InterruptedException e) { } tracker.close(); } @Override public Collection<FirewallInstanceManager> getInstanceManagers() { return instanceManagers.values(); } @Override public FirewallInstanceManager getInstanceManager(String name) { if (name == null) return null; return instanceManagers.get(name); } @Override public Collection<FirewallGroup> getGroups() { return groups.values(); } @Override public FirewallGroup getGroup(String groupName) { if (groupName == null) throw new IllegalArgumentException("group name should be not null"); return groups.get(groupName); } @Override public FirewallGroup createGroup(String groupName) { try { Preferences root = getMemberConfig(); if (root.nodeExists(groupName)) throw new IllegalStateException(groupName + " already exists"); root.node(groupName); root.flush(); root.sync(); FirewallGroup group = new FirewallGroup(this, groupName); group.addEventListener(this); groups.put(groupName, group); return group; } catch (BackingStoreException e) { throw new IllegalStateException(e); } } @Override public void removeGroup(String groupName) { try { Preferences memberRoot = getMemberConfig(); if (!memberRoot.nodeExists(groupName)) throw new IllegalStateException(groupName + " not found"); memberRoot.node(groupName).removeNode(); memberRoot.flush(); memberRoot.sync(); Preferences ruleRoot = getRuleConfig(); ruleRoot.node(groupName).removeNode(); ruleRoot.flush(); ruleRoot.sync(); FirewallGroup group = groups.remove(groupName); group.removeEventListener(this); } catch (BackingStoreException e) { throw new IllegalStateException(e); } } public void register(FirewallInstanceManager manager) { logger.trace("kraken firewall api: registering firewall instance manager {}", manager.getName()); // add event hook manager.addEventListener(this); // add to registry FirewallInstanceManager old = instanceManagers.putIfAbsent(manager.getName(), manager); if (old != null) throw new IllegalStateException("instance manager " + manager.getName() + " already exists"); // load all configured instances loadInstancesAutomatically(manager); } private void loadInstancesAutomatically(FirewallInstanceManager manager) { // load configured instances try { Preferences instanceRoot = getInstanceConfig(); for (String instanceName : instanceRoot.childrenNames()) { Preferences instanceNode = instanceRoot.node(instanceName); Preferences configNode = instanceNode.node("config"); Properties config = parseConfig(configNode); String managerName = instanceNode.get("manager", null); if (!managerName.equals(manager.getName())) continue; FirewallInstance instance = instances.get(instanceName); if (instance == null) { logger.trace("kraken firewall api: creating firewall instance {} automatically", instanceName); instance = manager.createInstance(instanceName, config); instances.put(instanceName, instance); } } Preferences memberRoot = getMemberConfig(); for (String groupName : memberRoot.childrenNames()) { Preferences groupNode = memberRoot.node(groupName); for (String instanceName : groupNode.childrenNames()) { FirewallInstance instance = instances.get(instanceName); if (instance == null) continue; FirewallGroup group = groups.get(groupName); if (group != null) { group.onLoad(manager, instance); } } } } catch (BackingStoreException e) { logger.error("kraken firewall api: cannot load firewall instances of manager " + manager.getName(), e); } } private Properties parseConfig(Preferences config) { Properties props = new Properties(); try { for (String key : config.keys()) { String value = config.get(key, null); props.setProperty(key, value); } } catch (BackingStoreException e) { logger.error("kraken firewall api: "); } return props; } public void unregister(FirewallInstanceManager manager) { logger.trace("kraken firewall api: unregistering firewall instance manager {}", manager.getName()); // remove event hook manager.removeEventListener(this); // remove all instances from groups for (FirewallGroup group : getGroups()) { for (FirewallInstance instance : manager.getInstances()) group.onUnload(manager, instance); } // remove from instance registry for (FirewallInstance instance : manager.getInstances()) instances.remove(instance.getName()); // remove from manager registry FirewallInstanceManager old = instanceManagers.get(manager.getName()); if (old == manager) instanceManagers.remove(manager.getName()); } @Override public Collection<FirewallInstance> getInstances() { return new ArrayList<FirewallInstance>(instances.values()); } @Override public FirewallInstance getInstance(String name) { if (name == null) return null; return instances.get(name); } @Override public void register(FirewallInstance instance) { logger.trace("kraken firewall api: registering firewall instance {}", instance.getName()); FirewallInstance old = instances.putIfAbsent(instance.getName(), instance); if (old != null) throw new IllegalStateException("duplicated instance name exists: " + instance.getName()); } @Override public void unregister(FirewallInstance instance) { logger.trace("kraken firewall api: unregistering firewall instance {}", instance.getName()); // remove from instance registry FirewallInstance old = instances.remove(instance.getName()); if (old != instance) instances.put(old.getName(), old); } // // FirewallInstanceManagerListener // @Override public void onCreate(FirewallInstanceManager manager, FirewallInstance instance) { // create persistent config try { Preferences instanceRoot = getInstanceConfig(); Preferences instanceNode = instanceRoot.node(instance.getName()); instanceNode.put("manager", manager.getName()); instanceRoot.flush(); instanceRoot.sync(); } catch (BackingStoreException e) { logger.error("kraken firewall api: cannot create persistent instance config for " + instance.getName(), e); } instances.putIfAbsent(instance.getName(), instance); FirewallGroup allGroup = getGroup("all"); allGroup.join(instance.getName()); instances.remove(instance.getName()); } @Override public void onRemove(FirewallInstanceManager manager, FirewallInstance instance) { try { // leave all groups for (FirewallGroup group : groups.values()) { group.leave(instance.getName()); } // remove persistent config Preferences instanceRoot = getInstanceConfig(); instanceRoot.node(instance.getName()).removeNode(); instanceRoot.flush(); instanceRoot.sync(); logger.trace("kraken firewall api: removed persistent instance [{}] config", instance.getName()); } catch (BackingStoreException e) { logger.error("kraken firewall api: cannot remove persistent instance config for " + instance.getName()); } // invoke callbacks for (FirewallGroup group : getGroups()) { group.leave(instance.getName()); } } // // FirewallGroupListener // @Override public void onJoin(FirewallGroup group, String instanceName) { try { logger.trace("kraken firewall api: join group {}, member {}", group.getName(), instanceName); Preferences memberRoot = getMemberConfig(); Preferences p = memberRoot.node(group.getName()); p.node(instanceName); memberRoot.flush(); memberRoot.sync(); } catch (BackingStoreException e) { logger.error("kraken firewall api: cannot join group {}, {}", group.getName(), instanceName); } } @Override public void onLeave(FirewallGroup group, String instanceName) { try { logger.trace("kraken firewall api: leave group {}, member {}", group.getName(), instanceName); Preferences memberRoot = getMemberConfig(); Preferences p = memberRoot.node(group.getName()); p.node(instanceName).removeNode(); memberRoot.flush(); memberRoot.sync(); } catch (BackingStoreException e) { logger.error("kraken firewall api: cannot leave group {}, {}", group.getName(), instanceName); } } @Override public void onBlock(FirewallGroup group, FirewallRule rule) { try { logger.trace("kraken firewall api: block group {}, rule {}", group.getName(), rule); Preferences rootNode = getRuleConfig(); Preferences groupNode = rootNode.node(group.getName()); String ip = rule.getSourceIp().getHostAddress(); Preferences p = groupNode.node(ip); p.putLong("expire", rule.getExpire().getTime()); rootNode.flush(); rootNode.sync(); } catch (BackingStoreException e) { logger.error("kraken firewall api: cannot block [group {}, ip {}]", group.getName(), rule.getSourceIp()); } } @Override public void onUnblock(FirewallGroup group, FirewallRule rule) { try { logger.trace("kraken firewall api: unblock group {}, rule {}", group.getName(), rule); Preferences rootNode = getRuleConfig(); Preferences groupNode = rootNode.node(group.getName()); String ip = rule.getSourceIp().getHostAddress(); groupNode.node(ip).removeNode(); rootNode.flush(); rootNode.sync(); } catch (BackingStoreException e) { logger.error("kraken firewall api: cannot unblock [group {}, ip {}]", group.getName(), rule.getSourceIp()); } } private Preferences getInstanceConfig() { if (prefsvc == null) throw new IllegalStateException("PreferencesService not found"); return prefsvc.getSystemPreferences().node(INSTANCES); } private Preferences getMemberConfig() { if (prefsvc == null) throw new IllegalStateException("PreferencesService not found"); return prefsvc.getSystemPreferences().node(MEMBERS); } private Preferences getRuleConfig() { if (prefsvc == null) throw new IllegalStateException("PreferencesService not found"); return prefsvc.getSystemPreferences().node(RULES); } private class Unblocker implements Runnable { private volatile boolean doStop = false; @Override public void run() { while (!doStop) { try { Thread.sleep(10000); Date now = new Date(); for (FirewallGroup group : groups.values()) { for (FirewallRule rule : group.getRules()) { logger.debug("kraken firewall api: inspecting expiration of rule [{}]", rule); if (now.after(rule.getExpire())) { logger.trace("kraken firewall api: unblock rule [{}]", rule); group.unblockSourceIp(rule.getSourceIp()); } } } } catch (InterruptedException e) { logger.info("kraken firewall api: interrupted"); } catch (Exception e) { logger.error("kraken firewall api: unblocker error", e); } } doStop = false; logger.info("kraken firewall api: unblocker stopped"); } } }