diff --git a/db/Lan.pm.in b/db/Lan.pm.in index 63b949c276b3288647e8e5b6c3585ad1242fa402..5d15b7e359e5e616df5ae2ee7bc4bb363d36b446 100644 --- a/db/Lan.pm.in +++ b/db/Lan.pm.in @@ -48,6 +48,93 @@ my %LanTables = ("lans" => ["lanid"], "lan_member_attributes" => ["lanid", "memberid", "attrkey"], "ifaces" => ["lanid", "ifaceid"]); +# +# Initialize Openflow attributes in lan_attributes table. +# +sub InitOpenflowAttributes($$$) +{ + my ($exptidx, $vname, $lanid) = @_; + + my $ofenabled = 0; + my $ofcontroller = ""; + my $safe_val; + + # Add openflow arrtibutes: + my $query_result = + DBQueryWarn("select ofenabled, ofcontroller from virt_lans ". + "where exptidx='$exptidx' and vname='$vname'"); + return 0 + if (!$query_result); + ($ofenabled, $ofcontroller) = $query_result->fetchrow_array() + if ($query_result->numrows); + + # + # Firstly check if the attribuets are there, if no values, + # insert them. This is because the vlan may be created + # by Lookup many times. The values can be overwritten by + # the later creation. + # + # Process 'ofenabled': + $query_result = + DBQueryWarn("select attrvalue from lan_attributes ". + "where lanid='$lanid' and attrkey='ofenabled'"); + return 0 + if (!$query_result); + if (!$query_result->numrows) + { + $safe_val = DBQuoteSpecial($ofenabled); + $query_result = + DBQueryWarn("replace into lan_attributes set ". + " lanid='$lanid', ". + " attrkey='ofenabled', ". + " attrvalue=$safe_val, ". + " attrtype='integer'"); + return 0 + if (!defined($query_result)); + } + + # Process 'ofcontroller': + $query_result = + DBQueryWarn("select attrvalue from lan_attributes ". + "where lanid='$lanid' and attrkey='ofcontroller'"); + return 0 + if (!$query_result); + if (!$query_result->numrows) + { + $safe_val = DBQuoteSpecial($ofcontroller); + $query_result = + DBQueryWarn("replace into lan_attributes set ". + " lanid='$lanid', ". + " attrkey='ofcontroller', ". + " attrvalue=$safe_val, ". + " attrtype='string'"); + return 0 + if (!defined($query_result)); + } + + # Process 'oflistener': + $query_result = + DBQueryWarn("select attrvalue from lan_attributes ". + "where lanid='$lanid' and attrkey='oflistener'"); + return 0 + if (!$query_result); + if (!$query_result->numrows) + { + $safe_val = DBQuoteSpecial(""); + $query_result = + DBQueryWarn("replace into lan_attributes set ". + " lanid='$lanid', ". + " attrkey='oflistener', ". + " attrvalue=$safe_val, ". + " attrtype='string'"); + return 0 + if (!defined($query_result)); + } + + return 1; +} + + # # Lookup and create a class instance to return. # @@ -107,6 +194,9 @@ sub Lookup($$;$$) $self->{"ATTRS"} = {}; $self->{"EXPT"} = $experiment; + return undef + if (!Lan->InitOpenflowAttributes($self->{'LAN'}->{'exptidx'}, $self->{'LAN'}->{'vname'}, $lanid)); + # # Grab the attributes for this lan now. # @@ -188,7 +278,7 @@ sub Create($$$;$$$) # Need the newly minted ID my $lanid = $query_result->insertid(); my $lan = Lan->Lookup($lanid); - + print "Created lan: $lan\n" if ($debug && $lan); return $lan; diff --git a/install/boss-install.in b/install/boss-install.in index 1944b7fbe2bf30c00ba8fca5c8c62f85d1e615cc..1b10be24f705259e37f6d3431ba1fa068e97cec0 100644 --- a/install/boss-install.in +++ b/install/boss-install.in @@ -155,6 +155,8 @@ my $TFTPD_PKG = "emulab-tftp-hpa-0.48"; my $P5DBD_PKG = "p5-DBD-mysql50-3.0002"; # XXX temporary until fix dependencies in emulab-boss package my $PYM2_PKG = "py25-m2crypto-0.19.1"; +# XXX temporary until someone extracts their head from the dark regions +my $EASYINSTALL = "/usr/local/bin/easy_install"; # # Named pid file. @@ -603,6 +605,44 @@ Phase "portfixup", "Fixing up packages", sub { ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD $pname"); }; } + if ($FBSD_MAJOR > 6) { + # + # What a crock! First, that the FreeBSD ports system + # defaults to installing the zipped .egg files that have + # to be unzipped per-user before use. Second, that it + # offers no easy way at package creation to override this. + # Third, that there is no easy way that I could find in + # the python eggs system to set a global cache directory + # into which to unpack all eggs; you have to set + # PYTHON_EGG_CACHE in the environment of every script. + # Fourth, that the per-user default value might be in an + # unwritable location. + # + # So, I am reduced to manually unpacking all .egg files + # into the corresponding .egg directories after the + # install! Since this would totally screw any attempt + # to update those packages, I just do it to the one + # package that I know will fail otherwise. + # + Phase "m2crypto-egg", "Unpacking python m2crypto .egg", sub { + my $pydir = "/usr/local/lib/python2.5/site-packages"; + my $egg = `ls -d $pydir/M2Crypto-0.19.1-py2.5-*.egg 2>/dev/null`; + chomp($egg); + if (! -x $EASYINSTALL) { + PhaseSkip("python easy_install missing"); + } + if ($egg eq "") { + PhaseSkip("py25-m2crypto egg not found"); + } + if (-d "$egg") { + PhaseSkip("py25-m2crypto egg already unpacked"); + } + ExecQuietFatal("mv $egg /var/tmp/"); + $egg =~ s/$pydir//; + ExecQuietFatal("$EASYINSTALL -Z /var/tmp$egg"); + ExecQuietFatal("mv /var/tmp$egg $pydir/$egg.bak"); + }; + } }; Phase "patches", "Applying patches", sub { @@ -760,7 +800,7 @@ Phase "syslog", "Setting up syslog", sub { "!tftpd", "*.*\t\t\t\t\t\t$LOGDIR/tftpd.log", "!capserver", "*.*\t\t\t\t\t\t$LOGDIR/capserver.log", "!frisbeed", "*.*\t\t\t\t\t\t$LOGDIR/frisbeed.log", - "!pubsubd", "*.*\t\t\t\t\t\t$LOGDIR/pubsubd.log", + "!pubsubd", "*.*\t\t\t\t\t\t$LOGDIR/pubsubd.log", "!osselect", "*.*\t\t\t\t\t\t$LOGDIR/osselect.log", "!genlastlog","*.*\t\t\t\t\t\t$LOGDIR/genlastlog.log", "!sdcollectd","*.*\t\t\t\t\t\t$LOGDIR/sdcollectd.log", @@ -997,7 +1037,8 @@ Phase "rc.conf", "Adding testbed content to $RCCONF", sub { qq|xntpd_enable="YES"|, qq|syslogd_flags=""|, qq|tftpd_flags="-lvvvv -C 40 -s /tftpboot"|, - qq|apache_enable="YES"|); + qq|apache_enable="YES"|, + qq|pubsubd_flags="-T 10"|); # Starting at FreeBSD 6 we use the default version of bind, not the port. if ($FBSD_MAJOR < 6) { diff --git a/install/ops-install.in b/install/ops-install.in index b6330645834556ac36d850a3a16afc12c4058c5f..9ed0ef28c82fa51c1b9b4979cdcff9e06fe2b528 100644 --- a/install/ops-install.in +++ b/install/ops-install.in @@ -74,6 +74,8 @@ my $TBADMINGID = 101; my $P5DBD_PKG = "p5-DBD-mysql50-3.0002"; # XXX temporary until fix dependencies in emulab-ops package my $PYM2_PKG = "py25-m2crypto-0.19.1"; +# XXX temporary until someone extracts their head from the dark regions +my $EASYINSTALL = "/usr/local/bin/easy_install"; # # Allow this to work if the library is left in the source directory @@ -544,6 +546,44 @@ Phase "portfixup", "Fixing up packages", sub { ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD $pname"); }; } + if ($FBSD_MAJOR > 6) { + # + # What a crock! First, that the FreeBSD ports system + # defaults to installing the zipped .egg files that have + # to be unzipped per-user before use. Second, that it + # offers no easy way at package creation to override this. + # Third, that there is no easy way that I could find in + # the python eggs system to set a global cache directory + # into which to unpack all eggs; you have to set + # PYTHON_EGG_CACHE in the environment of every script. + # Fourth, that the per-user default value might be in an + # unwritable location. + # + # So, I am reduced to manually unpacking all .egg files + # into the corresponding .egg directories after the + # install! Since this would totally screw any attempt + # to update those packages, I just do it to the one + # package that I know will fail otherwise. + # + Phase "m2crypto-egg", "Unpacking python m2crypto .egg", sub { + my $pydir = "/usr/local/lib/python2.5/site-packages"; + my $egg = `ls -d $pydir/M2Crypto-0.19.1-py2.5-*.egg 2>/dev/null`; + chomp($egg); + if (! -x $EASYINSTALL) { + PhaseSkip("python easy_install missing"); + } + if ($egg eq "") { + PhaseSkip("py25-m2crypto egg not found"); + } + if (-d "$egg") { + PhaseSkip("py25-m2crypto egg already unpacked"); + } + ExecQuietFatal("mv $egg /var/tmp/"); + $egg =~ s/$pydir//; + ExecQuietFatal("$EASYINSTALL -Z /var/tmp$egg"); + ExecQuietFatal("mv /var/tmp$egg $pydir/$egg.bak"); + }; + } }; Phase "patches", "Applying patches", sub { @@ -587,7 +627,8 @@ Phase "rc.conf", "Adding testbed content to rc.conf", sub { (($ISFS && $WINSUPPORT) ? qq|smbd_enable="YES"| : ()), qq|apache_enable="YES"|, qq|syslogd_flags=""|, - ($CVSSUPPORT ? qq|cvsd_enable="YES"| : ())); + ($CVSSUPPORT ? qq|cvsd_enable="YES"| : ()), + qq|pubsubd_flags="-T 10"|); }; Phase "hosts", "Adding boss/ops/fs IP addresses to $HOSTS", sub { diff --git a/protogeni/lib/GeniCM.pm.in b/protogeni/lib/GeniCM.pm.in index dd266ca9d4289c2e738cc9693832fb243819bdcb..c59539828eec919feffe353e11899ce224d1ccb9 100644 --- a/protogeni/lib/GeniCM.pm.in +++ b/protogeni/lib/GeniCM.pm.in @@ -158,7 +158,7 @@ sub Resolve($) undef, "Nothing here by that name"); } - my $rspec = GetAdvertisement(0, $node->node_id()); + my $rspec = GetAdvertisement(0, $node->node_id(), "0.1"); if (! defined($rspec)) { return GeniResponse->Create(GENIRESPONSE_ERROR, undef, "Could not start avail"); @@ -248,7 +248,7 @@ sub DiscoverResourcesAux($$$) # # Acquire the advertisement from ptopgen and compress it if requested. # - my $xml = GetAdvertisement($available, undef); + my $xml = GetAdvertisement($available, undef, "0.2"); if (! defined($xml)) { return GeniResponse->Create(GENIRESPONSE_ERROR, undef, "Could not start avail"); @@ -266,12 +266,12 @@ sub DiscoverResourcesAux($$$) # # Use ptopgen in xml mode to spit back an xml file. # -sub GetAdvertisement($$) +sub GetAdvertisement($$$) { - my ($available, $pc) = @_; + my ($available, $pc, $version) = @_; my $xml = undef; - my $invocation = "$PTOPGEN -x -g 0.2 -r -p GeniSlices"; + my $invocation = "$PTOPGEN -x -g $version -r -p GeniSlices"; $invocation .= " -a" unless $available; if (defined($pc)) { $invocation .= " -1 $pc"; diff --git a/protogeni/lib/GeniCMV2.pm.in b/protogeni/lib/GeniCMV2.pm.in index 6febc6409f931b08317ee9f63985a858ec3bbef7..ca3d2952fcf42de87d2892ec305c32382dc4ee07 100755 --- a/protogeni/lib/GeniCMV2.pm.in +++ b/protogeni/lib/GeniCMV2.pm.in @@ -139,7 +139,7 @@ sub Resolve($) if ($type eq "node") { my $node = $object; - my $rspec = GeniCM::GetAdvertisement(0, $node->node_id()); + my $rspec = GeniCM::GetAdvertisement(0, $node->node_id(), "0.1"); if (! defined($rspec)) { return GeniResponse->Create(GENIRESPONSE_ERROR, undef, "Error getting advertisement"); diff --git a/sql/database-create.sql b/sql/database-create.sql index 419d4d3fcba6e515d5fd5314da0cbcfb60b9cda0..f28d0465a4a348c8f9f8e3d55f46e3ae6cfd9dfb 100644 --- a/sql/database-create.sql +++ b/sql/database-create.sql @@ -3831,6 +3831,8 @@ CREATE TABLE `virt_lans` ( `layer` tinyint(4) NOT NULL default '2', `implemented_by_path` tinytext, `implemented_by_link` tinytext, + `ofenabled` tinyint(1) default '0', + `ofcontroller` tinytext, PRIMARY KEY (`exptidx`,`vname`,`vnode`,`vport`), UNIQUE KEY `vport` (`pid`,`eid`,`vname`,`vnode`,`vport`), KEY `pid` (`pid`,`eid`,`vname`), diff --git a/sql/database-fill.sql b/sql/database-fill.sql index bd23d33f9575d11219b24b581741b866f706d52f..2628dc70e5b3c7667dd5c1e52e25e079377274c5 100644 --- a/sql/database-fill.sql +++ b/sql/database-fill.sql @@ -684,6 +684,9 @@ REPLACE INTO table_regex VALUES ('virt_lans','fixed_iface','text','redirect','de REPLACE INTO table_regex VALUES ('virt_lans','modbase','int','redirect','default:boolean',0,0,NULL); REPLACE INTO table_regex VALUES ('virt_lans','compat','int','redirect','default:boolean',0,0,NULL); REPLACE INTO table_regex VALUES ('virt_lans','layer','int','redirect','default:tinyint',1,2,NULL); +REPLACE INTO table_regex VALUES ('virt_lans','ofenabled','int','redirect','default:boolean',0,0,NULL); +REPLACE INTO table_regex VALUES ('virt_lans','ofcontroller','text','redirect','default:tinytext',0,0,NULL); + REPLACE INTO table_regex VALUES ('virt_node_desires','pid','text','redirect','projects:pid',0,0,NULL); REPLACE INTO table_regex VALUES ('virt_node_desires','eid','text','redirect','experiments:eid',0,0,NULL); REPLACE INTO table_regex VALUES ('virt_node_desires','vname','text','redirect','virt_nodes:vname',0,0,NULL); diff --git a/sql/updates/4/211 b/sql/updates/4/211 new file mode 100644 index 0000000000000000000000000000000000000000..7b58df4c0f5a8afd6ed3e83b9c40bba178e909d6 --- /dev/null +++ b/sql/updates/4/211 @@ -0,0 +1,27 @@ +# +# Add virt_paths +# +use strict; +use libdb; + +sub DoUpdate($$$) +{ + + if (!DBSlotExists("virt_lans", "ofenabled")) { + DBQueryFatal("ALTER TABLE virt_lans ADD ". + " `ofenabled` tinyint(1) default '0'"); + } + if (!DBSlotExists("virt_lans", "ofcontroller")) { + DBQueryFatal("ALTER TABLE virt_lans ADD ". + " `ofcontroller` tinytext"); + } + DBQueryFatal("REPLACE INTO table_regex VALUES" . + " ('virt_lans', 'ofenabled', 'int', 'redirect', ". + " 'default:boolean', 0,0,NULL);"); + + DBQueryFatal("REPLACE INTO table_regex VALUES" . + " ('virt_lans', 'ofcontroller', 'text', 'redirect', ". + " 'default:tinytext', 0,0,NULL);"); + return 0; +} +1; diff --git a/tbsetup/ns2ir/lanlink.tcl b/tbsetup/ns2ir/lanlink.tcl index 566c296c5e06656974e9a4d862dcdeb48891c9b2..7ec7822206da7bbdb274ff524914162e5d7b4cd2 100644 --- a/tbsetup/ns2ir/lanlink.tcl +++ b/tbsetup/ns2ir/lanlink.tcl @@ -273,6 +273,11 @@ LanLink instproc init {s nodes bw d type} { $self instvar iscloud $self set iscloud 0 + $self instvar ofenabled + $self instvar ofcontroller + #$self instvar oflistener # this is not needed + $self set ofenabled 0 + foreach node $nodes { set nodepair [list $node [$node add_lanlink $self]] set bandwidth($nodepair) $bw @@ -296,6 +301,16 @@ LanLink instproc init {s nodes bw d type} { } } +# +# Enable Openflow on lan/link and set controller +# +LanLink instproc enable_openflow {ofcontrollerstr} { + $self instvar ofenabled + $self instvar ofcontroller + set ofenabled 1 + set ofcontroller $ofcontrollerstr +} + # # Set the mustdelay flag. # @@ -753,6 +768,8 @@ Link instproc updatedb {DB} { $self instvar fixed_iface $self instvar layer $self instvar implemented_by + $self instvar ofenabled + $self instvar ofcontroller $sim spitxml_data "virt_lan_lans" [list "vname"] [list $self] @@ -882,6 +899,22 @@ Link instproc updatedb {DB} { if { $implemented_by != {} } { lappend values $implemented_by } + + # openflow + # + # table: virt_lans + # columns: ofenabled = 0/1 + # ofcontroller = ""/"controller connection string" + # + lappend fields "ofenabled" + lappend fields "ofcontroller" + + lappend values $ofenabled + if {$ofenabled == 1} { + lappend values $ofcontroller + } else { + lappend values "" + } $sim spitxml_data "virt_lans" $fields $values } @@ -919,6 +952,8 @@ Lan instproc updatedb {DB} { $self instvar member_settings $self instvar mustdelay $self instvar fixed_iface + $self instvar ofenabled + $self instvar ofcontroller if {$modelnet_cores > 0 || $modelnet_edges > 0} { perror "Lans are not allowed when using modelnet; just duplex links." @@ -1047,6 +1082,22 @@ Lan instproc updatedb {DB} { lappend values $fixed_iface($nodeport) } + # openflow + # + # table: virt_lans + # columns: ofenabled = 0/1 + # ofcontroller = ""/"controller connection string" + # + lappend fields "ofenabled" + lappend fields "ofcontroller" + + lappend values $ofenabled + if {$ofenabled == 1} { + lappend values $ofcontroller + } else { + lappend values "" + } + $sim spitxml_data "virt_lans" $fields $values foreach setting_key [array names member_settings] { diff --git a/tbsetup/ptopgen.in b/tbsetup/ptopgen.in index d95f2121a2ca64a63da929760fb83dc521265593..633f45fc491adcd860ab49312281196aceed2a1b 100644 --- a/tbsetup/ptopgen.in +++ b/tbsetup/ptopgen.in @@ -1655,7 +1655,11 @@ sub print_switch push @$interfaces, "(null)"; } # XXX - print_node($name, ["switch:1", "*lan:*"], [], [], $uuid, $interfaces, + my $types = ["switch:1"]; + if ($name ne "procurve1") { + push(@$types, "*lan:*"); + } + print_node($name, $types, [], [], $uuid, $interfaces, $country, $latitude, $longitude, undef); } diff --git a/tbsetup/snmpit.in b/tbsetup/snmpit.in index c5969928f175762ea3946868f67786d74d423dd7..4b3c7017801e0f65dac2dc678c05fa288c262617 100755 --- a/tbsetup/snmpit.in +++ b/tbsetup/snmpit.in @@ -1547,6 +1547,36 @@ sub doVlansFromTables($$@) { } delete($trunkedPorts{$port}); } + + # + # Set openflow + # + # A much more right place for the openflow settings is CreateOneVlan, + # however, it is called by sync vlan function and others, which don't destroy + # the existing vlan_arrtibutes rows. + # + # TODO: Move this part to CreateOneVlan and Make sure the existed + # vlan_attributes rows are deleted + # + my $ofenabled; + $vlan->GetAttribute("ofenabled", \$ofenabled); + if (defined($ofenabled) && $ofenabled == 1) { + $errors += doOpenflowEnable($stacks, $vlanid); + + my $ofcontroller; + $vlan->GetAttribute("ofcontroller", \$ofcontroller); + if (defined($ofcontroller) && $ofcontroller ne "") { + $errors += doSetOpenflowController($stacks, $vlanid, $ofcontroller); + } + + $errors += doEnableOpenflowListener($stacks, $vlanid); + + # doEnableOpenflowListener updates the lan_arrtibutes table, so vlan + # should refresh itself. But here vlan will not be used any more, so + # maybe we can comment this refresh. + $vlan->Refresh(); + } + # Delete this vlan from the list that will be created below. delete($vlans{"$vlanid"}); } @@ -1586,6 +1616,35 @@ sub doVlansFromTables($$@) { my @ports = getVlanPorts($vlanid); $errors += CreateOneVlan($stack, $vlanid, @ports); + + # + # Set openflow + # + # A much more right place for the openflow settings is CreateOneVlan, + # however, it is called by sync vlan function and others, which don't destroy + # the existing vlan_arrtibutes rows. + # + # TODO: Move this part to CreateOneVlan and Make sure the existed + # vlan_attributes rows are deleted + # + my $ofenabled; + $vlan->GetAttribute("ofenabled", \$ofenabled); + if (defined($ofenabled) && $ofenabled == 1) { + $errors += doOpenflowEnable($stacks, $vlanid); + + my $ofcontroller; + $vlan->GetAttribute("ofcontroller", \$ofcontroller); + if (defined($ofcontroller) && $ofcontroller ne "") { + $errors += doSetOpenflowController($stacks, $vlanid, $ofcontroller); + } + + $errors += doEnableOpenflowListener($stacks, $vlanid); + + # doEnableOpenflowListener updates the lan_arrtibutes table, so vlan + # should refresh itself. But here vlan will not be used any more, so + # maybe we can comment this refresh. + $vlan->Refresh(); + } } return $errors; } @@ -2688,6 +2747,13 @@ sub doEnableOpenflowListener($$) { } ofunlock(); + + # + # update the vlan object with the new listener string + # other openflow functions may also need this, especially doOpenflowEnable/doOpenflowDisable + # + my $vlaninst = VLan->Lookup($vlan); + $vlaninst->SetAttribute('oflistener', $listenerConnStr, 'string'); return $errors; } diff --git a/tbsetup/tbswap.in b/tbsetup/tbswap.in index 84ce64ca8bc02e01ccf2c60919cf5d20951489f8..c7e368dbb6643932569b1d4eb054c69873bfa2d7 100644 --- a/tbsetup/tbswap.in +++ b/tbsetup/tbswap.in @@ -199,7 +199,7 @@ if (!defined($experiment)) { } my $special = ($pid eq "testbed" || $pid eq "tbres" || $pid eq "emulab-ops" || $pid eq "utahstud"); -my $newsetup = ($pid eq "testbed"); +my $newsetup = ($pid eq "testbed" || $pid eq "geni"); # # Print starting message. diff --git a/www/experiment_defs.php b/www/experiment_defs.php index 859d16147ea39bd92ac2aecc677449fe7fb3a051..a85a4d814d1ffa02e34912b5bac05e6dca272f98 100644 --- a/www/experiment_defs.php +++ b/www/experiment_defs.php @@ -1111,6 +1111,7 @@ class Experiment if ($slice) { $slice_hrn = $slice->hrn(); + $slice_urn = $slice->urn(); if (ISADMIN()) { $url = CreateURL("showslice", "slice_idx", $slice->idx(), "showtype", "cm"); @@ -1118,13 +1119,13 @@ class Experiment echo "
diff --git a/www/showslice.php b/www/showslice.php index f50140a662b58a23a8a208e2ba1aaba165dcf0e2..58d75b1b97be4cdd35f1ffc877b2e32e618c80c6 100644 --- a/www/showslice.php +++ b/www/showslice.php @@ -52,6 +52,10 @@ $rows = array(); $rows[] = array("idx" => $slice->idx()); $rows[] = array("hrn" => $slice->hrn()); +$urn = $slice->urn(); +if ($urn) { + $rows[] = array("urn" => $slice->urn()); +} $rows[] = array("uuid" => $slice->uuid()); $rows[] = array("created" => $slice->created()); $rows[] = array("expires" => $slice->expires()); diff --git a/www/tutorial/mobilewireless.php3 b/www/tutorial/mobilewireless.php3 index 9a786576ed2bd38db4c80ff4ead56a6953d37b64..5090b9b0f6b996e8e3c2e7ee4f9eecc1e936ec11 100644 --- a/www/tutorial/mobilewireless.php3 +++ b/www/tutorial/mobilewireless.php3 @@ -1,7 +1,7 @@ +