1fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt/* 2fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt * Copyright (C) 2008 The Android Open Source Project 3fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt * 4fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt * Licensed under the Apache License, Version 2.0 (the "License"); 5fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt * you may not use this file except in compliance with the License. 6fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt * You may obtain a copy of the License at 7fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt * 8fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt * http://www.apache.org/licenses/LICENSE-2.0 9fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt * 10fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt * Unless required by applicable law or agreed to in writing, software 11fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt * distributed under the License is distributed on an "AS IS" BASIS, 12fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt * See the License for the specific language governing permissions and 14fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt * limitations under the License. 15fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt */ 16fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt 17fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt#include <stdlib.h> 18fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt#include <errno.h> 19fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt#include <fcntl.h> 20fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt#include <string.h> 21fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt 22fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt#include <sys/socket.h> 23fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt#include <sys/stat.h> 24fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt#include <sys/types.h> 25fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt#include <sys/wait.h> 26fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt 27fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt#include <netinet/in.h> 28fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt#include <arpa/inet.h> 29fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt 30fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt#define LOG_TAG "SecondaryTablController" 31fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt#include <cutils/log.h> 32fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt#include <cutils/properties.h> 33fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt 349e5e0ce62e88ddf9a09798eda51b0c270d354c8eJP Abgrallextern "C" int system_nosh(const char *command); 359e5e0ce62e88ddf9a09798eda51b0c270d354c8eJP Abgrall 36fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt#include "ResponseCode.h" 37c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt#include "NetdConstants.h" 38fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt#include "SecondaryTableController.h" 39fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt 40fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert GreenwaltSecondaryTableController::SecondaryTableController() { 41fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt int i; 42fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt for (i=0; i < INTERFACES_TRACKED; i++) { 43fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt mInterfaceTable[i][0] = 0; 44fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt // TODO - use a hashtable or other prebuilt container class 45fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt mInterfaceRuleCount[i] = 0; 46fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt } 47fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt} 48fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt 49fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert GreenwaltSecondaryTableController::~SecondaryTableController() { 50fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt} 51fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt 52fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwaltint SecondaryTableController::findTableNumber(const char *iface) { 53fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt int i; 54fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt for (i = 0; i < INTERFACES_TRACKED; i++) { 553c20787d7935c2016e8e3cc49d8f15647c12c41cJaime A Lopez-Sollano // compare through the final null, hence +1 563c20787d7935c2016e8e3cc49d8f15647c12c41cJaime A Lopez-Sollano if (strncmp(iface, mInterfaceTable[i], IFNAMSIZ + 1) == 0) { 57fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt return i; 58fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt } 59fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt } 60fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt return -1; 61fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt} 62fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt 63fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwaltint SecondaryTableController::addRoute(SocketClient *cli, char *iface, char *dest, int prefix, 64fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt char *gateway) { 65fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt int tableIndex = findTableNumber(iface); 66fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt if (tableIndex == -1) { 67fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt tableIndex = findTableNumber(""); // look for an empty slot 68fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt if (tableIndex == -1) { 695ea0c05a1e7d8e664b808aa1bb1efd08fdb2fb13Steve Block ALOGE("Max number of NATed interfaces reached"); 70fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt errno = ENODEV; 71fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt cli->sendMsg(ResponseCode::OperationFailed, "Max number NATed", true); 72fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt return -1; 73fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt } 74d14fd4f83ffeea4ad1cd559a41f775f6814565ccJaime A Lopez-Sollano strncpy(mInterfaceTable[tableIndex], iface, IFNAMSIZ); 75d14fd4f83ffeea4ad1cd559a41f775f6814565ccJaime A Lopez-Sollano // Ensure null termination even if truncation happened 76d14fd4f83ffeea4ad1cd559a41f775f6814565ccJaime A Lopez-Sollano mInterfaceTable[tableIndex][IFNAMSIZ] = 0; 77fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt } 78fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt 79063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt return modifyRoute(cli, ADD, iface, dest, prefix, gateway, tableIndex); 80063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt} 81063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt 82c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwaltint SecondaryTableController::modifyRoute(SocketClient *cli, const char *action, char *iface, 83c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt char *dest, int prefix, char *gateway, int tableIndex) { 84063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt char *cmd; 85063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt 86063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt if (strcmp("::", gateway) == 0) { 87063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt // IP tool doesn't like "::" - the equiv of 0.0.0.0 that it accepts for ipv4 88063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt asprintf(&cmd, "%s route %s %s/%d dev %s table %d", 89063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt IP_PATH, action, dest, prefix, iface, tableIndex+BASE_TABLE_NUMBER); 90063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt } else { 91063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt asprintf(&cmd, "%s route %s %s/%d via %s dev %s table %d", 92063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt IP_PATH, action, dest, prefix, gateway, iface, tableIndex+BASE_TABLE_NUMBER); 93063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt } 94063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt 95fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt if (runAndFree(cli, cmd)) { 965ea0c05a1e7d8e664b808aa1bb1efd08fdb2fb13Steve Block ALOGE("ip route %s failed: %s route %s %s/%d via %s dev %s table %d", action, 97063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt IP_PATH, action, dest, prefix, gateway, iface, tableIndex+BASE_TABLE_NUMBER); 98fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt errno = ENODEV; 99063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt cli->sendMsg(ResponseCode::OperationFailed, "ip route modification failed", true); 100fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt return -1; 101fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt } 102063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt 103063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt if (strcmp(action, ADD) == 0) { 104063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt mInterfaceRuleCount[tableIndex]++; 105063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt } else { 106063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt if (--mInterfaceRuleCount[tableIndex] < 1) { 107063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt mInterfaceRuleCount[tableIndex] = 0; 108063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt mInterfaceTable[tableIndex][0] = 0; 109063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt } 110063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt } 111c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt modifyRuleCount(tableIndex, action); 112063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt cli->sendMsg(ResponseCode::CommandOkay, "Route modified", false); 113fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt return 0; 114fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt} 115fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt 116c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwaltvoid SecondaryTableController::modifyRuleCount(int tableIndex, const char *action) { 117c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt if (strcmp(action, ADD) == 0) { 118c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt mInterfaceRuleCount[tableIndex]++; 119c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt } else { 120c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt if (--mInterfaceRuleCount[tableIndex] < 1) { 121c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt mInterfaceRuleCount[tableIndex] = 0; 122c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt mInterfaceTable[tableIndex][0] = 0; 123c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt } 124c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt } 125c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt} 126c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt 127c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwaltint SecondaryTableController::verifyTableIndex(int tableIndex) { 128c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt if ((tableIndex < 0) || 129c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt (tableIndex >= INTERFACES_TRACKED) || 130c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt (mInterfaceTable[tableIndex][0] == 0)) { 131c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt return -1; 132c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt } else { 133c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt return 0; 134c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt } 135c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt} 136c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt 137c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwaltconst char *SecondaryTableController::getVersion(const char *addr) { 138c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt if (strchr(addr, ':') != NULL) { 139c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt return "-6"; 140c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt } else { 141c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt return "-4"; 142c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt } 143c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt} 144c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt 145fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwaltint SecondaryTableController::removeRoute(SocketClient *cli, char *iface, char *dest, int prefix, 146fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt char *gateway) { 147fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt int tableIndex = findTableNumber(iface); 148fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt if (tableIndex == -1) { 1495ea0c05a1e7d8e664b808aa1bb1efd08fdb2fb13Steve Block ALOGE("Interface not found"); 150fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt errno = ENODEV; 151fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt cli->sendMsg(ResponseCode::OperationFailed, "Interface not found", true); 152fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt return -1; 153fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt } 154fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt 155063af322b48ab1bb0c3e09eb0b64915ba568275bRobert Greenwalt return modifyRoute(cli, DEL, iface, dest, prefix, gateway, tableIndex); 156fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt} 157fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt 158c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwaltint SecondaryTableController::modifyFromRule(int tableIndex, const char *action, 159c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt const char *addr) { 160c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt char *cmd; 161c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt 162c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt if (verifyTableIndex(tableIndex)) { 163c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt return -1; 164c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt } 165c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt asprintf(&cmd, "%s %s rule %s from %s table %d", IP_PATH, getVersion(addr), 166c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt action, addr, tableIndex + BASE_TABLE_NUMBER); 167c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt if (runAndFree(NULL, cmd)) { 168c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt return -1; 169c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt } 170c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt 171c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt modifyRuleCount(tableIndex, action); 172c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt return 0; 173c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt} 174c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt 175c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwaltint SecondaryTableController::modifyLocalRoute(int tableIndex, const char *action, 176c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt const char *iface, const char *addr) { 177c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt char *cmd; 178c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt 179c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt if (verifyTableIndex(tableIndex)) { 180c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt return -1; 181c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt } 182c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt 183c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt modifyRuleCount(tableIndex, action); // some del's will fail as the iface is already gone. 184c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt 185c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt asprintf(&cmd, "%s route %s %s dev %s table %d", IP_PATH, action, addr, iface, 186c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt tableIndex+BASE_TABLE_NUMBER); 187c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt return runAndFree(NULL, cmd); 188c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt} 189c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt 190fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwaltint SecondaryTableController::runAndFree(SocketClient *cli, char *cmd) { 191fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt int ret = 0; 192fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt if (strlen(cmd) >= 255) { 193c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt if (cli != NULL) { 194c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt ALOGE("ip command (%s) too long", cmd); 195c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt errno = E2BIG; 196c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt cli->sendMsg(ResponseCode::CommandSyntaxError, "Too long", true); 197c462177bd58e3bf0ac4f618934dae060569e3e0bRobert Greenwalt } 198fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt free(cmd); 199fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt return -1; 200fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt } 2019e5e0ce62e88ddf9a09798eda51b0c270d354c8eJP Abgrall ret = system_nosh(cmd); 202fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt free(cmd); 203fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt return ret; 204fc97b82e02979f246d56a4bfd60e4aab8686d3f6Robert Greenwalt} 205