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