-
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_bitmap
andoutport_bitmap
are 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_table
with one row. You can think of theindex_table
as a register for maintaining the runningeth_
table index.eth_match_table
is of typeCAM
and has one fieldeth_addr
for 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
ADD
instruction. Then we load these header fields with the content from the packet using theLD
instruction. - 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_table
to 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_table
using theSTt
instruction, otherwise, we add the Ethernet source address and the input port at theindex
value in theeth_match_table
andeth_params_table
, respectively. After that we increment theindex
value and store it in theindex_table
. If theeth_match_table
is full, we reset theindex
value 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
ATM
execution mode for grouping instructions for Ethernet lookup and learning processes as they are operating on shared state (i.e.,eth_match_table
andeth_params_table
). Otherwise, the defaultSEQ
mode will cause conflicts and will corrupt the data.