Skip to content

Understanding NetASM

Muhammad Shahbaz edited this page Jun 21, 2015 · 7 revisions

Understanding with HUB Example

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 and outport_bitmap are reserved fields.

Understanding with Learning Switch Example

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), and index_table with one row. You can think of the index_table as a register for maintaining the running eth_ table index. eth_match_table is of type CAM and has one field eth_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 the LD 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 the eth_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 the eth_params_table using the STt instruction, otherwise, we add the Ethernet source address and the input port at the index value in the eth_match_table and eth_params_table, respectively. After that we increment the index value and store it in the index_table. If the eth_match_table is full, we reset the index value to 0.

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 and eth_params_table). Otherwise, the default SEQ mode will cause conflicts and will corrupt the data.