/** * Copyright 2011, Big Switch Networks, Inc. * Originally created by Amer Tahir * * 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 net.floodlightcontroller.firewall; import java.io.IOException; import java.util.Iterator; import java.util.List; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.MappingJsonFactory; import org.projectfloodlight.openflow.types.DatapathId; import org.projectfloodlight.openflow.types.EthType; import org.projectfloodlight.openflow.types.IPv4AddressWithMask; import org.projectfloodlight.openflow.types.IpProtocol; import org.projectfloodlight.openflow.types.MacAddress; import org.projectfloodlight.openflow.types.OFPort; import org.projectfloodlight.openflow.types.TransportPort; import org.restlet.resource.Delete; import org.restlet.resource.Post; import org.restlet.resource.Get; import org.restlet.resource.ServerResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class FirewallRulesResource extends ServerResource { protected static Logger log = LoggerFactory.getLogger(FirewallRulesResource.class); @Get("json") public List<FirewallRule> retrieve() { IFirewallService firewall = (IFirewallService)getContext().getAttributes(). get(IFirewallService.class.getCanonicalName()); return firewall.getRules(); } /** * Takes a Firewall Rule string in JSON format and parses it into * our firewall rule data structure, then adds it to the firewall. * @param fmJson The Firewall rule entry in JSON format. * @return A string status message */ @Post public String store(String fmJson) { IFirewallService firewall = (IFirewallService)getContext().getAttributes(). get(IFirewallService.class.getCanonicalName()); FirewallRule rule = jsonToFirewallRule(fmJson); if (rule == null) { return "{\"status\" : \"Error! Could not parse firewall rule, see log for details.\"}"; } String status = null; if (checkRuleExists(rule, firewall.getRules())) { status = "Error! A similar firewall rule already exists."; log.error(status); return ("{\"status\" : \"" + status + "\"}"); } else { // add rule to firewall String res = checkRuleOverlap(rule, firewall.getRules()); if(res != null){ // isOverlapable status = "Rule Not added"; log.error(res); return ("{\"status\" : \"" + status + "\"}"); } firewall.addRule(rule); status = "Rule added"; return ("{\"status\" : \"" + status + "\", \"rule-id\" : \""+ Integer.toString(rule.ruleid) + "\"}"); } } /** * Takes a Firewall Rule string in JSON format and parses it into * our firewall rule data structure, then deletes it from the firewall. * @param fmJson The Firewall rule entry in JSON format. * @return A string status message */ @Delete public String remove(String fmJson) { IFirewallService firewall = (IFirewallService)getContext().getAttributes(). get(IFirewallService.class.getCanonicalName()); FirewallRule rule = jsonToFirewallRule(fmJson); if (rule == null) { //TODO compose the error with a json formatter return "{\"status\" : \"Error! Could not parse firewall rule, see log for details.\"}"; } String status = null; boolean exists = false; Iterator<FirewallRule> iter = firewall.getRules().iterator(); while (iter.hasNext()) { FirewallRule r = iter.next(); if (r.ruleid == rule.ruleid) { exists = true; break; } } if (!exists) { status = "Error! Can't delete, a rule with this ID doesn't exist."; log.error(status); } else { // delete rule from firewall firewall.deleteRule(rule.ruleid); status = "Rule deleted"; } return ("{\"status\" : \"" + status + "\"}"); } /** * Turns a JSON formatted Firewall Rule string into a FirewallRule instance * @param fmJson The JSON formatted static firewall rule * @return The FirewallRule instance * @throws IOException If there was an error parsing the JSON */ public static FirewallRule jsonToFirewallRule(String fmJson) { FirewallRule rule = new FirewallRule(); MappingJsonFactory f = new MappingJsonFactory(); JsonParser jp; try { try { jp = f.createParser(fmJson); } catch (JsonParseException e) { throw new IOException(e); } jp.nextToken(); if (jp.getCurrentToken() != JsonToken.START_OBJECT) { throw new IOException("Expected START_OBJECT"); } while (jp.nextToken() != JsonToken.END_OBJECT) { if (jp.getCurrentToken() != JsonToken.FIELD_NAME) { throw new IOException("Expected FIELD_NAME"); } String n = jp.getCurrentName(); jp.nextToken(); if (jp.getText().equals("")) { continue; } // This is currently only applicable for remove(). In store(), ruleid takes a random number if (n.equalsIgnoreCase("ruleid")) { try { rule.ruleid = Integer.parseInt(jp.getText()); } catch (IllegalArgumentException e) { log.error("Unable to parse rule ID: {}", jp.getText()); } } // This assumes user having dpid info for involved switches else if (n.equalsIgnoreCase("switchid")) { rule.any_dpid = false; try { rule.dpid = DatapathId.of(jp.getText()); } catch (NumberFormatException e) { log.error("Unable to parse switch DPID: {}", jp.getText()); //TODO should return some error message via HTTP message } } else if (n.equalsIgnoreCase("src-inport")) { rule.any_in_port = false; try { rule.in_port = OFPort.of(Integer.parseInt(jp.getText())); } catch (NumberFormatException e) { log.error("Unable to parse ingress port: {}", jp.getText()); //TODO should return some error message via HTTP message } } else if (n.equalsIgnoreCase("src-mac")) { if (!jp.getText().equalsIgnoreCase("ANY")) { rule.any_dl_src = false; try { rule.dl_src = MacAddress.of(jp.getText()); } catch (IllegalArgumentException e) { log.error("Unable to parse source MAC: {}", jp.getText()); //TODO should return some error message via HTTP message } } } else if (n.equalsIgnoreCase("dst-mac")) { if (!jp.getText().equalsIgnoreCase("ANY")) { rule.any_dl_dst = false; try { rule.dl_dst = MacAddress.of(jp.getText()); } catch (IllegalArgumentException e) { log.error("Unable to parse destination MAC: {}", jp.getText()); //TODO should return some error message via HTTP message } } } else if (n.equalsIgnoreCase("dl-type")) { if (jp.getText().equalsIgnoreCase("ARP")) { rule.any_dl_type = false; rule.dl_type = EthType.ARP; } else if (jp.getText().equalsIgnoreCase("IPv4")) { rule.any_dl_type = false; rule.dl_type = EthType.IPv4; } } else if (n.equalsIgnoreCase("src-ip")) { if (!jp.getText().equalsIgnoreCase("ANY")) { rule.any_nw_src = false; if (rule.dl_type.equals(EthType.NONE)){ rule.any_dl_type = false; rule.dl_type = EthType.IPv4; } try { rule.nw_src_prefix_and_mask = IPv4AddressWithMask.of(jp.getText()); } catch (IllegalArgumentException e) { log.error("Unable to parse source IP: {}", jp.getText()); //TODO should return some error message via HTTP message } } } else if (n.equalsIgnoreCase("dst-ip")) { if (!jp.getText().equalsIgnoreCase("ANY")) { rule.any_nw_dst = false; if (rule.dl_type.equals(EthType.NONE)){ rule.any_dl_type = false; rule.dl_type = EthType.IPv4; } try { rule.nw_dst_prefix_and_mask = IPv4AddressWithMask.of(jp.getText()); } catch (IllegalArgumentException e) { log.error("Unable to parse destination IP: {}", jp.getText()); //TODO should return some error message via HTTP message } } } else if (n.equalsIgnoreCase("nw-proto")) { if (jp.getText().equalsIgnoreCase("TCP")) { rule.any_nw_proto = false; rule.nw_proto = IpProtocol.TCP; rule.any_dl_type = false; rule.dl_type = EthType.IPv4; } else if (jp.getText().equalsIgnoreCase("UDP")) { rule.any_nw_proto = false; rule.nw_proto = IpProtocol.UDP; rule.any_dl_type = false; rule.dl_type = EthType.IPv4; } else if (jp.getText().equalsIgnoreCase("ICMP")) { rule.any_nw_proto = false; rule.nw_proto = IpProtocol.ICMP; rule.any_dl_type = false; rule.dl_type = EthType.IPv4; } } else if (n.equalsIgnoreCase("tp-src")) { rule.any_tp_src = false; try { rule.tp_src = TransportPort.of(Integer.parseInt(jp.getText())); } catch (IllegalArgumentException e) { log.error("Unable to parse source transport port: {}", jp.getText()); //TODO should return some error message via HTTP message } } else if (n.equalsIgnoreCase("tp-dst")) { rule.any_tp_dst = false; try { rule.tp_dst = TransportPort.of(Integer.parseInt(jp.getText())); } catch (IllegalArgumentException e) { log.error("Unable to parse destination transport port: {}", jp.getText()); //TODO should return some error message via HTTP message } } else if (n.equalsIgnoreCase("priority")) { try { rule.priority = Integer.parseInt(jp.getText()); } catch (IllegalArgumentException e) { log.error("Unable to parse priority: {}", jp.getText()); //TODO should return some error message via HTTP message } } else if (n.equalsIgnoreCase("action")) { if (jp.getText().equalsIgnoreCase("allow") || jp.getText().equalsIgnoreCase("accept")) { rule.action = FirewallRule.FirewallAction.ALLOW; } else if (jp.getText().equalsIgnoreCase("deny") || jp.getText().equalsIgnoreCase("drop")) { rule.action = FirewallRule.FirewallAction.DROP; } } } } catch (IOException e) { log.error("Unable to parse JSON string: {}", e); } return rule; } public static boolean checkRuleExists(FirewallRule rule, List<FirewallRule> rules) { Iterator<FirewallRule> iter = rules.iterator(); while (iter.hasNext()) { FirewallRule r = iter.next(); // check if we find a similar rule if (rule.isSameAs(r)) { return true; } } // no rule matched, so it doesn't exist in the rules return false; } public static final int DPID_BIT = 1; public static final int IN_PORT_BIT = 2; public static final int DL_SRC_BIT = 4; public static final int DL_DST_BIT = 8; public static final int DL_TYPE_BIT = 16; public static final int NW_SRC_BIT = 32; public static final int NW_DST_BIT = 64; public static final int NW_PROTO_BIT= 128; public static final int TP_SRC_BIT = 256; public static final int TP_DST_BIT = 512; public static final String NEW_RULE_OVERLAPS = "WARNING: This rule overlapes another firewall rule with rule id: "; public static final String NEW_RULE_OVERLAPED = "WARNING: The rule is overlaped by another firewall rule with rule id: "; /** * Checks for Rule Overlaping in following conditions * New rule having priority equal to current rules * New rule having having equal parameters and wildcards * @param rule - the new rule * @param rules - rules list * @return error a String error message. Null if no overlap event is found */ public static String checkRuleOverlap(FirewallRule rule, List<FirewallRule> rules) { Iterator<FirewallRule> iter = rules.iterator(); // Loops throught all Rules while (iter.hasNext()) { FirewallRule r = iter.next(); // Priority check if(rule.priority == r.priority){ int overlap = 0; // if true , new rule overlapes, false new rule is overlaped boolean whoOverlapes = false; int sameField = 0; // Check Switch Overlap if(rule.any_dpid ^ r.any_dpid){ overlap += DPID_BIT; whoOverlapes = (rule.any_dpid && !whoOverlapes) ? true : false; } if(rule.any_in_port ^ r.any_in_port){ overlap += IN_PORT_BIT; whoOverlapes = (rule.any_in_port && !whoOverlapes) ? true : false; } if(rule.dpid.equals(r.dpid)) sameField += DPID_BIT; if(rule.in_port.equals(r.in_port)) sameField += IN_PORT_BIT; if((overlap | sameField) == 3 && overlap > 0) return ((whoOverlapes) ? NEW_RULE_OVERLAPS : NEW_RULE_OVERLAPED) + r.ruleid; // Check Layer 2 Overlap if(rule.any_dl_src ^ r.any_dl_src){ overlap += DL_SRC_BIT; whoOverlapes = (rule.any_dl_src && !whoOverlapes) ? true : false; } if(rule.any_dl_dst ^ r.any_dl_dst){ overlap += DL_DST_BIT; whoOverlapes = (rule.any_dl_dst && !whoOverlapes) ? true : false; } if(rule.any_dl_type ^ r.any_dl_type){ overlap += DL_TYPE_BIT; whoOverlapes = (rule.any_dl_type && !whoOverlapes) ? true : false; } if(rule.dl_src.equals(r.dl_src)) sameField += DL_SRC_BIT; if(rule.dl_dst.equals(r.dl_src)) sameField += DL_DST_BIT; if(rule.dl_type.equals(r.dl_type)) sameField += DL_TYPE_BIT; if((overlap | sameField) == 31 && overlap > 0) return ((whoOverlapes) ? NEW_RULE_OVERLAPS : NEW_RULE_OVERLAPED) + r.ruleid; // Check Layer 3 Overlap if(rule.any_nw_src ^ r.any_nw_src){ overlap += NW_SRC_BIT; whoOverlapes = (rule.any_nw_src && !whoOverlapes) ? true : false; } if(rule.any_nw_dst ^ r.any_nw_dst){ overlap += NW_DST_BIT; whoOverlapes = (rule.any_nw_src && !whoOverlapes) ? true : false; } if(rule.nw_src_prefix_and_mask.equals(r.nw_src_prefix_and_mask)) sameField += NW_SRC_BIT; if(rule.nw_dst_prefix_and_mask.equals(r.nw_dst_prefix_and_mask)) sameField += NW_DST_BIT; if((overlap | sameField) == 127 && overlap > 0) return ((whoOverlapes) ? NEW_RULE_OVERLAPS : NEW_RULE_OVERLAPED) + r.ruleid; // Check Layer 4 Overlap if(rule.any_nw_proto ^ r.any_nw_proto){ overlap += NW_PROTO_BIT; whoOverlapes = (rule.any_nw_proto && !whoOverlapes) ? true : false; } if(rule.any_tp_src ^ r.any_tp_src ){ overlap += TP_SRC_BIT; whoOverlapes = (rule.any_tp_src && !whoOverlapes) ? true : false; } if(rule.any_tp_dst ^ r.any_tp_dst){ overlap += TP_DST_BIT; whoOverlapes = (rule.any_tp_dst && !whoOverlapes) ? true : false; } if(rule.nw_proto.equals(r.nw_proto)) sameField += NW_PROTO_BIT; if(rule.tp_src.equals(r.tp_src)) sameField += TP_SRC_BIT; if(rule.tp_dst.equals(r.tp_dst)) sameField += TP_DST_BIT; if((overlap | sameField) == 1027 && overlap > 0){ return ((whoOverlapes) ? NEW_RULE_OVERLAPS : NEW_RULE_OVERLAPED) + r.ruleid; } } } return null; } }