-
Notifications
You must be signed in to change notification settings - Fork 10
Understanding NetASM
Let’s take a look at the Hub example in detail.
The listing below shows the program for the Hub example written in NetASM.
from netasm.netasm.core import *
def main():
decls = Decls(TableDecls())
PORT_COUNT_BITMAP = 0xFFFF # mean [... bit(1): port_2, bit(0): port_1]
code = I.Code(
Fields(),
I.Instructions(
I.OP(
O.Field(Field('outport_bitmap')),
O.Field(Field('inport_bitmap')),
Op.Xor,
O.Value(Value(PORT_COUNT_BITMAP, Size(16))),
),
I.HLT()
)
)
return Policy(decls, code)Almost every program in NetASM, starts by importing netasm.netasm.core module. It contains definitions of the syntax and semantics of the NetASM language.
The code shown in the latter half of the program above, is a tuple consisting of Fields (serving as code arguments) and Instructions (the actual assembly code). The code has two instructions: OP and HLT. The OP instruction performs an Xor operation on the inport_bitmap field with PORT_COUNT_BITMAP and stores its result in the outport_bitmap field of the header. The Xor operation sets all bits of the outport_bitmap field to 1s except the bit for the incoming port, effectively performing a flood operation. A brief description and usage of OP and HLT instructions are provided here.
Note: both
inport_bitmapandoutport_bitmapare reserved fields.
Now let’s take a look at a relatively more complex example, a stateful learning switch. (“Stateful” indicates that the state elements i.e., tables, are updated locally by the switch without the intervention of the remote controller.) The code listing below shows the program for the stateful learning example written in NetASM.
from netasm.netasm.core import *
def main():
# Constants
PORT_COUNT_BITMAP = 0xFFFF # means [... bit(1): port_2, bit(0): port_1]
# Declarations
decls = Decls(TableDecls())
# Tables
# Ethernet address table
MAC_TABLE_SIZE = Size(16)
decls.table_decls[TableId('eth_match_table')] = \
Table(TableFieldsCollection.MatchFields(),
MAC_TABLE_SIZE,
TableTypeCollection.CAM)
match_table = decls.table_decls[TableId('eth_match_table')]
match_table.table_fields[Field('eth_addr')] = Size(48), MatchTypeCollection.Binary
decls.table_decls[TableId('eth_params_table')] = \
Table(TableFieldsCollection.SimpleFields(),
MAC_TABLE_SIZE,
TableTypeCollection.RAM)
params_table = decls.table_decls[TableId('eth_params_table')]
params_table.table_fields[Field('outport_bitmap')] = Size(3)
# Index address table
INDEX_TABLE_SIZE = Size(1)
decls.table_decls[TableId('index_table')] = \
Table(TableFieldsCollection.SimpleFields(),
INDEX_TABLE_SIZE,
TableTypeCollection.RAM)
index_table = decls.table_decls[TableId('index_table')]
index_table.table_fields[Field('index')] = Size(16)
# Code
code = I.Code(
##################
### Arguments ####
##################
Fields(),
##################
## Instructions ##
##################
I.Instructions(
##################
## Parse packet ##
##################
# Add ethernet header fields in the header set
I.ADD(O.Field(Field('eth_dst')),
Size(48)),
I.ADD(O.Field(Field('eth_src')),
Size(48)),
I.ADD(O.Field(Field('eth_type')),
Size(16)),
# Load fields with default values
I.LD(O.Field(Field('eth_dst')),
O.Value(Value(0, Size(48)))),
I.LD(O.Field(Field('eth_src')),
O.Value(Value(0, Size(48)))),
I.LD(O.Field(Field('eth_type')),
O.Value(Value(0, Size(16)))),
# Parse ethernet
# load ethernet header fields from the packet
I.LD(O.Field(Field('eth_dst')),
O.Location(
Location(
O.Value(Value(0, Size(16)))))),
I.LD(O.Field(Field('eth_src')),
O.Location(
Location(
O.Value(Value(48, Size(16)))))),
I.LD(O.Field(Field('eth_type')),
O.Location(
Location(
O.Value(Value(96, Size(16)))))),
########################
## Lookup MAC address ##
########################
# Add the following header fields in the header set
I.ADD(O.Field(Field('index')),
Size(16)),
I.ATM(
I.Code(
Fields(Field('index'), Field('eth_dst'), Field('eth_src')),
I.Instructions(
# Lookup in the match table and store the matched index
I.LKt(O.Field(Field('index')),
TableId('eth_match_table'),
O.Operands_(
O.Field(Field('eth_dst')))),
I.BR(O.Field(Field('index')),
Op.Neq,
O.Value(Value(-1, Size(16))),
Label('LBL_LKP_0')),
# Case: there is no match in the match table
# Broadcast the packet
I.OP(
O.Field(Field('outport_bitmap')),
O.Field(Field('inport_bitmap')),
Op.Xor,
O.Value(Value(PORT_COUNT_BITMAP, Size(16))),
),
I.JMP(Label('LBL_LRN')),
# Case: there is a match in the l2 match table
I.LBL(Label('LBL_LKP_0')),
# Load output port from the parameters table
I.LDt(
O.Operands__(
O.Field(Field('outport_bitmap'))),
TableId('eth_params_table'),
O.Field(Field('index'))),
#######################
## Learn MAC address ##
#######################
I.LBL(Label('LBL_LRN')),
# Lookup in the match table and store the matched index
I.LKt(O.Field(Field('index')),
TableId('eth_match_table'),
O.Operands_(
O.Field(Field('eth_src')))),
I.BR(O.Field(Field('index')),
Op.Neq,
O.Value(Value(-1, Size(16))),
Label('LBL_LRN_0')),
# Case: there is no match in the match table
# Read the running index from the index table
I.LDt(
O.Operands__(
O.Field(Field('index'))),
TableId('index_table'),
O.Value(Value(0, Size(1)))),
# Store eth_src in the eth_match_table
I.STt(TableId('eth_match_table'),
O.Field(Field('index')),
O.OperandsMasks_(
(O.Field(Field('eth_src')), Mask(0xFFFFFFFFFFFF)))),
# Store inport_bitmap in the eth_params_table
I.STt(TableId('eth_params_table'),
O.Field(Field('index')),
O.Operands_(
O.Field(Field('inport_bitmap')))),
# Increment the running index
I.OP(
O.Field(Field('index')),
O.Field(Field('index')),
Op.Add,
O.Value(Value(1, Size(16))),
),
# Check if the index is less than the MAC_TABLE_SIZE
I.BR(O.Field(Field('index')),
Op.Lt,
O.Value(Value(MAC_TABLE_SIZE, Size(16))),
Label('LBL_LRN_1')),
# Reset the running index
I.LD(O.Field(Field('index')),
O.Value(Value(0, Size(16)))),
# Store the running index back in the table
I.LBL(Label('LBL_LRN_1')),
I.STt(TableId('index_table'),
O.Value(Value(0, Size(1))),
O.Operands_(
O.Field(Field('index')))),
I.JMP(Label('LBL_HLT')),
# Store the current inport_bitmap in the eth_params_table
I.LBL(Label('LBL_LRN_0')),
I.STt(TableId('eth_params_table'),
O.Field(Field('index')),
O.Operands_(
O.Field(Field('inport_bitmap')))),
# Halt
I.LBL(Label('LBL_HLT')),
I.HLT()
)
)
),
##########
## Halt ##
##########
I.LBL(Label('LBL_HLT')),
I.HLT()
)
)
return Policy(decls, code)Recall that in a learning switch, the destination Ethernet address of the incoming packet is matched against the Ethernet table, populated with source Ethernet addresses the switch has already seen. If the destination address of the packet is in the table, the switch updates the packet header with the corresponding output port from the Outport table, otherwise, it floods the packet. Ethernet and Outport tables are then updated with the source Ethernet address and input port of the incoming packet.
The code listing above describes a switch layout that implements such a learning switch.
-
First, we create three tables:
eth_match_table(for storing Ethernet addresses),eth_params_table(for storing output port for a given Ethernet address), andindex_tablewith one row. You can think of theindex_tableas a register for maintaining the runningeth_table index.eth_match_tableis of typeCAMand has one fieldeth_addrfor performing an exact or binary match. -
Second, we write instructions to implement the learning switch.
- In the first half of the instructions, we parse the packet. First we add fields that we need for packet processing (i.e., Ethernet header fields) using the
ADDinstruction. Then we load these header fields with the content from the packet using theLDinstruction. - In the second half, we perform Ethernet lookup and learning.
- We perform a lookup for the Ethernet destination address using the LKt instruction on the
eth_match_tableto see if there is a match. If a match is found, we read the corresponding output port from theeth_params_table, otherwise, we flood the packet. We then jump to the learning process. - We again perform a lookup on the
eth_match_table, this time for the Ethernet source address. If a match is found, we write the input port for the current packet in theeth_params_tableusing theSTtinstruction, otherwise, we add the Ethernet source address and the input port at theindexvalue in theeth_match_tableandeth_params_table, respectively. After that we increment theindexvalue and store it in theindex_table. If theeth_match_tableis full, we reset theindexvalue to 0.
- We perform a lookup for the Ethernet destination address using the LKt instruction on the
- In the first half of the instructions, we parse the packet. First we add fields that we need for packet processing (i.e., Ethernet header fields) using the
Note: we have used the
ATMexecution mode for grouping instructions for Ethernet lookup and learning processes as they are operating on shared state (i.e.,eth_match_tableandeth_params_table). Otherwise, the defaultSEQmode will cause conflicts and will corrupt the data.