Commit d3274468 authored by Shashi Guruprasad's avatar Shashi Guruprasad

Simulation/Emulation Transparency integration

parent b0b2497b
...@@ -9,7 +9,8 @@ SUBDIR = tbsetup/ns2ir ...@@ -9,7 +9,8 @@ SUBDIR = tbsetup/ns2ir
include $(OBJDIR)/Makeconf include $(OBJDIR)/Makeconf
LIB_STUFF = lanlink.tcl node.tcl sim.tcl tb_compat.tcl null.tcl \ LIB_STUFF = lanlink.tcl node.tcl sim.tcl tb_compat.tcl null.tcl \
nsobject.tcl traffic.tcl vtype.tcl parse.tcl program.tcl nsobject.tcl traffic.tcl vtype.tcl parse.tcl program.tcl \
nsenode.tcl nstb_compat.tcl
LIBEXEC_STUFF = parse-ns LIBEXEC_STUFF = parse-ns
# #
......
...@@ -52,6 +52,18 @@ Node instproc init {s} { ...@@ -52,6 +52,18 @@ Node instproc init {s} {
$self set tarfiles "" $self set tarfiles ""
$self set failureaction "fatal" $self set failureaction "fatal"
$self set fixed "" $self set fixed ""
$self set nseconfig ""
var_import ::GLOBALS::simulated
var_import ::GLOBALS::curnsenode
if { $simulated == 1 } {
$self set simulated 1
$self set nsenode $curnsenode
} else {
$self set simulated 0
$self set nsenode ""
}
$self set nsenode_vport ""
} }
# The following procs support renaming (see README) # The following procs support renaming (see README)
...@@ -94,10 +106,17 @@ Node instproc updatedb {DB} { ...@@ -94,10 +106,17 @@ Node instproc updatedb {DB} {
$self instvar agentlist $self instvar agentlist
$self instvar routelist $self instvar routelist
$self instvar sim $self instvar sim
$self instvar simulated
$self instvar nseconfig
var_import ::GLOBALS::pid var_import ::GLOBALS::pid
var_import ::GLOBALS::eid var_import ::GLOBALS::eid
var_import ::GLOBALS::default_ip_routing_type var_import ::GLOBALS::default_ip_routing_type
# currently we don't want to update the DB for simulated nodes
if { $simulated == 1 } {
return
}
# If we haven't specified a osid so far then we should fill it # If we haven't specified a osid so far then we should fill it
# with the id from the node_types table now. # with the id from the node_types table now.
if {$osid == {}} { if {$osid == {}} {
...@@ -114,15 +133,21 @@ Node instproc updatedb {DB} { ...@@ -114,15 +133,21 @@ Node instproc updatedb {DB} {
incr i incr i
} }
set nseconfig ""
foreach agent $agentlist { foreach agent $agentlist {
$agent updatedb $DB $agent updatedb $DB
append nseconfig [$agent get_nseconfig] append nseconfig [$agent get_nseconfig]
} }
if {$nseconfig != {}} { if {$nseconfig != {}} {
set nsecfg_script ""
set simu [lindex [Simulator info instances] 0]
append nsecfg_script "set $simu \[new Simulator]\n"
append nsecfg_script "\$$simu use-scheduler RealTime\n\n"
append nsecfg_script $nseconfig
# update the per-node nseconfigs table in the DB # update the per-node nseconfigs table in the DB
sql exec $DB "insert into nseconfigs (pid,eid,vname,nseconfig) values ('$pid','$eid','$self','$nseconfig')"; sql exec $DB "insert into nseconfigs (pid,eid,vname,nseconfig) values ('$pid','$eid','$self','$nsecfg_script')";
} }
$self add_routes_to_DB $DB $self add_routes_to_DB $DB
......
######################################################################
# nsenode.tcl
#
# This defines the NSENode class. Instances of this class are created by
# the 'nsenode' method of Simulator.
# This node supports a method make-simulated and when invoked with
# code in-between {} as an argument, treats the code as simulated
# and runs NSE on the physical NSENode with the provided script
######################################################################
Class NSENode -superclass Node
# need to have a single namespace for simulated node names
# lets have simulated node -> nsenode map
# need to support simulated to real node links, simulated to simulated
# across nse nodes => we need a duplex-link specialization even in
# child interpreters
# routing must be
# manually enabled so that a RAW IP packet written should automatically
# make it out
NSENode instproc init {s} {
set new_classes(NSENode) {}
$self next $s
$self set nseconfig ""
$self set simcode_present 0
}
NSENode instproc make-simulated {args} {
var_import ::GLOBALS::simulated
var_import ::GLOBALS::curnsenode
$self instvar nseconfig
$self instvar simcode_present
set simulated 1
set curnsenode $self
global script
set script [string trim $args "\{\}"]
if { $script == {} } {
set simulated 0
set curnsenode ""
return
}
set simcode_present 1
# we ignore any type of errors coz they have
# been caught when we ran the script through NSE
catch {uplevel 1 eval $script}
append nseconfig $script
set simulated 0
set curnsenode ""
}
NSENode instproc updatedb {DB} {
$self instvar nseconfig
$self instvar simcode_present
# for all the simulated nodes, we need to find out if
# they are connected to a real node and if so, find the
# ip address of the port on nsenode
foreach node [Node info instances] {
if { [$node set simulated] == 1 && [$node set nsenode] == $self } {
append nseconfig "\n\n\$$node set simulated 1\n"
append nseconfig "\$$node set nsenode $self\n"
set nsenode_vport [$node set nsenode_vport]
if { $nsenode_vport != {} } {
append nseconfig "\$$node set nsenode_vport $nsenode_vport\n"
append nseconfig "\$$node set nsenode_ipaddr [$self ip $nsenode_vport]\n\n"
}
}
}
if { $simcode_present == 1 } {
append nseconfig "set simcode_present $simcode_present\n\n"
}
# NSE code runs only FreeBSD as of now
$self set osid "FBSD-STD"
$self next $DB
}
# This is a nop tb_compact.tcl file that should be used when running scripts
# under ns.
proc tb-set-ip {node ip} {}
proc tb-set-ip-interface {src dst ip} {}
proc tb-set-ip-link {src link ip} {}
proc tb-set-ip-lan {src lan ip} {}
proc tb-set-hardware {node type args} {}
proc tb-set-node-os {node os} {}
proc tb-set-link-loss {src args} {}
proc tb-set-lan-loss {lan rate} {}
proc tb-set-node-rpms {node args} {}
proc tb-set-node-startup {node cmd} {}
proc tb-set-node-cmdline {node cmd} {}
proc tb-set-node-deltas {node args} {}
proc tb-set-node-tarfiles {node args} {}
proc tb-set-node-lan-delay {node lan delay} {}
proc tb-set-node-lan-bandwidth {node lan bw} {}
proc tb-set-node-lan-loss {node lan loss} {}
proc tb-set-node-lan-params {node lan delay bw loss} {}
proc tb-set-node-failure-action {node type} {}
proc tb-set-ip-routing {type} {}
proc tb-fix-node {v p} {}
proc tb-make-weighted-vtype {name weight types} {}
proc tb-make-soft-vtype {name types} {}
proc tb-make-hard-vtype {name types} {}
proc tb-set-lan-simplex-params {lan node todelay tobw toloss fromdelay frombw fromloss} {}
proc tb-set-link-simplex-params {link src delay bw loss} {}
Class Program
Program instproc unknown {m args} {
}
Class NSENode -superclass Node
NSENode instproc make-simulated {args} {
uplevel 1 eval $args
}
# We are just syntax checking the NS file
Simulator instproc run {args} {
}
Simulator instproc nsenode {args} {
return [new NSENode]
}
...@@ -9,10 +9,11 @@ ...@@ -9,10 +9,11 @@
Class NullClass Class NullClass
NullClass instproc init {mytype} { NullClass instproc init {mtype} {
$self set type $mytype $self set type $mtype
} }
NullClass instproc unknown {m args} { NullClass instproc unknown {m args} {
$self instvar type
punsup "$type $m" punsup "$type $m"
} }
\ No newline at end of file
...@@ -56,6 +56,15 @@ proc var_import {varspec} { ...@@ -56,6 +56,15 @@ proc var_import {varspec} {
### ###
proc perror {msg} { proc perror {msg} {
var_import ::GLOBALS::errors var_import ::GLOBALS::errors
var_import ::GLOBALS::simulated
# If this was a true error in specifying
# the simulation, it would have been
# caught when run with NSE
if { $simulated == 1 } {
return 0
}
global argv0 global argv0
puts stderr "*** $argv0: " puts stderr "*** $argv0: "
puts stderr " $msg" puts stderr " $msg"
...@@ -69,7 +78,12 @@ proc perror {msg} { ...@@ -69,7 +78,12 @@ proc perror {msg} {
proc punsup {msg} { proc punsup {msg} {
var_import ::GLOBALS::verbose var_import ::GLOBALS::verbose
var_import ::GLOBALS::WARN_FILE var_import ::GLOBALS::WARN_FILE
if {$verbose == 1} { var_import ::GLOBALS::simulated
# If this was a true error in specifying
# the simulation, it would have been
# caught when run with NSE
if {$simulated == 0 && $verbose == 1} {
puts stderr "*** WARNING: Unsupported NS Statement!" puts stderr "*** WARNING: Unsupported NS Statement!"
puts stderr " $msg" puts stderr " $msg"
puts $WARN_FILE "*** WARNING: Unsupported NS Statement!" puts $WARN_FILE "*** WARNING: Unsupported NS Statement!"
...@@ -143,6 +157,9 @@ namespace eval GLOBALS { ...@@ -143,6 +157,9 @@ namespace eval GLOBALS {
# 'source tb_compat.tcl' statement succeeds. # 'source tb_compat.tcl' statement succeeds.
variable tbcompat "$libdir/tb_compat.tcl" variable tbcompat "$libdir/tb_compat.tcl"
# This is used in running the script through nse for syntax errors
variable nstbcompat "$libdir/nstb_compat.tcl"
# Is 1 if any errors have occured so far. # Is 1 if any errors have occured so far.
variable errors 0 variable errors 0
...@@ -161,6 +178,15 @@ namespace eval GLOBALS { ...@@ -161,6 +178,15 @@ namespace eval GLOBALS {
# This is the file handler for the warnings file # This is the file handler for the warnings file
variable WARN_FILE [open "$eid.warnings" w] variable WARN_FILE [open "$eid.warnings" w]
# This distinguishes whether the script that
# is being parsed should go into a NSE simulation or not
variable simulated 0
# If simulated is 1, then curnsenode gives us the name
# of the physical node on which the simulation script
# should go into
variable curnsenode ""
} }
# Connect to the DB # Connect to the DB
...@@ -187,6 +213,7 @@ source ${GLOBALS::libdir}/nsobject.tcl ...@@ -187,6 +213,7 @@ source ${GLOBALS::libdir}/nsobject.tcl
source ${GLOBALS::libdir}/sim.tcl source ${GLOBALS::libdir}/sim.tcl
source ${GLOBALS::libdir}/lanlink.tcl source ${GLOBALS::libdir}/lanlink.tcl
source ${GLOBALS::libdir}/node.tcl source ${GLOBALS::libdir}/node.tcl
source ${GLOBALS::libdir}/nsenode.tcl
source ${GLOBALS::libdir}/null.tcl source ${GLOBALS::libdir}/null.tcl
source ${GLOBALS::libdir}/traffic.tcl source ${GLOBALS::libdir}/traffic.tcl
source ${GLOBALS::libdir}/vtype.tcl source ${GLOBALS::libdir}/vtype.tcl
...@@ -289,7 +316,7 @@ proc new {class args} { ...@@ -289,7 +316,7 @@ proc new {class args} {
if {! [info exists new_classes($class)]} { if {! [info exists new_classes($class)]} {
punsup "Object: $class" punsup "Object: $class"
set id null[incr new_counter] set id null[incr new_counter]
NullClass $id NullClass $id $class
return $id return $id
} }
...@@ -364,16 +391,25 @@ proc parse_delay {dspec} { ...@@ -364,16 +391,25 @@ proc parse_delay {dspec} {
# We now have all our infrastructure in place. We are ready to load # We now have all our infrastructure in place. We are ready to load
# the NS file. # the NS file.
file copy -force ${GLOBALS::tbcompat} . # We first run the script through NSE to check syntax errors
source ${GLOBALS::nsfile} file copy -force ${GLOBALS::nstbcompat} tb_compat.tcl
if { [catch {exec nse ${GLOBALS::nsfile}}] == 1 } {
perror "Syntax check with NS failed\nerrorcode:$errorCode"
}
# Clean up if { ${GLOBALS::errors} != 1 } {
sql disconnect ${GLOBALS::DB} file copy -force ${GLOBALS::tbcompat} .
source ${GLOBALS::nsfile}
if {${GLOBALS::ran} == 0} {
perror "No 'Simulator run' statement found."
}
if {${GLOBALS::ran} == 0} {
perror "No 'Simulator run' statement found."
} }
# Clean up
sql disconnect ${GLOBALS::DB}
close ${GLOBALS::WARN_FILE} close ${GLOBALS::WARN_FILE}
exit ${GLOBALS::errors} exit ${GLOBALS::errors}
...@@ -48,6 +48,13 @@ Program instproc updatedb {DB} { ...@@ -48,6 +48,13 @@ Program instproc updatedb {DB} {
return return
} }
sql exec $DB "insert into virt_agents (pid,eid,vnode,vname,objecttype) values ('$pid','$eid','$node','$self','$objtypes(PROGRAM)')"; set progvnode $node
# if the attached node is a simulated one, we attach the
# program to the physical node on which the simulation runs
if { [$node set simulated] == 1 } {
set progvnode [$node set nsenode]
}
sql exec $DB "insert into virt_agents (pid,eid,vnode,vname,objecttype) values ('$pid','$eid','$prognode','$self','$objtypes(PROGRAM)')";
} }
...@@ -51,6 +51,15 @@ Simulator instproc init {args} { ...@@ -51,6 +51,15 @@ Simulator instproc init {args} {
# Program list. # Program list.
$self instvar prog_list; $self instvar prog_list;
array set prog_list {} array set prog_list {}
var_import ::GLOBALS::last_class
set last_class $self
}
# renaming the simulator instance
# needed to find the name of the instance
# for use in NSE code
Simulator instproc rename {old new} {
} }
# node # node
...@@ -58,6 +67,8 @@ Simulator instproc init {args} { ...@@ -58,6 +67,8 @@ Simulator instproc init {args} {
# of the node. # of the node.
Simulator instproc node {args} { Simulator instproc node {args} {
var_import ::GLOBALS::last_class var_import ::GLOBALS::last_class
var_import ::GLOBALS::simulated
var_import ::GLOBALS::curnsenode
$self instvar id_counter $self instvar id_counter
$self instvar node_list $self instvar node_list
...@@ -67,18 +78,53 @@ Simulator instproc node {args} { ...@@ -67,18 +78,53 @@ Simulator instproc node {args} {
set curnode n[incr id_counter] set curnode n[incr id_counter]
Node $curnode $self Node $curnode $self
set node_list($curnode) {}
# not adding simulated nodes to the node_list
if { $simulated == 0 } {
set node_list($curnode) {}
}
set last_class $curnode set last_class $curnode
return $curnode return $curnode
} }
# nsenode
# this method does pretty much what the node proc does
# with some XXXXX
Simulator instproc nsenode {args} {
var_import ::GLOBALS::last_class
var_import ::GLOBALS::simulated
var_import ::GLOBALS::curnsenode
$self instvar id_counter
$self instvar node_list
if {($args != {})} {
punsup "Arguments for node: $args"
}
set curnode n[incr id_counter]
NSENode $curnode $self
# not adding simulated nodes to the node_list
if { $simulated == 0 } {
set node_list($curnode) {}
} else {
$curnode set simulated 1
$curnode set nsenode $curnsenode
}
set last_class $curnode
return $curnode
}
# duplex-link <node1> <node2> <bandwidth> <delay> <type> # duplex-link <node1> <node2> <bandwidth> <delay> <type>
# This adds a new link to the topology. <bandwidth> can be in any # This adds a new link to the topology. <bandwidth> can be in any
# form accepted by parse_bw and <delay> in any form accepted by # form accepted by parse_bw and <delay> in any form accepted by
# parse_delay. Currently only the type 'DropTail' is supported. # parse_delay. Currently only the type 'DropTail' is supported.
Simulator instproc duplex-link {n1 n2 bw delay type args} { Simulator instproc duplex-link {n1 n2 bw delay type args} {
var_import ::GLOBALS::last_class var_import ::GLOBALS::last_class
var_import ::GLOBALS::simulated
var_import ::GLOBALS::curnsenode
$self instvar id_counter $self instvar id_counter
$self instvar lanlink_list $self instvar lanlink_list
$self instvar link_map $self instvar link_map
...@@ -97,17 +143,58 @@ Simulator instproc duplex-link {n1 n2 bw delay type args} { ...@@ -97,17 +143,58 @@ Simulator instproc duplex-link {n1 n2 bw delay type args} {
} }
if {$error} {return} if {$error} {return}
set curlink l[incr id_counter]
# Convert bandwidth and delay # Convert bandwidth and delay
set rbw [parse_bw $bw] set rbw [parse_bw $bw]
set rdelay [parse_delay $delay] set rdelay [parse_delay $delay]
Link $curlink $self "$n1 $n2" $rbw $rdelay $type
set lanlink_list($curlink) {}
set link_map($n1:$n2) $curlink
set link_map($n2:$n1) $curlink
set n1sim [$n1 set simulated]
set n2sim [$n2 set simulated]
set n1node $n1
set n2node $n2
set simnode ""
# If one of the nodes is a real node, we
# create a link between the real node and
# the physical node corresponding to the
# simulated node. If both are simulated
# nodes, we don't need to do anything
if { $n1sim == 1 && $n2sim == 0 } {
set simnode $n1
set n1node [$n1 set nsenode]
} elseif { $n1sim == 0 && $n2sim == 1 } {
set simnode $n2
set n2node [$n2 set nsenode]
} elseif { $n1sim == 1 && $n2sim == 1 } {
return ""
}
# At this point we have one of the nodes of
# the link to be real. We need to make sure
# that this is not being defined in make-simulated.
# In other words links or lans from real nodes and
# simulated nodes should happen outside make-simulated
if { $simulated == 1 } {
set simulated 0
perror "Please define links between real and simulated nodes outside make-simulated"
set simulated 1
return ""
}
set curlink l[incr id_counter]
Link $curlink $self "$n1node $n2node" $rbw $rdelay $type
set lanlink_list($curlink) {}
set link_map($n1node:$n2node) $curlink
set link_map($n2node:$n1node) $curlink
# get the vport number on nsenode on which we make
# the link and store it in the simulated node that
# goes into NSE
if { $simnode != {} } {
set vport [[$simnode set nsenode] find_port $curlink]
$simnode set nsenode_vport $vport
}
set last_class $curlink set last_class $curlink
return $curlink return $curlink
} }
...@@ -118,24 +205,65 @@ Simulator instproc duplex-link {n1 n2 bw delay type args} { ...@@ -118,24 +205,65 @@ Simulator instproc duplex-link {n1 n2 bw delay type args} {
# parse_delay. # parse_delay.
Simulator instproc make-lan {nodelist bw delay args} { Simulator instproc make-lan {nodelist bw delay args} {
var_import ::GLOBALS::last_class var_import ::GLOBALS::last_class
var_import ::GLOBALS::simulated
var_import ::GLOBALS::curnsenode
$self instvar id_counter $self instvar id_counter
$self instvar lanlink_list $self instvar lanlink_list
if {($args != {})} { if {($args != {})} {
punsup "Arguments for make-lan: $args" punsup "Arguments for make-lan: $args"
} }
set modified_nodelist ""
set realnode_present 0
set simnode_list ""
foreach node $nodelist {
if { [$node set simulated] == 1 } {
append modified_nodelist [$node set nsenode]
append simnode_list $node
} else {
append modified_nodelist $node
set realnode_present 1
}
}
set curlan lan[incr id_counter] if { $realnode_present == 1 } {
# Convert bandwidth and delay
set rbw [parse_bw $bw] # At this point we have one of the nodes of
set rdelay [parse_delay $delay] # the lan to be real. We need to make sure
# that this is not being defined in make-simulated.
Lan $curlan $self $nodelist $rbw $rdelay {} # In other words links or lans from real nodes and
set lanlink_list($curlan) {} # simulated nodes should happen outside make-simulated
set last_class $curlan if { $simulated == 1 } {
set simulated 0
perror "Please define links between real and simulated nodes outside make-simulated"
set simulated 1
return ""
}
set curlan lan[incr id_counter]
# Convert bandwidth and delay
set rbw [parse_bw $bw]
set rdelay [parse_delay $delay]
Lan $curlan $self $modified_nodelist $rbw $rdelay {}
set lanlink_list($curlan) {}
set last_class $curlan
# get the vport number on nsenode on which we make
# the lan and store it in the simulated node that
# goes into NSE
foreach simnode $simnode_list {
set vport [[$simnode set nsenode] find_port $curlan]
$simnode set nsenode_vport $vport
}
return $curlan return $curlan
} else {
return ""
}
} }
# run # run
...@@ -228,6 +356,8 @@ Simulator instproc run {} { ...@@ -228,6 +356,8 @@ Simulator instproc run {} {
# attach-agent <node> <agent> # attach-agent <node> <agent>
# This creates an attachment between <node> and <agent>. # This creates an attachment between <node> and <agent>.
Simulator instproc attach-agent {node agent} { Simulator instproc attach-agent {node agent} {
var_import ::GLOBALS::simulated
if {! [$agent info class Agent]} { if {! [$agent info class Agent]} {
perror "\[attach-agent] $agent is not an Agent." perror "\[attach-agent] $agent is not an Agent."
return return
...@@ -236,6 +366,16 @@ Simulator instproc attach-agent {node agent} { ...@@ -236,6 +366,16 @@ Simulator instproc attach-agent {node agent} {
perror "\[attach-agent] $node is not a Node." perror "\[attach-agent] $node is not a Node."
return return
} }
# If the node is real and yet this code is in make-simulated
# we don't allow it
if { [$node set simulated] == 0 && $simulated == 1 } {
set simulated 0
perror "Please attach agents on to real nodes outside make-simulated"
set simulated 1
return ""
}
$node attach-agent $agent $node attach-agent $agent
} }
...@@ -245,7 +385,7 @@ Simulator instproc connect {src dst} { ...@@ -245,7 +385,7 @@ Simulator instproc connect {src dst} {
set error 0 set error 0
if {! [$src info class Agent]} { if {! [$src info class Agent]} {
perror "\[connect] $src is not an Agent." perror "\[connect] $src is not an Agent."
set error 1OB set error 1
} }
if {! [$dst info class Agent]} { if {! [$dst info class Agent]} {
perror "\[connect] $dst is not an Agent." perror "\[connect] $dst is not an Agent."
...@@ -264,6 +404,14 @@ Simulator instproc connect {src dst} { ...@@ -264,6 +404,14 @@ Simulator instproc connect {src dst} {
# <link> down # <link> down
# ... # ...
Simulator instproc at {time eventstring} { Simulator instproc at {time eventstring} {
var_import ::GLOBALS::simulated
var_import ::GLOBALS::curnsenode
# ignore at statement for simulated case
if { $simulated == 1 } {
return
}
# Check that time is float # Check that time is float
if {[regexp {(^[0-9]+(\.[0-9]+)?$)|(^\.[0-9]+$)} $time] == 0} { if {[regexp {(^[0-9]+(\.[0-9]+)?$)|(^\.[0-9]+$)} $time] == 0} {
perror "Invalid time spec: $time" perror "Invalid time spec: $time"
...@@ -507,6 +655,13 @@ Simulator instproc at {time eventstring} { ...@@ -507,6 +655,13 @@ Simulator instproc at {time eventstring} {
# #
Simulator instproc rtproto {type args} { Simulator instproc rtproto {type args} {
var_import ::GLOBALS::default_ip_routing_type var_import ::GLOBALS::default_ip_routing_type
var_import ::GLOBALS::simulated
var_import ::GLOBALS::curnsenode
# ignore at statement for simulated case
if { $simulated == 1 } {
return
}
if {$args != {}} { if {$args != {}} {
punsup "rtproto: arguments ignored: $args" punsup "rtproto: arguments ignored: $args"
...@@ -556,8 +711,12 @@ Simulator instproc rename_lanlink {old new} { ...@@ -556,8 +711,12 @@ Simulator instproc rename_lanlink {old new} {
} }
Simulator instproc rename_node {old new} { Simulator instproc rename_node {old new} {
$self instvar node_list $self instvar node_list
unset node_list($old)
set node_list($new) {} # simulated nodes won't exist in the node_list
if { [info exists node_list($old)] } {
unset node_list($old)
set node_list($new) {}
}
} }
Simulator instproc rename_program {old new} { Simulator instproc rename_program {old new} {
......
...@@ -16,7 +16,7 @@ Class Agent -superclass NSObject ...@@ -16,7 +16,7 @@ Class Agent -superclass NSObject
Class Agent/UDP -superclass Agent Class Agent/UDP -superclass Agent