'''
Created on Mar 7, 2012
@author: rcs
'''
from helper import *
from sts.headerspace.headerspace.tf import *
from sts.headerspace.headerspace.hs import *
from pox.openflow.libopenflow_01 import *
from pox.openflow.flow_table import SwitchFlowTable, TableEntry
from pox.openflow.software_switch import SoftwareSwitch
from pox.lib.packet.ethernet import ethernet
import re
from collections import namedtuple
global header_length
header_length = 31
# These should really be frozen (immutable), but python doesn't have language support for that..
global all_one
all_one = byte_array_get_all_one(header_length*2)
global all_zero
all_zero = byte_array_get_all_zero(header_length*2)
global all_x
all_x = byte_array_get_all_x(header_length*2)
global fields
# Taken from ofp_match in openflow 1.0 spec
# Note that these are ordered
fields = ["dl_src", "dl_dst", "dl_vlan", "dl_vlan_pcp", "dl_type", "nw_tos", "nw_proto", "nw_src", "nw_dst", "tp_src", "tp_dst"]
field_info = namedtuple('field_info', ['position', 'length'])
[docs]def ethernet_display(bytes):
if bytes_all_x(bytes):
return "x"
else:
# OpenFlow 1.0 doesn't support prefix matching over ethernet addrs
# So assume it's raw (no wildcards)
#print "Int unpacked is:", bytes_to_int(bytes)
#print "Eth String is:" + str(EthAddr(bytes_to_int(bytes)))
return str(EthAddr(bytes_to_int(bytes)))
[docs]def ip_display(bytes):
if bytes_all_x(bytes):
# Optimization
return "x"
else:
if len(bytes) != 4*2:
raise RuntimeError(len(bytes))
# Have to get a bit at a time, since an HSA byte contains 4 real bits,
# and mask length may not be a multiple of 4
# 0th byte, 0th bit is least significant
val = 0
current_idx = 0
mask_length_bits = 32
# twice as many bytes as normal IP addr
for byte_idx in range(8):
# Half as many bits per byte
for bit_idx in range(4):
bit_val = byte_array_get_bit(bytes,byte_idx,bit_idx)
if bit_val == 0x03:
# wildcard.
# Assumes that all wildcard bits are consecutive (no non-wildcard exists after a wildcard)
mask_length_bits -= 1
else:
normal_bit = hsa_bit_to_normal_bit(bit_val)
val += (normal_bit << current_idx)
current_idx += 1
# I'm misunderstanding something about IPAddr's network order
# arg... the val is in network order (has been sanity checked), but
# seeting network_order=True makes the byte order backwards...
return IPAddr(val).__str__() + "/" + str(mask_length_bits)
[docs]def default_display(bytes):
if bytes_all_x(bytes):
return "x"
else:
return str(bytes_to_int(bytes))
global hs_format
hs_format = HS_FORMAT()
# TODOC: wtf does wc stand for?
[docs]def wc_to_parsed_string(byte_arr):
out_string = ""
for field in fields:
offset = hs_format[field].position
len = hs_format[field].length
ba = bytearray()
for i in range(0,len):
ba.append(byte_arr[offset+i])
out_string = "%s%s:%s, "%(out_string,field,byte_array_to_hs_string(ba))
return out_string
[docs]def set_field(arr, field, value, right_mask=0):
'''
Sets the field in byte array arr to value.
@arr: the bytearray to set the field bits to value.
@field: 'eth_src','eth_dst','vlan','vlan_priority','eth_frame','ip_tos','ip_src','ip_dst','ip_proto','tcp_src','tcp_dst'
@value: an integer number, of the width equal to field's width
@right_mask: number of bits, from right that should be ignored when written to field.
e.g. to have a /24 ip address, set mask to 8.
'''
b_array = int_to_byte_array(value,8*hs_format[field].length)
start_pos = 2*hs_format[field].position
for i in range(2*hs_format[field].length):
if right_mask <= 4*i:
arr[start_pos + i] = b_array[i]
elif (right_mask > 4*i and right_mask < 4*i + 4):
shft = right_mask % 4;
rm = (0xff << 2*shft) & 0xff
lm = ~rm & 0xff
arr[start_pos + i] = (b_array[i] & rm) | (arr[start_pos + i] & lm)
# XXX CURRENTLY NOT WORKING
[docs]def optimize_forwarding_table(self):
print "=== Compressing forwarding table ==="
print " * Originally has %d ip fwd entries * "%len(self.fwd_table)
n = compress_ip_list(self.fwd_table)
print " * After compression has %d ip fwd entries * "%len(n)
self.fwd_table = n
'''
for elem in n:
str = "%s/%d: action: %s compressing: "%(int_to_dotted_ip(elem[0]) , elem[1], elem[2])
for e in elem[3]:
str = str + int_to_dotted_ip(e[0]) + "/%d, "%e[1]
print str
'''
print "=== DONE forwarding table compression ==="
[docs]def ofp_match_to_hsa_match(ofp_match):
hsa_match = byte_array_get_all_x(hs_format["length"]*2)
def field_wardcarded(ofp_match, flag):
if (ofp_match.wildcards & flag):
return True
return False
for field_name in set(ofp_match_data.keys()) - set(['in_port','dl_src','dl_dst','nw_src','nw_dst']):
flag = ofp_match_data[field_name][1]
if not field_wardcarded(ofp_match, flag):
set_field(hsa_match, field_name, getattr(ofp_match, field_name))
for field_name in ['dl_src', 'dl_dst']:
addr = getattr(ofp_match, field_name)
if addr is not None: # None addr implies wildcard
if type(addr) != EthAddr:
addr = EthAddr(addr).toInt()
else:
addr = addr.toInt()
#print "addr is: ", addr
set_field(hsa_match, field_name, addr)
for field_name in ['nw_src', 'nw_dst']:
(addr, mask_bits_from_left) = getattr(ofp_match, "get_%s"%field_name)()
if addr is not None: # None addr implies wildcard
# TODO: signed or unsigned?
if type(addr) == IPAddr:
addr = socket.htonl(addr.toUnsignedN())
# if addr, not all wildcard bits set
set_field(hsa_match, field_name, addr, right_mask=32-mask_bits_from_left)
return hsa_match
# Returns (mask, rewrite)
[docs]def ofp_actions_to_hsa_rewrite(ofp_actions):
# TODO: this should include the previous match by default? Is that already implemented by TF?
# Bits set to one are not touched
mask = byte_array_get_all_one(hs_format["length"]*2)
# Bits set to one are rewritten
rewrite = byte_array_get_all_zero(hs_format["length"]*2)
def set_vlan_id(action):
set_field(mask, "vlan", 0)
set_field(rewrite, "vlan", action.vlan_id)
def set_vlan_pcp(action):
set_field(mask, "vlan_pcp", 0)
set_field(rewrite, "vlan_pcp", action.vlan_pcp)
def strip_vlan(action):
# TODO: Is this a bug in software_switch? Two pcps...
set_field(mask, "vlan", 0)
set_field(rewrite, "vlan", 0)
def set_dl_src(action):
set_field(mask, "dl_src", 0)
set_field(rewrite, "dl_src", action.dl_addr)
def set_dl_dst(action):
set_field(mask, "dl_dst", 0)
set_field(rewrite, "dl_dst", action.dl_addr)
def set_nw_src(action):
set_field(mask, "nw_src", 0)
set_field(rewrite, "nw_src", action.nw_addr)
def set_nw_dst(action):
set_field(mask, "nw_dst", 0)
set_field(rewrite, "nw_dst", action.nw_addr)
def set_nw_tos(action):
set_field(mask, "nw_tos", 0)
set_field(rewrite, "nw_tos", action.nw_tos)
def set_tp_src(action):
set_field(mask, "tp_src", 0)
set_field(rewrite, "tp_src", action.tp_port)
def set_tp_dst(action):
set_field(mask, "tp_dst", 0)
set_field(rewrite, "tp_dst", action.tp_port)
handler_map = {
OFPAT_SET_VLAN_VID: set_vlan_id,
OFPAT_SET_VLAN_PCP: set_vlan_pcp,
OFPAT_STRIP_VLAN: strip_vlan,
OFPAT_SET_DL_SRC: set_dl_src,
OFPAT_SET_DL_DST: set_dl_dst,
OFPAT_SET_NW_SRC: set_nw_src,
OFPAT_SET_NW_DST: set_nw_dst,
OFPAT_SET_NW_TOS: set_nw_tos,
OFPAT_SET_TP_SRC: set_tp_src,
OFPAT_SET_TP_DST: set_tp_dst,
}
for action in ofp_actions:
if action.type in handler_map:
handler_map[action.type](action)
return (mask, rewrite)
[docs]def ofp_actions_to_output_ports(ofp_actions, switch, all_port_ids, in_port_id):
global out_port_nos
output_port_nos = []
def output_packet(action):
out_port = action.port
out_port_id = get_uniq_port_id(switch, out_port)
# The OF spec states that packets should not be forwarded out their
# in_port unless OFPP_IN_PORT is explicitly used.
if out_port_id == in_port_id:
return
if out_port < OFPP_MAX:
output_port_nos.append(out_port_id)
elif out_port == OFPP_IN_PORT:
output_port_nos.append(in_port_id)
elif out_port == OFPP_FLOOD or out_port == OFPP_ALL:
for port_id in all_port_ids:
if port_id != in_port_id:
output_port_nos.append(port_id)
elif out_port == OFPP_CONTROLLER:
return
else:
raise("Unsupported virtual output port: %x" % out_port)
handler_map = {
OFPAT_OUTPUT: output_packet,
OFPAT_ENQUEUE: output_packet,
}
for action in ofp_actions:
if action.type in handler_map:
handler_map[action.type](action)
return output_port_nos
[docs]def generate_transfer_function(tf, software_switch, ignore_lldp=True):
'''
The rules will be added to transfer function tf passed to the function.
'''
log.debug("=== Generating Transfer Function for switch %d ===" % software_switch.dpid)
# generate the forwarding part of transfer fucntion, from the fwd_prt, to pre-output ports
table = software_switch.table
all_port_ids = map(lambda port: get_uniq_port_id(software_switch, port), software_switch.ports.values())
for flow_entry in table.entries:
# TODO: For now, we're assuming completely non-overlapping entries. Need to
# deal with priorities properly!
ofp_match = flow_entry.match
if ignore_lldp and ofp_match.dl_type == ethernet.LLDP_TYPE:
continue
ofp_actions = flow_entry.actions
hsa_match = ofp_match_to_hsa_match(ofp_match)
input_port_ids = set(ofp_match_to_input_ports(ofp_match, software_switch, all_port_ids))
(mask, rewrite) = ofp_actions_to_hsa_rewrite(ofp_actions)
output_port_nos = set()
for input_port_id in input_port_ids:
output_port_nos = output_port_nos.union(ofp_actions_to_output_ports(ofp_actions, software_switch, all_port_ids, input_port_id))
if len(output_port_nos) == 0:
# No output ports means a dropped packet in OpenFlow
self_rule = TF.create_standard_rule(input_port_ids,hsa_match,[],None,None)
tf.add_fwd_rule(self_rule)
else:
tf_rule = TF.create_standard_rule(input_port_ids, hsa_match, output_port_nos, mask, rewrite)
if byte_array_equal(mask, all_one) and byte_array_equal(rewrite, all_zero):
tf.add_fwd_rule(tf_rule)
else:
tf.add_rewrite_rule(tf_rule)
log.debug("=== Successfully Generated Transfer function ===")
return 0
[docs]def get_kw_for_field_match(field_match):
# TODO(cs): nom_snapshot is not defined
type_to_name = {
nom_snapshot.Match.dl_src:"dl_src",
nom_snapshot.Match.dl_dst:"dl_dst",
nom_snapshot.Match.dl_vlan:"dl_vlan",
nom_snapshot.Match.dl_vlan_pcp:"dl_vlan_pcp",
nom_snapshot.Match.dl_type:"dl_type",
nom_snapshot.Match.nw_tos:"nw_tos",
nom_snapshot.Match.nw_proto:"nw_proto",
nom_snapshot.Match.nw_src:"nw_src",
nom_snapshot.Match.nw_dst:"nw_dst",
nom_snapshot.Match.tp_src:"tp_src",
nom_snapshot.Match.tp_dst:"tp_dst",
nom_snapshot.Match.in_port:"in_port",
nom_snapshot.Match.switch:"switch"
}
value = field_match.value
field_name = type_to_name[field_match.field]
if field_name == "dl_dst" or field_name == "dl_src":
value = "EthAddr(\"%s\")" % value
if field_name == "nw_dst" or field_name == "nw_src":
# Odd that ofp_match takes EthAddrs, but just strings for IpAddrs...
value = "\"%s\"" % value
return "%s=%s" % (field_name, value)
[docs]def tf_from_switch(ntf, switch, real_switch):
# clone the real switch, insert flow entries from policy, run
# generate_transfer_function()
dummy_switch = SoftwareSwitch(real_switch.dpid, ports=real_switch.ports.values())
for entry in switch.flow_table.entries:
dummy_switch.table.add_entry(entry)
return generate_transfer_function(ntf, dummy_switch)
[docs]def get_uniq_port_id(switch, port):
''' HSA assumes uniquely labeled ports '''
if type(switch) == int:
dpid = switch
else:
dpid = switch.dpid
if type(port) == int or type(port) == long:
port_no = port
else:
port_no = port.port_no
# The cisco config parser multiplies the dpid by 100000 and adds the port
# number. (The hassel-c code assumes this encoding)
return (dpid * 100000) + port_no