Scalable Simulation Framework
How to write DML network models

introductory tutorials
1.1 a simple two-host network: host configuration, random number streams
1.2 a simple two-host network again: use of attribute substitution
1.3 a simple two-host network once more: use of attribute inheritance
2 a simple two-router, four-host network: IP protocol and packet queues
advanced tutorials
3 a more complex network: topology specification, addressing and routing protocols
       3.1 a note about VLSM IP addressing (CIDR)
  3.2 a complete network with user-defined address aggregation
4 hierarchical composition of very large networks
 

You should have a general idea about the design of SSFNet, and about the syntax of DML configuration files before reading the DML network configurations in these tutorials.

The tutorials go step-by-step through several network configurations as of SSFNet 1.1. These pages contain many graphics, some of them large. More complicated network diagrams containing small details are also available in PDF format for printing and/or zooming in.

Simple network of two hosts

Open the configuration file client-server-1.dml now:

(will open in a new window) to see the entire DML configuration of the simple network shown on the left. Keep it side-by-side with the narrative below (we hope your screen is large enough, otherwise print it) to better follow the explanations.

We will spend quite a bit of time on this example, showing three equivalent ways of writing its DML configuration file:

  1. First, a fully expanded form.
  2. Then, we'll use the _find keyword to show how it can be written more compactly by substitution of attributes that repeat themselves.
  3. Finally, we will show how the use of inheritance keyword _extends makes the design even more compact.

The use of _find and _extends gives quite powerful object oriented capabilities to DML, as you will see in the much larger networks.

Below, we will explain the attributes one after another. You should also run this example and see the generated reports with different values of the parameters.

line-by-line explanation of client-server-1.dml
#  client-server-1.dml
  

everything on a line after the comment symbol "#" is ignored by DML parser.

