function findip() { if (isset($_SERVER)) { if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && ipv6ip($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; } elseif (isset($_SERVER['HTTP_CLIENT_IP']) && ipv6ip($_SERVER['HTTP_CLIENT_IP'])) { $ip = $_SERVER['HTTP_CLIENT_IP']; } else { $ip = $_SERVER['REMOTE_ADDR']; } } else { if (getenv('HTTP_X_FORWARDED_FOR') && ipv6ip(getenv('HTTP_X_FORWARDED_FOR'))) { $ip = getenv('HTTP_X_FORWARDED_FOR'); } elseif (getenv('HTTP_CLIENT_IP') && ipv6ip(getenv('HTTP_CLIENT_IP'))) { $ip = getenv('HTTP_CLIENT_IP'); } else { $ip = getenv('REMOTE_ADDR'); } } return $ip; }
function main_entry($inf, $phyinf, $devnam, $dhcpopt, $dns, $me) { /* generate callback script */ $pid = "/var/servd/" . $inf . "-rdisc6.pid"; $hlp = "/var/servd/" . $inf . "-rdisc6.sh"; fwrite(w, $hlp, "#!/bin/sh\n" . "echo [\$0]: [{$IFNAME}] [{$MFLAG}] [{$OFLAG}] > /dev/console\n" . "phpsh /etc/services/INET/inet6_rdisc6_helper.php" . ' "IFNAME=$IFNAME"' . ' "MFLAG=$MFLAG"' . ' "OFLAG=$OFLAG"' . ' "PREFIX=$PREFIX"' . ' "PFXLEN=$PFXLEN"' . ' "LLADDR=$LLADDR"' . ' "RDNSS=$RDNSS"' . ' "DNSSL=$DNSSL"' . "\n"); cmd("chmod +x " . $hlp); /* run rdisc */ //cmd("killall rdisc6"); //cmd("sleep 1"); /* INF status path. */ $stsp = XNODE_getpathbytarget("/runtime", "inf", "uid", $inf, 0); if ($stsp == "") { return error($inf . " has not runtime nodes !"); } /* need infprev */ $infprev = query($stsp . "/infprevious"); if ($infprev != "") { $prevstsp = XNODE_getpathbytarget("/runtime", "inf", "uid", $infprev, 0); $prevdevnam = query($prevstsp . "/devnam"); $prevphyinf = query($prevstsp . "/phyinf"); } /* check if interface if ppp */ if (strstr($prevdevnam, "ppp") == "") { if (strstr($prevdevnam, "sit") == "") { cmd("rdisc6 -c " . $hlp . " -p " . $pid . " -q " . $devnam . " &"); } else { msg("SIT-Autoconf mode"); /* auto in sit mode */ $p = XNODE_getpathbytarget("/runtime", "phyinf", "uid", $prevphyinf, 0); if ($p != "") { $ipaddr = query($p . "/ipv6/link/ipaddr"); } else { return error("SIT tunnel need exist!! "); } msg("ipaddr: " . $ipaddr . " and devnam: " . $prevdevnam); cmd("rdisc6 -c " . $hlp . " -p " . $pid . " -q -e " . $ipaddr . " " . $prevdevnam . " &"); } } else { msg("PPP-Autoconf mode"); /* need infprev */ $infprev = query($stsp . "/infprevious"); $prevstsp = XNODE_getpathbytarget("/runtime", "inf", "uid", $infprev, 0); /* need infprev */ $atype = query($prevstsp . "/inet/addrtype"); if ($atype == "ipv6") { $ipaddr = query($prevstsp . "/inet/ipv6/ipaddr"); } else { if ($atype == "ppp6") { $ipaddr = query($prevstsp . "/inet/ppp6/local"); } else { return error($inf . " has wrong addrtype of infprevious"); } } cmd("rdisc6 -c " . $hlp . " -p " . $pid . " -q -e " . $ipaddr . " " . $prevdevnam . " &"); } cmd("sleep 5"); /* Clear any old records. */ del($stsp . "/stateless"); /* Preparing & Get M flag */ $child = query($stsp . "/child/uid"); if (strstr($prevdevnam, "ppp") == "") { $conf = "/var/run/" . $devnam; } else { $conf = "/var/run/" . $prevdevnam; } $mflag = fread("e", $conf . ".ra_mflag"); $oflag = fread("e", $conf . ".ra_oflag"); $rdnss = fread("e", $conf . ".ra_rdnss"); $dnssl = fread("e", $conf . ".ra_dnssl"); /* need infnext */ $infnext = query($stsp . "/infnext"); if ($infnext != "") { XNODE_set_var($inf . "_NEXTINF", $infnext); } /* need dnssl info */ if ($dnssl != "") { msg("DNSSL :" . $dnssl); XNODE_set_var($child . "_DOMAIN", $dnssl); } if (strstr($prevdevnam, "ppp") == "") { msg($inf . "/" . $devnam . ", M=[" . $mflag . "], O=[" . $oflag . "]"); } else { msg($inf . "/" . $prevdevnam . ", M=[" . $mflag . "], O=[" . $oflag . "]"); } /* according to TR-124 issue 2 */ /* Ignore m and o flags, always start dhcp client */ /* generate wait script */ $ipwait = "/var/servd/INET." . $inf . "-ipwait.sh"; fwrite(w, $ipwait, "#!/bin/sh\n" . "phpsh /etc/scripts/IP-WAIT.php" . " INF=" . $inf . " PHYINF=" . $phyinf . " DEVNAM=" . $devnam . ' "DNS=' . $dns . '"' . " ME=" . $ipwait . "\n"); /* start script */ cmd("chmod +x " . $ipwait); cmd('xmldbc -t "ra.iptest.' . $inf . ':10:' . $ipwait . '"'); if ($child != "") { /* always need dhcp client */ $dhcpopt = "IA-NA+IA-PD"; if (strstr($prevdevnam, "ppp") == "") { dhcp_client("STATEFUL", $inf, $devnam, $dhcpopt, $router, $dns); } else { XNODE_set_var($inf . "_PREVINF", $infprev); dhcp_client("PPPDHCP", $inf, $devnam, $dhcpopt, $router, $dns); } return 0; } else { $dhcpopt = "IA-NA"; if (strstr($prevdevnam, "ppp") == "") { dhcp_client("STATEFUL", $inf, $devnam, $dhcpopt, $router, $dns); } else { XNODE_set_var($inf . "_PREVINF", $infprev); dhcp_client("PPPDHCP", $inf, $devnam, $dhcpopt, $router, $dns); } return 0; } //not reach here if ($mflag == "1") { /* Stateful ... */ if ($dhcpopt == "") { if ($child == "") { $dhcpopt = "IA-NA"; } else { $dhcpopt = "IA-NA+IA-PD"; } } //dhcp_client("STATEFUL", $inf, $devnam, $dhcpopt, $router, $dns); if (strstr($prevdevnam, "ppp") == "") { dhcp_client("STATEFUL", $inf, $devnam, $dhcpopt, $router, $dns); } else { XNODE_set_var($inf . "_PREVINF", $infprev); dhcp_client("PPPDHCP", $inf, $devnam, $dhcpopt, $router, $dns); } return 0; } else { if ($mflag == "0") { /* Stateless */ $p = XNODE_getpathbytarget("/runtime", "phyinf", "uid", $phyinf, 0); if ($p == "") { return error($phyinf . " has not runtime nodes!"); } /* Get self-configured IP address. */ /*$ipaddr = query($p."/ipv6/global/ipaddr");*/ /*$prefix = query($p."/ipv6/global/prefix");*/ $mac = PHYINF_getphymac($inf); $hostid = ipv6eui64($mac); $ra_prefix = fread("e", $conf . ".ra_prefix"); $prefix = fread("e", $conf . ".ra_prefix_len"); $ipaddr = ipv6ip($ra_prefix, $prefix, $hostid, 0, 0); $router = fread("e", $conf . ".ra_saddr"); if (strstr($prevdevnam, "ppp") != "") { $p = XNODE_getpathbytarget("/runtime", "phyinf", "uid", $prevphyinf, 0); $llipaddr = query($p . "/ipv6/link/ipaddr"); $hostid = ipv6hostid($llipaddr, 64); $ipaddr = ipv6ip($ra_prefix, $prefix, $hostid, 0, 0); } if ($ipaddr != "") { if ($oflag == "0" && $dns == "" && $rdnss != "") { $dns = $rdnss; } msg("Stateless Self-Config IP: " . $ipaddr . "/" . $prefix); //$router = fread("e", $conf."/ra_saddr"); if ($child != "") { set($stsp . "/stateless/ipaddr", $ipaddr); set($stsp . "/stateless/prefix", $prefix); //set($stsp."/stateless/gateway", $router); setattr($stsp . "/stateless/gateway", "get", "ip -6 route show default | scut -p via"); set($stsp . "/stateless/dns", $dns); if ($dnssl != "") { set($stsp . "/stateless/domain", $dnssl); } /* run dhcpc6 for the child interface, and attach at the callback of dhcpc. */ //dhcp_client("STATELESS", $inf, $devnam, "IA-PD", $router, $dns); if (strstr($prevdevnam, "ppp") == "") { dhcp_client("STATELESS", $inf, $devnam, "IA-PD", $router, $dns); } else { XNODE_set_var($inf . "_PREVINF", $infprev); dhcp_client("PPPDHCP", $inf, $devnam, "IA-PD", $router, $dns); } } else { cmd("phpsh /etc/scripts/IPV6.INET.php ACTION=ATTACH" . " MODE=STATELESS" . " INF=" . $inf . " DEVNAM=" . $devnam . " IPADDR=" . $ipaddr . " PREFIX=" . $prefix . " GATEWAY=" . $router . ' "DNS=' . $dns . '"'); if ($oflag == "1" && $dns == "") { msg("STATELESS DHCP: information only."); dhcp_client("INFOONLY", $inf, $devnam, "", "", ""); } } return 0; } } } //cmd("killall rdisc6"); cmd("/etc/scripts/killpid.sh " . $pid); cmd("sleep 1"); /* Not configured, try later. */ cmd('xmldbc -t "ra.iptest.' . $inf . ':5:' . $me . '"'); /* force to send RS */ //$conf = "/proc/sys/net/ipv6/conf/".$_GLOBALS["DEVNAM"]; //fwrite(w, $conf."/disable_ipv6", "1"); //fwrite(w, $conf."/disable_ipv6", "0"); return 0; }
function handle_stateless($inf, $prefix, $pfxlen) { $stsp = XNODE_getpathbytarget("/runtime", "inf", "uid", $inf, 0); if ($stsp == "") { msg("STATLESS - no runtime for " . $inf); return; } if ($prefix == "" || $pfxlen == "") { msg("STATELESS - no prefix"); return; } /* Prepare child. */ msg("STATELESS - Got prefix " . $prefix . "/" . $pfxlen); $NEW_PD_PREFIX = $prefix; $NEW_PD_PLEN = $pfxlen; $child = query($stsp . "/child/uid"); $mac = PHYINF_getphymac($child); $hostid = ipv6eui64($mac); if ($NEW_PD_PLEN < 64) { /* handle blackhole issue */ //cmd("ip -6 route add blackhole ".$prefix."/".$pfxlen." dev lo"); //set($stsp."/blackhole/prefix", $prefix); //set($stsp."/blackhole/plen", $pfxlen); $holecnt = query($stsp . "/blackhole/count"); if ($holecnt == "") { $holecnt = 1; set($stsp . "/blackhole/entry:" . $holecnt . "/prefix", $prefix); set($stsp . "/blackhole/entry:" . $holecnt . "/plen", $pfxlen); set($stsp . "/blackhole/count", $holecnt); cmd("ip -6 route add blackhole " . $prefix . "/" . $pfxlen . " dev lo"); } $slalen = 64 - $pfxlen; $ipaddr = ipv6ip($prefix, $pfxlen, $hostid, 1, $slalen); $pfxlen = 64; } else { $ipaddr = ipv6ip($prefix, $pfxlen, $hostid, 0, 0); } /* Check renew, do nothing if we got the same prefix and prefix len as previous ones */ $oldip = query($stsp . "/child/ipaddr"); $oldpfl = query($stsp . "/child/prefix"); if ($oldip == $ipaddr && $oldpfl == $pfxlen) { msg("STATELESS - Renew but do nothing"); return; } set($stsp . "/child/ipaddr", $ipaddr); set($stsp . "/child/prefix", $pfxlen); set($stsp . "/child/pdnetwork", strip($_GLOBALS["NEW_PD_PREFIX"])); set($stsp . "/child/pdprefix", strip($_GLOBALS["NEW_PD_PLEN"])); set($stsp . "/child/pdplft", strip($_GLOBALS["NEW_PD_PLTIME"])); set($stsp . "/child/pdvlft", strip($_GLOBALS["NEW_PD_VLTIME"])); $childgz = query($stsp . "/childgz/uid"); if ($childgz != "") { $mac = PHYINF_getphymac($childgz); msg("Guest zone mac is " . $mac); $hostid = ipv6eui64($mac); if ($NEW_PD_PLEN < 64) { $cipaddr = ipv6ip($prefix, $pfxlen, $hostid, 0, 64 - $pfxlen); $cpfxlen = 64; } else { //If pd length is bigger than 63, don't set to guest zone $cipaddr = ""; $cpfxlen = ""; } if ($cipaddr != "") { set($stsp . "/childgz/ipaddr", $cipaddr); set($stsp . "/childgz/prefix", $cpfxlen); set($stsp . "/childgz/pdnetwork", strip($_GLOBALS["NEW_PD_PREFIX"])); set($stsp . "/childgz/pdprefix", strip($_GLOBALS["NEW_PD_PLEN"])); set($stsp . "/childgz/pdplft", strip($_GLOBALS["NEW_PD_PLTIME"])); set($stsp . "/childgz/pdvlft", strip($_GLOBALS["NEW_PD_VLTIME"])); } } $DOMAIN = strip($_GLOBALS["DOMAIN"]); if ($DOMAIN != "") { XNODE_set_var($child . "_DOMAIN", $DOMAIN); } XNODE_set_var($child . "_PDPLFT", strip($_GLOBALS["NEW_PD_PLTIME"])); XNODE_set_var($child . "_PDVLFT", strip($_GLOBALS["NEW_PD_VLTIME"])); msg("STATELESS - Child " . $ipaddr . "/" . $pfxlen); $DNS = strip($_GLOBALS["DNS"]); $NAMESERVERS = strip($_GLOBALS["NAMESERVERS"]); /* Combine the user config and DHCP server setting. */ $dns = $DNS; /* if ($NAMESERVERS!="") { if ($dns=="") $dns = $NAMESERVERS; else $dns = $dns." ".$NAMESERVERS; } */ $infprev = query($stsp . "/infprevious"); if ($infprev != "") { $stsprevp = XNODE_getpathbytarget("/runtime", "inf", "uid", $infprev, 0); $pdevnam = query($stsprevp . "/devnam"); $conf = "/var/run/" . $pdevnam; } else { $conf = "/var/run/" . query($stsp . "/devnam"); } //$oflag = fread("e", $conf.".ra_oflag"); //if($oflag=="1") //{ if ($NAMESERVERS != "") { if ($dns == "") { $dns = $NAMESERVERS; } else { $dns = $dns . " " . $NAMESERVERS; } } //} /* DS-Lite info */ $remote = strip($_GLOBALS["NEW_AFTR_NAME"]); if ($remote != "") { set($stsp . "/inet/ipv4/ipv4in6/remote", $remote); } anchor($stsp); cmd("phpsh /etc/scripts/IPV6.INET.php ACTION=ATTACH" . " MODE=STATELESS" . " INF=" . $inf . " DEVNAM=" . query("devnam") . " IPADDR=" . query("stateless/ipaddr") . " PREFIX=" . query("stateless/prefix") . " GATEWAY=" . query("stateless/gateway") . ' "DNS=' . $dns . '"'); /*del($stsp."/stateless");*/ /*renew may cause wan ip be disappered*/ }
function main_entry($inf, $phyinf, $devnam, $dns, $me) { /* INF status path. */ $stsp = XNODE_getpathbytarget("/runtime", "inf", "uid", $inf, 0); if ($stsp == "") { return error($inf . " has not runtime nodes !"); } $infprev = query($stsp . "/infprevious"); if ($infprev != "") { $prevstsp = XNODE_getpathbytarget("/runtime", "inf", "uid", $infprev, 0); $prevdevnam = query($prevstsp . "/devnam"); $prevphyinf = query($prevstsp . "/phyinf"); } /* Preparing & Get M flag */ if (strstr($prevdevnam, "ppp") == "") { $conf = "/var/run/" . $devnam; } else { $conf = "/var/run/" . $prevdevnam; } $mflag = fread("e", $conf . ".ra_mflag"); /* generate callback script */ $hlp = "/var/servd/" . $inf . "-rdisc6.sh"; $pid = "/var/servd/" . $inf . "-rdisc6.pid"; $child = query($stsp . "/child/uid"); if ($mflag == "") { //msg("mflag is null, kill rdisc6"); /* kill daemon */ cmd("/etc/scripts/killpid.sh " . $pid); /* check if interface if ppp */ if (strstr($prevdevnam, "ppp") == "") { if (strstr($prevdevnam, "sit") == "") { cmd("rdisc6 -c " . $hlp . " -p " . $pid . " -q " . $devnam . " &"); } else { //msg("SIT-Autoconf mode"); /* auto in sit mode */ $p = XNODE_getpathbytarget("/runtime", "phyinf", "uid", $prevphyinf, 0); if ($p != "") { $ipaddr = query($p . "/ipv6/link/ipaddr"); } else { return error("SIT tunnel need exist!! "); } msg("ipaddr: " . $ipaddr . " and devnam: " . $prevdevnam); cmd("rdisc6 -c " . $hlp . " -p " . $pid . " -q -e " . $ipaddr . " " . $prevdevnam . " &"); } } else { //msg("PPP-Autoconf mode"); /* need infprev */ $infprev = query($stsp . "/infprevious"); $prevstsp = XNODE_getpathbytarget("/runtime", "inf", "uid", $infprev, 0); /* need infprev */ $atype = query($prevstsp . "/inet/addrtype"); if ($atype == "ipv6") { $ipaddr = query($prevstsp . "/inet/ipv6/ipaddr"); /* share session with ipv4*/ } else { if ($atype == "ppp6") { $ipaddr = query($prevstsp . "/inet/ppp6/local"); /* only ipv6 session */ } else { return error($inf . " has wrong addrtype of infprevious"); } } cmd("rdisc6 -c " . $hlp . " -p " . $pid . " -q -e " . $ipaddr . " " . $prevdevnam . " &"); } //cmd("sleep 5"); cmd('xmldbc -t "ra.iptest.' . $inf . ':10:' . $me . '"'); return 0; } /* Preparing & Get M flag */ $mflag = fread("e", $conf . ".ra_mflag"); $oflag = fread("e", $conf . ".ra_oflag"); $rdnss = fread("e", $conf . ".ra_rdnss"); $dnssl = fread("e", $conf . ".ra_dnssl"); /* need dnssl info */ if ($dnssl != "") { msg("DNSSL :" . $dnssl); XNODE_set_var($child . "_DOMAIN", $dnssl); } if ($mflag != "") { if (strstr($prevdevnam, "ppp") == "") { msg($inf . "/" . $devnam . ", M=[" . $mflag . "], O=[" . $oflag . "]"); } else { msg($inf . "/" . $prevdevnam . ", M=[" . $mflag . "], O=[" . $oflag . "]"); } } $p = XNODE_getpathbytarget("/runtime", "phyinf", "uid", $phyinf, 0); if ($p == "") { return error($phyinf . " has not runtime nodes!"); } if ($mflag != "") { /* we got ra */ $mac = PHYINF_getphymac($inf); $hostid = ipv6eui64($mac); $ra_prefix = fread("e", $conf . ".ra_prefix"); $prefix = fread("e", $conf . ".ra_prefix_len"); $ra_saddr = fread("e", $conf . ".ra_saddr"); $ipaddr = ipv6ip($ra_prefix, $prefix, $hostid, 0, 0); $router = fread("e", $conf . ".ra_saddr"); if (strstr($prevdevnam, "ppp") != "") { $p = XNODE_getpathbytarget("/runtime", "phyinf", "uid", $prevphyinf, 0); $llipaddr = query($p . "/ipv6/link/ipaddr"); $hostid = ipv6hostid($llipaddr, 64); $ipaddr = ipv6ip($ra_prefix, $prefix, $hostid, 0, 0); } //if ($ipaddr!="") if ($ra_saddr != "") { if ($oflag == "0" && $dns == "" && $rdnss != "") { $dns = $rdnss; } $defrt = query($stsp . "/defaultroute"); if ($defrt != "" && $defrt > 0 && $router != "") { if (isfile("/var/run/wan_ralft_zero") != 1) { /* add default route and runtime node*/ $gateway = query($stsp . "/inet/ipv6/gateway"); if ($gateway != "") { cmd("ip -6 route del default via " . $gateway . " dev " . $devnam . " metric " . $defrt); del($stsp . "/inet/ipv6/gateway"); } cmd("ip -6 route add default via " . $router . " dev " . $devnam . " metric " . $defrt); } //set($stsp."/inet/ipv6/gateway",$router); setattr($stsp . "/inet/ipv6/gateway", "get", "ip -6 route show default | scut -p via"); /* Replace runtime dynamic route */ /* $base = "/runtime/dynamic/route6"; $cnt = query($base."/entry#"); $i=1; $found=0; while($i<=$cnt) { $dest = query($base."/entry:".$i."/ipaddr"); $pfx = query($base."/entry:".$i."/prefix"); $dname = query($base."/entry:".$i."/inf"); if($dest=="::" && $pfx=="0" && $dname==$inf) { set($base."/entry:".$i."/gateway", $router); $found=1; break; } $i++; } if($found=="0") { set($base."/entry:".$i."/ipaddr", "::"); set($base."/entry:".$i."/prefix", "0"); set($base."/entry:".$i."/gateway", $router); set($base."/entry:".$i."/metric", $defrt); set($base."/entry:".$i."/inf", $inf); } */ if ($child != "") { set($stsp . "/stateless/ipaddr", $ipaddr); set($stsp . "/stateless/prefix", $prefix); //set($stsp."/stateless/gateway", $router); setattr($stsp . "/stateless/gateway", "get", "ip -6 route show default | scut -p via"); set($stsp . "/stateless/dns", $dns); } if ($child == "" && isfile("/var/run/" . $inf . ".UP") != 1) { cmd("phpsh /etc/scripts/IPV6.INET.php ACTION=ATTACH" . " MODE=STATELESS" . " INF=" . $inf . " DEVNAM=" . $devnam . " IPADDR=" . $ipaddr . " PREFIX=" . $prefix . " GATEWAY=" . $router . ' "DNS=' . $dns . '"'); } } } return 0; } /* Not configured, try later. */ //cmd("killall rdisc6"); //cmd("/etc/scripts/killpid.sh /var/servd/".$inf."-rdisc6.pid"); //msg("mflag is null, kill rdisc6-part2"); //cmd("/etc/scripts/killpid.sh ".$pid); //cmd("sleep 1"); //cmd('xmldbc -t "ra.iptest.'.$inf.':5:'.$me.'"'); //cmd('xmldbc -t "ra.iptest.'.$inf.':10:'.$me.'"'); //return 0; }
function tunnel_create($p) { /* Create the tunnel */ $phy = add_tunnel($_GLOBALS["MODE"], "", $_GLOBALS["DEVNAM"], $_GLOBALS["INF"], $_GLOBALS["REMOTE"], $_GLOBALS["LOCAL"], $_GLOBALS["MTU"]); if ($phy == "") { return; } /* Convert the IPv6 address to simplified format. */ $ipaddr = ipv6ip($_GLOBALS["IPADDR"], 128, 0, 0, 0); $gateway = ipv6ip($_GLOBALS["GATEWAY"], 128, 0, 0, 0); /* TODO: DNS may also need to convert... */ /* update the phyiscal interface the tunnel device. */ set($p . "/phyinf", $phy); /* Attach IP address. */ echo "phpsh /etc/scripts/IPV6.INET.php ACTION=ATTACH" . " MODE=" . $_GLOBALS["MODE"] . " INF=" . $_GLOBALS["INF"] . " DEVNAM=" . $_GLOBALS["DEVNAM"] . " MTU=" . $_GLOBALS["MTU"] . " IPADDR=" . $ipaddr . " PREFIX=" . $_GLOBALS["PREFIX"] . " GATEWAY=" . $gateway . ' "DNS=' . $_GLOBALS["DNS"] . '"' . "\n"; /* Prepare the configuration for TSP child. */ $child = query($p . "/child/uid"); if ($_GLOBALS["MODE"] == "TSP" && $child != "") { prepare_tsp_child($p, $child); } }