|
|---|
| How to write DML network models |
|
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 | ||
| ||
| 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:
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:
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:
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 [....]. | ||
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 [....]. 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. | ||
|
|