/**
* 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 static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.IFloodlightProviderService;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.module.FloodlightModuleContext;
import net.floodlightcontroller.packet.ARP;
import net.floodlightcontroller.packet.Data;
import net.floodlightcontroller.packet.Ethernet;
import net.floodlightcontroller.packet.IPacket;
import net.floodlightcontroller.packet.IPv4;
import net.floodlightcontroller.packet.TCP;
import net.floodlightcontroller.packet.UDP;
import net.floodlightcontroller.restserver.IRestApiService;
import net.floodlightcontroller.restserver.RestApiServer;
import net.floodlightcontroller.routing.IRoutingDecision;
import net.floodlightcontroller.storage.IStorageSourceService;
import net.floodlightcontroller.storage.memory.MemoryStorageSource;
import net.floodlightcontroller.test.FloodlightTestCase;
import net.floodlightcontroller.util.MACAddress;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
import org.openflow.protocol.OFPacketIn;
import org.openflow.protocol.OFPacketIn.OFPacketInReason;
import org.openflow.protocol.OFType;
import org.openflow.util.HexString;
/**
* Unit test for stateless firewall implemented as a Google Summer of Code project.
*
* @author Amer Tahir
*/
public class FirewallTest extends FloodlightTestCase {
protected FloodlightContext cntx;
protected OFPacketIn packetIn;
protected IOFSwitch sw;
protected IPacket tcpPacket;
protected IPacket broadcastARPPacket;
protected IPacket ARPReplyPacket;
protected IPacket broadcastIPPacket;
protected IPacket tcpPacketReply;
protected IPacket broadcastMalformedPacket;
private Firewall firewall;
public static String TestSwitch1DPID = "00:00:00:00:00:00:00:01";
@Override
@Before
public void setUp() throws Exception {
super.setUp();
cntx = new FloodlightContext();
mockFloodlightProvider = getMockFloodlightProvider();
firewall = new Firewall();
IStorageSourceService storageService = new MemoryStorageSource();
RestApiServer restApi = new RestApiServer();
// Mock switches
long dpid = HexString.toLong(TestSwitch1DPID);
sw = EasyMock.createNiceMock(IOFSwitch.class);
expect(sw.getId()).andReturn(dpid).anyTimes();
expect(sw.getStringId()).andReturn(TestSwitch1DPID).anyTimes();
replay(sw);
// Load the switch map
Map<Long, IOFSwitch> switches = new HashMap<Long, IOFSwitch>();
switches.put(dpid, sw);
mockFloodlightProvider.setSwitches(switches);
FloodlightModuleContext fmc = new FloodlightModuleContext();
fmc.addService(IFloodlightProviderService.class,
mockFloodlightProvider);
fmc.addService(IFirewallService.class, firewall);
fmc.addService(IStorageSourceService.class, storageService);
fmc.addService(IRestApiService.class, restApi);
restApi.init(fmc);
firewall.init(fmc);
firewall.startUp(fmc);
// Build our test packet
this.tcpPacket = new Ethernet()
.setDestinationMACAddress("00:11:22:33:44:55")
.setSourceMACAddress("00:44:33:22:11:00")
.setVlanID((short) 42)
.setEtherType(Ethernet.TYPE_IPv4)
.setPayload(
new IPv4()
.setTtl((byte) 128)
.setSourceAddress("192.168.1.1")
.setDestinationAddress("192.168.1.2")
.setPayload(new TCP()
.setSourcePort((short) 81)
.setDestinationPort((short) 80)
.setPayload(new Data(new byte[] {0x01}))));
// Build a broadcast ARP packet
this.broadcastARPPacket = new Ethernet()
.setDestinationMACAddress("FF:FF:FF:FF:FF:FF")
.setSourceMACAddress("00:44:33:22:11:00")
.setVlanID((short) 42)
.setEtherType(Ethernet.TYPE_ARP)
.setPayload(
new ARP()
.setHardwareType(ARP.HW_TYPE_ETHERNET)
.setProtocolType(ARP.PROTO_TYPE_IP)
.setOpCode(ARP.OP_REQUEST)
.setHardwareAddressLength((byte)6)
.setProtocolAddressLength((byte)4)
.setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:00"))
.setSenderProtocolAddress(IPv4.toIPv4Address("192.168.1.1"))
.setTargetHardwareAddress(Ethernet.toMACAddress("00:00:00:00:00:00"))
.setTargetProtocolAddress(IPv4.toIPv4Address("192.168.1.2"))
.setPayload(new Data(new byte[] {0x01})));
// Build a ARP packet
this.ARPReplyPacket = new Ethernet()
.setDestinationMACAddress("00:44:33:22:11:00")
.setSourceMACAddress("00:11:22:33:44:55")
.setVlanID((short) 42)
.setEtherType(Ethernet.TYPE_ARP)
.setPayload(
new ARP()
.setHardwareType(ARP.HW_TYPE_ETHERNET)
.setProtocolType(ARP.PROTO_TYPE_IP)
.setOpCode(ARP.OP_REQUEST)
.setHardwareAddressLength((byte)6)
.setProtocolAddressLength((byte)4)
.setSenderHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55"))
.setSenderProtocolAddress(IPv4.toIPv4Address("192.168.1.2"))
.setTargetHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:00"))
.setTargetProtocolAddress(IPv4.toIPv4Address("192.168.1.1"))
.setPayload(new Data(new byte[] {0x01})));
// Build a broadcast IP packet
this.broadcastIPPacket = new Ethernet()
.setDestinationMACAddress("FF:FF:FF:FF:FF:FF")
.setSourceMACAddress("00:44:33:22:11:00")
.setVlanID((short) 42)
.setEtherType(Ethernet.TYPE_IPv4)
.setPayload(
new IPv4()
.setTtl((byte) 128)
.setSourceAddress("192.168.1.1")
.setDestinationAddress("192.168.1.255")
.setPayload(new UDP()
.setSourcePort((short) 5000)
.setDestinationPort((short) 5001)
.setPayload(new Data(new byte[] {0x01}))));
// Build a malformed broadcast packet
this.broadcastMalformedPacket = new Ethernet()
.setDestinationMACAddress("FF:FF:FF:FF:FF:FF")
.setSourceMACAddress("00:44:33:22:11:00")
.setVlanID((short) 42)
.setEtherType(Ethernet.TYPE_IPv4)
.setPayload(
new IPv4()
.setTtl((byte) 128)
.setSourceAddress("192.168.1.1")
.setDestinationAddress("192.168.1.2")
.setPayload(new UDP()
.setSourcePort((short) 5000)
.setDestinationPort((short) 5001)
.setPayload(new Data(new byte[] {0x01}))));
this.tcpPacketReply = new Ethernet()
.setDestinationMACAddress("00:44:33:22:11:00")
.setSourceMACAddress("00:11:22:33:44:55")
.setVlanID((short) 42)
.setEtherType(Ethernet.TYPE_IPv4)
.setPayload(
new IPv4()
.setTtl((byte) 128)
.setSourceAddress("192.168.1.2")
.setDestinationAddress("192.168.1.1")
.setPayload(new TCP()
.setSourcePort((short) 80)
.setDestinationPort((short) 81)
.setPayload(new Data(new byte[] {0x02}))));
}
protected void setPacketIn(IPacket packet) {
byte[] serializedPacket = packet.serialize();
// Build the PacketIn
this.packetIn = ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_IN))
.setBufferId(-1)
.setInPort((short) 1)
.setPacketData(serializedPacket)
.setReason(OFPacketInReason.NO_MATCH)
.setTotalLength((short) serializedPacket.length);
// Add the packet to the context store
IFloodlightProviderService.bcStore.
put(cntx,
IFloodlightProviderService.CONTEXT_PI_PAYLOAD,
(Ethernet)packet);
}
@Test
public void testNoRules() throws Exception {
// enable firewall first
firewall.enableFirewall(true);
// simulate a packet-in event
this.setPacketIn(tcpPacket);
firewall.receive(sw, this.packetIn, cntx);
verify(sw);
assertEquals(0, firewall.rules.size());
IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
// no rules to match, so firewall should deny
assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP);
}
@Test
public void testReadRulesFromStorage() throws Exception {
// add 2 rules first
FirewallRule rule = new FirewallRule();
rule.in_port = 2;
rule.dl_src = MACAddress.valueOf("00:00:00:00:00:01").toLong();
rule.dl_dst = MACAddress.valueOf("00:00:00:00:00:02").toLong();
rule.priority = 1;
rule.action = FirewallRule.FirewallAction.DENY;
firewall.addRule(rule);
rule = new FirewallRule();
rule.in_port = 3;
rule.dl_src = MACAddress.valueOf("00:00:00:00:00:02").toLong();
rule.dl_dst = MACAddress.valueOf("00:00:00:00:00:01").toLong();
rule.nw_proto = IPv4.PROTOCOL_TCP;
rule.wildcard_nw_proto = false;
rule.tp_dst = 80;
rule.priority = 2;
rule.action = FirewallRule.FirewallAction.ALLOW;
firewall.addRule(rule);
List<FirewallRule> rules = firewall.readRulesFromStorage();
// verify rule 1
FirewallRule r = rules.get(0);
assertEquals(r.in_port, 2);
assertEquals(r.priority, 1);
assertEquals(r.dl_src, MACAddress.valueOf("00:00:00:00:00:01").toLong());
assertEquals(r.dl_dst, MACAddress.valueOf("00:00:00:00:00:02").toLong());
assertEquals(r.action, FirewallRule.FirewallAction.DENY);
// verify rule 2
r = rules.get(1);
assertEquals(r.in_port, 3);
assertEquals(r.priority, 2);
assertEquals(r.dl_src, MACAddress.valueOf("00:00:00:00:00:02").toLong());
assertEquals(r.dl_dst, MACAddress.valueOf("00:00:00:00:00:01").toLong());
assertEquals(r.nw_proto, IPv4.PROTOCOL_TCP);
assertEquals(r.tp_dst, 80);
assertEquals(r.wildcard_nw_proto, false);
assertEquals(r.action, FirewallRule.FirewallAction.ALLOW);
}
@Test
public void testRuleInsertionIntoStorage() throws Exception {
// add TCP rule
FirewallRule rule = new FirewallRule();
rule.nw_proto = IPv4.PROTOCOL_TCP;
rule.wildcard_nw_proto = false;
rule.priority = 1;
firewall.addRule(rule);
List<Map<String, Object>> rulesFromStorage = firewall.getStorageRules();
assertEquals(1, rulesFromStorage.size());
assertEquals(Integer.parseInt((String)rulesFromStorage.get(0).get("ruleid")), rule.ruleid);
}
@Test
public void testRuleDeletion() throws Exception {
// add TCP rule
FirewallRule rule = new FirewallRule();
rule.nw_proto = IPv4.PROTOCOL_TCP;
rule.wildcard_nw_proto = false;
rule.priority = 1;
firewall.addRule(rule);
int rid = rule.ruleid;
List<Map<String, Object>> rulesFromStorage = firewall.getStorageRules();
assertEquals(1, rulesFromStorage.size());
assertEquals(Integer.parseInt((String)rulesFromStorage.get(0).get("ruleid")), rid);
// delete rule
firewall.deleteRule(rid);
rulesFromStorage = firewall.getStorageRules();
assertEquals(0, rulesFromStorage.size());
}
@Test
public void testFirewallDisabled() throws Exception {
// firewall isn't enabled by default
// so, it shouldn't make any decision
// add TCP rule
FirewallRule rule = new FirewallRule();
rule.nw_proto = IPv4.PROTOCOL_TCP;
rule.wildcard_nw_proto = false;
rule.priority = 1;
firewall.addRule(rule);
this.setPacketIn(tcpPacket);
firewall.receive(sw, this.packetIn, cntx);
verify(sw);
assertEquals(1, firewall.rules.size());
IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
assertNull(decision);
}
@Test
public void testSimpleAllowRule() throws Exception {
// enable firewall first
firewall.enableFirewall(true);
// add TCP rule
FirewallRule rule = new FirewallRule();
rule.dl_type = Ethernet.TYPE_IPv4;
rule.wildcard_dl_type = false;
rule.nw_proto = IPv4.PROTOCOL_TCP;
rule.wildcard_nw_proto = false;
// source is IP 192.168.1.2
rule.nw_src_prefix = IPv4.toIPv4Address("192.168.1.2");
rule.wildcard_nw_src = false;
// dest is network 192.168.1.0/24
rule.nw_dst_prefix = IPv4.toIPv4Address("192.168.1.0");
rule.nw_dst_maskbits = 24;
rule.wildcard_nw_dst = false;
rule.priority = 1;
firewall.addRule(rule);
// simulate a packet-in events
this.setPacketIn(tcpPacketReply);
firewall.receive(sw, this.packetIn, cntx);
verify(sw);
IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD);
// clear decision
IRoutingDecision.rtStore.remove(cntx, IRoutingDecision.CONTEXT_DECISION);
this.setPacketIn(tcpPacket);
firewall.receive(sw, this.packetIn, cntx);
verify(sw);
decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP);
}
@Test
public void testOverlappingRules() throws Exception {
firewall.enableFirewall(true);
// add TCP port 80 (destination only) allow rule
FirewallRule rule = new FirewallRule();
rule.dl_type = Ethernet.TYPE_IPv4;
rule.wildcard_dl_type = false;
rule.nw_proto = IPv4.PROTOCOL_TCP;
rule.wildcard_nw_proto = false;
rule.tp_dst = 80;
rule.priority = 1;
firewall.addRule(rule);
// add block all rule
rule = new FirewallRule();
rule.action = FirewallRule.FirewallAction.DENY;
rule.priority = 2;
firewall.addRule(rule);
assertEquals(2, firewall.rules.size());
// packet destined to TCP port 80 - should be allowed
this.setPacketIn(tcpPacket);
firewall.receive(sw, this.packetIn, cntx);
verify(sw);
IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD);
// clear decision
IRoutingDecision.rtStore.remove(cntx, IRoutingDecision.CONTEXT_DECISION);
// packet destined for port 81 - should be denied
this.setPacketIn(tcpPacketReply);
firewall.receive(sw, this.packetIn, cntx);
verify(sw);
decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP);
}
@Test
public void testARP() throws Exception {
// enable firewall first
firewall.enableFirewall(true);
// no rules inserted so all traffic other than broadcast and ARP-request-broadcast should be blocked
// simulate an ARP broadcast packet-in event
this.setPacketIn(broadcastARPPacket);
firewall.receive(sw, this.packetIn, cntx);
verify(sw);
// broadcast-ARP traffic should be allowed
IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
assertEquals(IRoutingDecision.RoutingAction.MULTICAST, decision.getRoutingAction());
// clear decision
IRoutingDecision.rtStore.remove(cntx, IRoutingDecision.CONTEXT_DECISION);
// simulate an ARP reply packet-in event
this.setPacketIn(ARPReplyPacket);
firewall.receive(sw, this.packetIn, cntx);
verify(sw);
// ARP reply traffic should be denied
decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP);
}
@Test
public void testIPBroadcast() throws Exception {
// enable firewall first
firewall.enableFirewall(true);
// set subnet mask for IP broadcast
firewall.setSubnetMask("255.255.255.0");
// no rules inserted so all traffic other than broadcast and ARP-request-broadcast should be blocked
// simulate a packet-in event
this.setPacketIn(broadcastIPPacket);
firewall.receive(sw, this.packetIn, cntx);
verify(sw);
// broadcast traffic should be allowed
IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
assertEquals(IRoutingDecision.RoutingAction.MULTICAST, decision.getRoutingAction());
}
@Test
public void testMalformedIPBroadcast() throws Exception {
// enable firewall first
firewall.enableFirewall(true);
// no rules inserted so all traffic other than broadcast and ARP-request-broadcast should be blocked
// simulate a packet-in event
this.setPacketIn(broadcastMalformedPacket);
firewall.receive(sw, this.packetIn, cntx);
verify(sw);
// malformed broadcast traffic should NOT be allowed
IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP);
}
@Test
public void testLayer2Rule() throws Exception {
// enable firewall first
firewall.enableFirewall(true);
// add L2 rule
FirewallRule rule = new FirewallRule();
rule.dl_src = MACAddress.valueOf("00:44:33:22:11:00").toLong();
rule.wildcard_dl_src = false;
rule.dl_dst = MACAddress.valueOf("00:11:22:33:44:55").toLong();
rule.wildcard_dl_dst = false;
rule.priority = 1;
firewall.addRule(rule);
// add TCP deny all rule
rule = new FirewallRule();
rule.nw_proto = IPv4.PROTOCOL_TCP;
rule.wildcard_nw_proto = false;
rule.priority = 2;
rule.action = FirewallRule.FirewallAction.DENY;
firewall.addRule(rule);
// simulate a packet-in event
this.setPacketIn(tcpPacket);
firewall.receive(sw, this.packetIn, cntx);
verify(sw);
IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD);
}
}