Net [

  frequency 1000000000
  

Net: Every network configuration is simply the value of Net attribute, thus is enclosed within Net [....]. This will be used later to "import" entire networks into a model in one line of DML.

frequency sets the physical unit value of the simulated time resolution as the integer number of dimensionless simulator "clock ticks" per simulated second: in this model we have a 100 Mbs link meaning that nominally one bit is transmitted every 10 nanoseconds. Class SSF.Net.NIC (network interface card) computes the time needed to write a packet onto the link, thus needs time resolution at least of 10 nanoseconds, and our choice of 1 ns is safe and will not introduce timing errors. It is very important to set this parameter correctly.

In this model the simulated time resolution will be one nanosecond, that is the simulated time will advance by time intervals which are an integer number of nanoseconds.

  randomstream [
    generator "MersenneTwister"
    stream "seedstarter1"
    reproducibility_level "timeline"
  ]
  

randomstream attribute provides centralized control of the management and seeding of parallel random number streams, and degree of sharing of random number streams by consumers of the random numbers (i.e., RandomDistributions). This is critically important in a good quality simulation. In this example, random numbers are needed by TCP to randomize the offsets of TCP timers (TCP_SLOW_INTERVAL, TCP_FAST_INTERVAL) among hosts.

generator names one of highest quality random number generators distributed with SSFNet. The names you can choose are "MersenneTwister" and "Ranlux". You can also choose generator "Java" (which is java.util.Random).

stream value is an arbitrary user-defined string. The seeds for the random number generators are formed from such a root string, concatenated with other SSFNet-provided strings, using the cryptographic message digest techniques (currently the MD5 function). Changing this root string automatically and deterministically changes the seeds in all instantiated generators.

reproducibility_level controls how many separate instances of a random number generator will be created in the running model, and how the output of these generators will be shared by various consumers of random numbers. The name "reproducibility_level" refers to the fact that with this attribute a user may guarantee that the result of simulation will be exactly the same regardless of how many processors are used in a parallel simulation, or is willing to sacrifice that strict reproducibility for sake of performance (while all generators in SSFNet are fast, computing random numbers is not free and uses memory, and you may not want the penalty of having thousands of generator instances in a large model.)

The available string values of reproducibility_level in the order from most shared generators to least shared generators (one per instance of RandomDistribution) are:

  • timeline: all RandomDistributions instantiated in Entities aligned to a timeline share the same random number stream. Different timelines have independent and idependently seeded random number streams ( = generators). For a sequential execution (default, when no alignment attributes are used, as in this example) there will be just one instance of a named random number generator, whose output is shared by all instances of RandomDistributions (as said, in this tiny model they are buried in the TCP code).
  • host: all RandomDistributions instantiated in a Host share the same random number stream. Different Hosts have independent and idependently seeded random number streams ( = generators).
  • protocol: every instance of a ProtocolSession owns an independent random number stream. Different ProtocolSessions have independent and idependently seeded random number streams ( = generators).
  • distribution: every instance of a RandomDistributions owns an independent random number stream. Different RandomDistributions have independent and idependently seeded random number streams ( = generators).

See ssfnet/src/SSF/Util for the source code of random distributions. See the SSF.Net.Net source code for how the seeds are formed and distributed to individual generators.

  host [
    id 2
  
The attribute host begins the specification of a host machine. Every host (and router) within the enclosing Net must have a unique id number.
    interface [id 0 bitrate 100000000 latency 0.0]
  
This host has just one network interface. A router would have more such interface attributes, and each interface within a host must have a unique id. There are several attributes specifying the interface, see SSFNet DML Reference for a complete list. Here we specify that the interface will write packets onto the link at the rate of 100000000 bits/s = 100 Mbs, and that the interface itself will not introduce any processing delay (latency).
    graph [
      ProtocolSession [
        name client use SSF.OS.TCP.test.tcpClient
        start_time 1.0            # earliest time (seconds) to send one request to server
        start_window 1.0          # send request to server at randomly chosen time
                                  # in interval [start_time, start_time + start_window]
        file_size 10000000        # requested file size (number of "virtual" bytes)
        request_size  4           # client request datagram size (in bytes)
        show_report true          # print client-server session summary report, true or false
        debug false               # print verbose client/server diagnostics, true or false
      ] # end of tcpClient ProtocolSession
  
Above you see the beginning of a very important part: specification of protocols installed in this host, and of parameter settings for each protocol. Note that we begin with "highest" protocols; the reason for this is that initialization of a protocol may depend on protocols "below it", and indeed the class SSF.Net.Net initializes the protocols "from the bottom up".

graph refers to the ProtocolGraph - all protocols are installed within it. For users who think in terms of OSI layers, we point out that a collection of protocols in a host in general is NOT really a linear stack - it forms a directed acyclic graph (just notice e.g., that both TCP and UDP are "above" IP).

Attribute graph has within it a number of subattributes of the form

    ProtocolSession [
       name foo
       use SSF.OS.bar
       ...other protocol-dependent parameters
    ]
one per each installed protocol (we think of a client as an application-level protocol).

name is a symbolic tag, by which a protocol implementation finds its configuration parameters.

use names the SSFNet class that should be loaded to do the protocol's job. See the class SSF.OS.TCP.test.tcpClient: in it, look for method config(...) that actually reads all client's configuration parameters from DML.

The remaining attributes (from start_time to debug) are self-explanatory. A client will initiate a TCP connection to a server at a random time, send a small packet (whose payload is request_size bytes) specifying how many bytes of virtual data it requests from the server (file_size). The remaining two attributes control what will be printed on standard output when the simulation runs.

Below we continue with the rest of installed protocols.

      ProtocolSession [name socket use SSF.OS.Socket.socketMaster]
      ProtocolSession [name tcp use SSF.OS.TCP.tcpSessionMaster
        tcpinit[
          ISS 10000               # initial sequence number
          MSS 1000                # maximum segment size
          RcvWndSize  32          # receive buffer size
          SendWndSize 32          # maximum send window size
          SendBufferSize 128      # send buffer size
          MaxRexmitTimes 12       # maximum retransmission times before drop 
          TCP_SLOW_INTERVAL 0.5   # granularity of TCP slow timer
          TCP_FAST_INTERVAL 0.2   # granularity of TCP fast(delay-ack) timer
          MSL 60.0                # maximum segment lifetime
          MaxIdleTime 600.0       # maximum idle time for drop a connection
          delayed_ack false       # delayed ack option
          fast_recovery true      # implement fast recovery algorithm 
          show_report true        # print a summary connection report
        ]
      ]
      ProtocolSession [name ip use SSF.OS.IP]
    ] # end of graph
  ] # end of host
  

The next ProtocolSession is a BSD-like socket library. It is used to write all application-level protocols (meaning those using TCP or UDP for transport) and does not need any DML configuration parameters.

The next ProtocolSession is TCP, and here there are quite a few configuration parameters, all collected within the attribute tcpinit. They need a lot of explaining if you are not familiar with TCP, it is best to consult the SSF.OS.TCP documentation and validation pages for detailed information.

Lastly, we have the IP protocol.

Below we move on to the second host in the model: the server.

  host [
    id 1
    interface [id 0 bitrate 100000000 latency 0.0]
    graph [
    ProtocolSession [
      name server use SSF.OS.TCP.test.tcpServer
      port 1600               # server's well known port
      client_limit  10        # max number of contemporaneously allowed clients
                              # if omitted, default is no limit
      request_size  4         # client request datagram size (in bytes) - the same as in client
      show_report true        # print client-server session summary report
      debug false             # print verbose client/server diagnostics
    ]
    ProtocolSession [name socket use SSF.OS.Socket.socketMaster]
    ProtocolSession [name tcp use SSF.OS.TCP.tcpSessionMaster
      tcpinit[
        ISS 10000               # initial sequence number
        MSS 1000                # maximum segment size
        RcvWndSize  32          # receive buffer size
        SendWndSize 32          # maximum send window size
        SendBufferSize 128      # send buffer size
        MaxRexmitTimes 12       # maximum retransmission times before drop 
        TCP_SLOW_INTERVAL 0.5   # granularity of TCP slow timer
        TCP_FAST_INTERVAL 0.2   # granularity of TCP fast(delay-ack) timer
        MSL 60.0                # maximum segment lifetime
        MaxIdleTime 600.0       # maximum idle time for drop a connection
        delayed_ack false       # delayed ack option
        fast_recovery true      # implement fast recovery algorithm 
        show_report true        # print a summary connection report
      ]
    ]
    ProtocolSession [name ip use SSF.OS.IP]
  ]
  
As you see, the configuration of a server host is very similar to the client host. The differences are as follows:

A server loads a distinct application-level protocol:

    ProtocolSession [
      name server use SSF.OS.TCP.test.tcpServer
      port 1600               # server's well known port
      client_limit  10        # max number of contemporaneously allowed clients
                              # if omitted, default is no limit

Again, you may look up SSFNet class SSF.OS.TCP.test.tcpServer and it's method config() to see what it does when it reads the DML configuration parameters. The only difference in the DML fragment above is that a server must know its port number, and that we may wish to limit the number of clients that are simultaneously connected to the server (for instance, if we wished to run a denial of service attack scenario...).

  link [attach 1(0) attach 2(0) delay 0.002]
  
To model network topology we have the attribute link that represents level-2 connectivity, in this case just a point-to-point connection between the client and server hosts.

Each attribute attach specifies a network interface (of a host or of a router) attached to the link. The value of attach (like attach 1(0) above) is the topological (or NHI) address of the interface: here, 1(0) means "interface with id=0 in host with id=1".

delay is the link transmission delay with value in seconds: here, 0.002 s = 2 ms.

  traffic [
    pattern [
      client 2
      servers [nhi 1(0) port 1600]
    ]
  ]
  
] # end of Net
  
traffic is an interesting attribute: it is used to specify the global traffic scenarios for network models. All SSFNet 1.x application-level client protocols look up this attribute to find which servers they should connect to (there is a variant allowing an array of servers).

Within the traffic attribute, there may be an arbitrary number of pattern subattributes. (Of course we do not have any use for it in this 2-host example, but it becomes very useful for larger networks.)

Each pattern specifies two things:

  • client: The topological (NHI) address of a client or a group of clients, note that we ignore the interface id here.
  • servers: The topological (NHI) addresses of servers (including interface id, because the full NHI address has a unique IP address associated with it) and the port number.

You will see in more complex examples that each pattern may have many servers, and it is up to the client implementation which servers it will choose and at which times.

That concludes the explanation of the simplest client-server network client-server-1.dml.

Note one thing, however: in this example we didn't specify the IP addresses of the two hosts, although SSFNet.OS uses well-formed, realistic IP and TCP packet headers (see classes SSF.OS.IpHeader and SSF.OS.TCP.TCP_Header). The reason is that the default behavior of SSFNet is to allocate the IP addresses automatically at the beginning of a simulation, and the SSF.OS.IP protocol can figure out the IP network address on each of the host's interface attached to a link.


Simple network of two hosts again: use of _find

The example client-server-1.dml above is fairly straightforward, and you may ask why one would like to write it in any different way. The answer is that network configurations written in such a fully expanded style would not scale well to larger topologies, would not permit easy design reuse, and would be easy to make configuration errors.

Therefore, we will show next how to rewrite our simple example in a better way. For two hosts it is an academic exercise, but it illustrates important design principles. We want to solve two problems:

  1. In client-server-1.dml both the client and server must agree on their (trivial) request-reply protocol, namely what is the number of bytes in the client's request message (given by the attribute request_size). So far, this common information is in two separate places, once in the client and once in the server. It would be better to keep such shared information in one place, because if, say, modeler made an error and set request_size 4 in the client but request_size 64 in the server, the server's socket blocking read() method would wait for the missing bytes forever, and the TCP connection would time out.
  2. Another problem with client-server-1.dml is that both the client and the server use the same values of the TCP configuration attribute tcpinit, and it would be better to avoid lengthy repetitions to reduce the file size, as well as prevent possible errors as noted above.

Open the configuration file client-server-2.dml now

(will open in a new window), and keep it side-by-side with the narrative below (or print it) to better follow the explanations.

Observe that we have split the DML file into two top-level attributes: Net [....] and dictionary [....].
Net contains the network configuration as before, but the dictionary attribute serves as a catalogue of reusable attributes. Below we explain how this is interpreted by the DML database management utility that instantiates the model.

  host [
    id 2
    interface [id 0 bitrate 100000000 latency 0.0]
    graph [
      ProtocolSession [
        name client use SSF.OS.TCP.test.tcpClient
        start_time 1.0            # earliest time to send request to server
        start_window 1.0          # send request to server at randomly chosen time
                                  # in interval [start_time, start_time+start_window]
        file_size 10000000        # requested file size (payload bytes)
        _find .dictionary.appsession.request_size
        _find .dictionary.appsession.show_report
        _find .dictionary.appsession.debug
      ]
      ProtocolSession [name socket use SSF.OS.Socket.socketMaster]
      ProtocolSession [name tcp use SSF.OS.TCP.tcpSessionMaster
                       _find .dictionary.tcpinit]
      ProtocolSession [name ip use SSF.OS.IP]
    ]
  ]  
  
The line
        _find .dictionary.appsession.request_size
means: "replace this line with request_size followed by the value of the attribute .dictionary.appsession.request_size". We look it up in the file client-server-2.dml, where we see:
dictionary[

  # the client-server protocol needs to agree on some things
  
  appsession [
    request_size  4         # client request datagram size (in bytes)
    show_report true        # print client-server session summary report
    debug false             # print verbose client/server diagnostics
  ]
thus "_find .dictionary.appsession.request_size" is equivalent to "request_size 4" .

The other instances of _find in the DML fragment above get expanded in the analogous way.

The example of using _find above showed you how to reuse the whole attributes (i.e., key-value pairs) but this facility is not powerful enough to reuse more complex designs.


Simple network of two hosts once again: use of inheritance

Open the configuration file client-server-3.dml

(the third variant of client/server), and keep it side-by-side with the narrative below.

As before, we have split the DML file into two top-level attributes: Net [....] and dictionary [....].
Net contains the network configuration, and the dictionary attribute serves as a catalogue of reusable attributes.

Look how brief is the network configuration itself if we use inheritance:

Net [
  frequency 1000000000    # 1 nanosecond time resolution
  
  randomstream [
    generator "MersenneTwister"
    stream "seedstarter1"
    reproducibility_level "timeline"
  ]

  host [
    id 2
    interface [id 0 bitrate 100000000 latency 0.0]
    _extends .dictionary.typicalClient
  ]
      
  host [
    id 1
    interface [id 0 bitrate 100000000 latency 0.0]
    _extends .dictionary.typicalServer
  ]
  
  link [attach 1(0) attach 2(0) delay 0.002]

  traffic [
    pattern [
      client 2
      servers [nhi 1(0) port 1600]
    ]
  ]

] # end of Net
  

The attribute "_extends keypath" implements multiple inheritance with strict ordering. See the DML Reference Manual for a full explanation.

In the code fragment above, the line

    _extends .dictionary.typicalClient
is equivalent to the value of the attribute .dictionary.typicalClient, which you can look up in the file client-server-3.dml. Put this value in place of the _extends attribute, to see the fully expanded client host:
  host [
    id 2
    interface [id 0 bitrate 100000000 latency 0.0]
    graph [
      ProtocolSession [
        name client use SSF.OS.TCP.test.tcpClient
        start_time 1.0            # earliest time to send request to server
        start_window 1.0          # send request to server at randomly chosen time
                                  # in interval [start_time, start_time+start_window]
        file_size 10000000        # requested file size (payload bytes)
        _find .dictionary.appsession.request_size
        _find .dictionary.appsession.show_report
        _find .dictionary.appsession.debug
      ]
      ProtocolSession [name socket use SSF.OS.Socket.socketMaster]
      ProtocolSession [name tcp use SSF.OS.TCP.tcpSessionMaster
                       _find .dictionary.tcpinit]
      ProtocolSession [name ip use SSF.OS.IP]
    ]
  ]
  

In this sense, the client host literally inherits all attributes from typicalClient, and has two other attributes of its own (id, interface) that specialize the generic typicalClient.

We note here that he DML code may actually use _extends multiple times per attribute, and keys in the extended attribute may overlap with keys in the extending attribute. Order of same-name attributes is the same as that defined by _find, i.e., the imported attributes appear as if they had been defined directly at the position of _extends.

Finally, note that DML is extensible - you may define your own attributes to better organize the configuration. For instance, in the 3rd example above, we defined the internal attribute typicalClient (and similarly typicalServer) in order to enable inheritance of its sub-attributes.

 

continue the tutorial...