From 1c5053d080504379e02b5cda287fa014fe405e09 Mon Sep 17 00:00:00 2001 From: Amer Tahir <amertahir@gmail.com> Date: Wed, 8 Aug 2012 07:19:34 -0400 Subject: [PATCH] completed REST API for rules no hardcoded rules now, REST API allows adding/removing rules --- .../firewall/Firewall.java | 58 ++-- .../firewall/FirewallRule.java | 25 +- .../firewall/FirewallRulesResource.java | 250 ++++++++++++++++++ .../firewall/FirewallWebRoutable.java | 1 + .../firewall/IFirewallService.java | 10 + 5 files changed, 311 insertions(+), 33 deletions(-) create mode 100644 src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java diff --git a/src/main/java/net/floodlightcontroller/firewall/Firewall.java b/src/main/java/net/floodlightcontroller/firewall/Firewall.java index cdd989678..6b7bf8e2a 100644 --- a/src/main/java/net/floodlightcontroller/firewall/Firewall.java +++ b/src/main/java/net/floodlightcontroller/firewall/Firewall.java @@ -124,38 +124,6 @@ public class Firewall implements IFirewallService, IOFMessageListener, IFloodlig // assumes no switches connected at startup() //rules = readRulesFromStorage(); - - // insert rule to allow ICMP traffic - FirewallRule rule = new FirewallRule(); - rule.proto_type = IPv4.PROTOCOL_ICMP; - rule.wildcard_proto_type = false; - rule.priority = 1; - this.rules.add(rule); - // insert rule to allow TCP traffic destined to port 80 - rule = new FirewallRule(); - rule.proto_type = IPv4.PROTOCOL_TCP; - rule.wildcard_proto_type = false; - rule.proto_dstport = 80; - rule.priority = 2; - rule.is_denyrule = true; - this.rules.add(rule); - // insert rule to allow TCP traffic originating from port 80 - rule = new FirewallRule(); - rule.proto_type = IPv4.PROTOCOL_TCP; - rule.wildcard_proto_type = false; - rule.proto_srcport = 80; - rule.priority = 3; - rule.is_denyrule = true; - this.rules.add(rule); - // insert rule to allow TCP traffic - rule = new FirewallRule(); - rule.proto_type = IPv4.PROTOCOL_TCP; - rule.wildcard_proto_type = false; - rule.priority = 4; - this.rules.add(rule); - - // now sort the rules - Collections.sort(this.rules); } @Override @@ -215,6 +183,32 @@ public class Firewall implements IFirewallService, IOFMessageListener, IFloodlig return this.rules; } + @Override + public void addRule(FirewallRule rule) { + this.rules.add(rule); + // now re-sort the rules + Collections.sort(this.rules); + } + + @Override + public void deleteRule(int ruleid) { + boolean found = false; + Iterator<FirewallRule> iter = this.rules.iterator(); + while (iter.hasNext()) { + FirewallRule r = iter.next(); + if (r.ruleid == ruleid) { + // found the rule, now remove it + iter.remove(); + found = true; + break; + } + } + // now re-sort the rules if we deleted one + if (found) { + Collections.sort(this.rules); + } + } + protected List<Object> matchWithRule(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) { FirewallRule matched_rule = null; Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD); diff --git a/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java b/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java index 17d9fb16d..cf0f3374d 100644 --- a/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java +++ b/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java @@ -42,11 +42,34 @@ public class FirewallRule implements Comparable { this.wildcard_dst_mac = true; this.wildcard_dst_ip = true; this.wildcard_switchid = true; - this.priority = 0; + this.priority = 32767; this.is_denyrule = false; } public int compareTo(Object rule) { return this.priority - ((FirewallRule)rule).priority; } + + public boolean isSameAs(FirewallRule r) { + if ( + this.is_denyrule != r.is_denyrule || + this.wildcard_switchid != r.wildcard_switchid || + this.wildcard_src_inport != r.wildcard_src_inport || + this.wildcard_src_ip != r.wildcard_src_ip || + this.wildcard_src_mac != r.wildcard_src_ip || + this.wildcard_proto_type != r.wildcard_proto_type || + this.wildcard_dst_ip != r.wildcard_dst_ip || + this.wildcard_dst_mac != r.wildcard_dst_mac || + (this.wildcard_switchid == false && this.switchid != r.switchid) || + (this.wildcard_src_inport == false && this.src_inport != r.src_inport) || + (this.wildcard_src_ip == false && (this.src_ip_prefix != r.src_ip_prefix || this.src_ip_bits != r.src_ip_bits)) || + (this.wildcard_src_mac == false && this.src_mac != r.src_mac) || + (this.wildcard_proto_type == false && this.proto_type != r.proto_type) || + (this.wildcard_dst_ip == false && (this.dst_ip_prefix != r.dst_ip_prefix || this.dst_ip_bits != r.dst_ip_bits)) || + (this.wildcard_dst_mac == false && this.dst_mac != r.dst_mac) + ) { + return false; + } + return true; + } } diff --git a/src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java b/src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java new file mode 100644 index 000000000..f19950328 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java @@ -0,0 +1,250 @@ +package net.floodlightcontroller.firewall; + +import java.io.IOException; +import java.util.Iterator; +import java.util.List; + +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; +import org.codehaus.jackson.map.MappingJsonFactory; +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; + + +import net.floodlightcontroller.firewall.Firewall; +import net.floodlightcontroller.packet.Ethernet; +import net.floodlightcontroller.packet.IPv4; + + +public class FirewallRulesResource extends ServerResource { + protected static Logger log = LoggerFactory.getLogger(FirewallRulesResource.class); + + @Get("json") + public Object handleRequest() { + String op = (String) getRequestAttributes().get("op"); + IFirewallService firewall = + (IFirewallService)getContext().getAttributes(). + get(IFirewallService.class.getCanonicalName()); + + if (op.equalsIgnoreCase("all")) { + return firewall.getRules(); + } + + return "{\"status\" : \"failure\", \"details\" : \"invalid operation\"}"; + } + + /** + * 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("json") + public String store(String fmJson) { + IFirewallService firewall = + (IFirewallService)getContext().getAttributes(). + get(IFirewallService.class.getCanonicalName()); + + FirewallRule rule; + try { + rule = jsonToFirewallRule(fmJson); + } catch (IOException e) { + log.error("Error parsing firewall rule: " + fmJson, e); + e.printStackTrace(); + 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); + } else { + // add rule to firewall + firewall.addRule(rule); + status = "Rule added"; + } + return ("{\"status\" : \"" + status + "\"}"); + } + + /** + * 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("json") + public String remove(String fmJson) { + IFirewallService firewall = + (IFirewallService)getContext().getAttributes(). + get(IFirewallService.class.getCanonicalName()); + + FirewallRule rule; + try { + rule = jsonToFirewallRule(fmJson); + } catch (IOException e) { + log.error("Error parsing firewall rule: " + fmJson, e); + e.printStackTrace(); + 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) throws IOException { + FirewallRule rule = new FirewallRule(); + MappingJsonFactory f = new MappingJsonFactory(); + JsonParser jp; + + try { + jp = f.createJsonParser(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; + + String tmp; + if (n == "ruleid") { + rule.ruleid = Integer.parseInt(jp.getText()); + } else if (n == "switchid") { + tmp = jp.getText(); + if (tmp.equalsIgnoreCase("-1") == false) { + rule.switchid = Long.parseLong(tmp); + rule.wildcard_switchid = false; + } + } else if (n == "src_inport") { + rule.src_inport = Short.parseShort(jp.getText()); + } else if (n == "src_mac") { + tmp = jp.getText(); + if (tmp.equalsIgnoreCase("ANY") == false) { + rule.wildcard_src_mac = false; + rule.src_mac = Ethernet.toLong(Ethernet.toMACAddress(tmp)); + } + } else if (n == "src_ip") { + tmp = jp.getText(); + if (tmp.equalsIgnoreCase("ANY") == false) { + rule.wildcard_src_ip = false; + int[] cidr = IPCIDRToPrefixBits(tmp); + rule.src_ip_prefix = cidr[0]; + rule.src_ip_bits = cidr[1]; + } + } else if (n == "proto_type") { + tmp = jp.getText(); + if (tmp.equalsIgnoreCase("TCP")) { + rule.wildcard_proto_type = false; + rule.proto_type = IPv4.PROTOCOL_TCP; + } else if (tmp.equalsIgnoreCase("UDP")) { + rule.wildcard_proto_type = false; + rule.proto_type = IPv4.PROTOCOL_UDP; + } else if (tmp.equalsIgnoreCase("ICMP")) { + rule.wildcard_proto_type = false; + rule.proto_type = IPv4.PROTOCOL_ICMP; + } + } else if (n == "proto_srcport") { + rule.proto_srcport = Short.parseShort(jp.getText()); + } else if (n == "proto_dstport") { + rule.proto_dstport = Short.parseShort(jp.getText()); + } else if (n == "dst_mac") { + tmp = jp.getText(); + if (tmp.equalsIgnoreCase("ANY") == false) { + rule.wildcard_dst_mac = false; + rule.dst_mac = Ethernet.toLong(Ethernet.toMACAddress(tmp)); + } + } else if (n == "dst_ip") { + tmp = jp.getText(); + if (tmp.equalsIgnoreCase("ANY") == false) { + rule.wildcard_dst_ip = false; + int[] cidr = IPCIDRToPrefixBits(tmp); + rule.dst_ip_prefix = cidr[0]; + rule.dst_ip_bits = cidr[1]; + } + } else if (n == "priority") { + rule.priority = Integer.parseInt(jp.getText()); + } else if (n == "is_denyrule") { + if (jp.getText().equalsIgnoreCase("true") == true) { + rule.is_denyrule = true; + } else { + rule.is_denyrule = false; + } + } + } + + return rule; + } + + public static int[] IPCIDRToPrefixBits(String cidr) { + int ret[] = new int[2]; + + // as IP can also be a prefix rather than an absolute address + // split it over "/" to get the bit range + String[] parts = cidr.split("/"); + String cidr_prefix = parts[0].trim(); + int cidr_bits = 0; + if (parts.length == 2) { + try { + cidr_bits = Integer.parseInt(parts[1].trim()); + } catch (Exception exp) { + cidr_bits = 32; + } + } + ret[0] = IPv4.toIPv4Address(cidr_prefix); + ret[1] = cidr_bits; + + return ret; + } + + 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; + } +} diff --git a/src/main/java/net/floodlightcontroller/firewall/FirewallWebRoutable.java b/src/main/java/net/floodlightcontroller/firewall/FirewallWebRoutable.java index e38926305..acec88fae 100644 --- a/src/main/java/net/floodlightcontroller/firewall/FirewallWebRoutable.java +++ b/src/main/java/net/floodlightcontroller/firewall/FirewallWebRoutable.java @@ -19,6 +19,7 @@ public class FirewallWebRoutable implements RestletRoutable { public Router getRestlet(Context context) { Router router = new Router(context); router.attach("/{op}/json", FirewallResource.class); + router.attach("/rules/json", FirewallRulesResource.class); return router; } diff --git a/src/main/java/net/floodlightcontroller/firewall/IFirewallService.java b/src/main/java/net/floodlightcontroller/firewall/IFirewallService.java index 36e0e3c7d..9edd4bf3d 100644 --- a/src/main/java/net/floodlightcontroller/firewall/IFirewallService.java +++ b/src/main/java/net/floodlightcontroller/firewall/IFirewallService.java @@ -21,4 +21,14 @@ public interface IFirewallService extends IFloodlightService { * @return List of all rules */ public List<FirewallRule> getRules(); + + /** + * Adds a new Firewall rule + */ + public void addRule(FirewallRule rule); + + /** + * Deletes a Firewall rule + */ + public void deleteRule(int ruleid); } -- GitLab