//------------------------------------------------------------------------------ //! @file com_proto_route.cc //------------------------------------------------------------------------------ /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2018 CERN/Switzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ #include "common/StringConversion.hh" #include "common/StringTokenizer.hh" #include "common/ParseUtils.hh" #include "console/ConsoleMain.hh" #include "console/commands/ICmdHelper.hh" constexpr static int sDefaultXrdPort = 1094; constexpr static int sDefaultHttpPort = 8000; void com_route_help(); //------------------------------------------------------------------------------ //! Class RouteHelper //------------------------------------------------------------------------------ class RouteHelper: public ICmdHelper { public: //---------------------------------------------------------------------------- //! Constructor //! //! @param opts global options //---------------------------------------------------------------------------- RouteHelper(const GlobalOptions& opts): ICmdHelper(opts) {} //---------------------------------------------------------------------------- //! Destructor //---------------------------------------------------------------------------- ~RouteHelper() = default; //---------------------------------------------------------------------------- //! Parse command line input //! //! @param arg input //! //! @return true if successful, otherwise false //---------------------------------------------------------------------------- bool ParseCommand(const char* arg); private: //---------------------------------------------------------------------------- //! Check path validity - it shouldn't contain spaces, '/./' or '/../' or //! backslash characters. Append if necessary and end '/'. //! //! @param path given path //! //! @return true if valid, otherwise false //---------------------------------------------------------------------------- bool ValidatePath(std::string& path) const; }; //------------------------------------------------------------------------------ // Parse command line input //------------------------------------------------------------------------------ bool RouteHelper::ParseCommand(const char* arg) { const char* option; std::string soption; eos::console::RouteProto* route = mReq.mutable_route(); eos::common::StringTokenizer tokenizer(arg); tokenizer.GetLine(); option = tokenizer.GetToken(); std::string cmd = (option ? option : ""); if (cmd == "ls") { using eos::console::RouteProto_ListProto; RouteProto_ListProto* list = route->mutable_list(); if (!(option = tokenizer.GetToken())) { list->set_path(""); } else { soption = option; if (!ValidatePath(soption)) { return false; } list->set_path(soption); } } else if (cmd == "unlink") { using eos::console::RouteProto_UnlinkProto; RouteProto_UnlinkProto* unlink = route->mutable_unlink(); if (!(option = tokenizer.GetToken())) { return false; } // Do basic checks that this is a path soption = option; if (!ValidatePath(soption)) { return false; } unlink->set_path(soption); } else if (cmd == "link") { if (!(option = tokenizer.GetToken())) { return false; } // Do basic checks that this is a path soption = option; if (!ValidatePath(soption)) { return false; } eos::console::RouteProto_LinkProto* link = route->mutable_link(); link->set_path(soption); // Parse redirection locations which are "," separated if (!(option = tokenizer.GetToken())) { return false; } soption = option; std::vector endpoints; eos::common::StringConversion::Tokenize(soption, endpoints, ","); if (endpoints.empty()) { return false; } for (const auto& endpoint : endpoints) { eos::console::RouteProto_LinkProto_Endpoint* ep = link->add_endpoints(); std::vector elems; eos::common::StringConversion::Tokenize(endpoint, elems, ":"); const std::string fqdn = elems[0]; if (!eos::common::ValidHostnameOrIP(fqdn)) { std::cerr << "error: invalid hostname specified" << std::endl; return false; } uint32_t xrd_port = sDefaultXrdPort; uint32_t http_port = sDefaultHttpPort; if (elems.size() == 3) { try { xrd_port = std::stoul(elems[1]); http_port = std::stoul(elems[2]); } catch (const std::exception& e) { std::cerr << "error: failed to parse ports for route" << std::endl; return false; } } else if (elems.size() == 2) { try { xrd_port = std::stoi(elems[1]); } catch (const std::exception& e) { std::cerr << "error: failed to parse xrd port for route" << std::endl; return false; } } ep->set_fqdn(fqdn); ep->set_xrd_port(xrd_port); ep->set_http_port(http_port); } } else { return false; } return true; } //------------------------------------------------------------------------------ // Check if path is valid //------------------------------------------------------------------------------ bool RouteHelper::ValidatePath(std::string& path) const { if (path.empty() || path[0] != '/') { std::cerr << "error: path should be non-empty and start with '/'" << std::endl; return false; } if (path.back() != '/') { path += '/'; } std::set forbidden {" ", "/../", "/./", "\\"}; for (const auto& needle : forbidden) { if (path.find(needle) != std::string::npos) { std::cerr << "error: path should no contain any of the following " << "sequences of characters: \" \", \"/../\", \"/./\" or " << "\"\\\"" << std::endl; return false; } } return true; } //------------------------------------------------------------------------------ // Route command entrypoint //------------------------------------------------------------------------------ int com_route(char* arg) { if (wants_help(arg)) { com_route_help(); global_retc = EINVAL; return EINVAL; } RouteHelper route(gGlobalOpts); if (!route.ParseCommand(arg)) { com_route_help(); global_retc = EINVAL; return EINVAL; } global_retc = route.Execute(); return global_retc; } //------------------------------------------------------------------------------ // Print help message //------------------------------------------------------------------------------ void com_route_help() { std::ostringstream oss; oss << "Usage: route [ls|link|unlink]" << std::endl << " namespace routing to redirect clients to external instances" << std::endl << std::endl << " route ls []" << std::endl << " list all routes or the one matching for the given path" << std::endl << " * as the first character means the node is a master" << std::endl << " _ as the first character means the node is offline" << std::endl << std::endl << " route link [:[:]],..." << std::endl << " create routing from to destination host. If the xrd_port" << std::endl << " is omitted the default 1094 is used, if the http_port is omitted" << std::endl << " the default 8000 is used. Several dst_hosts can be specified by" << std::endl << " separating them with \",\". The redirection will go to the MGM" << std::endl << " from the specified list" << std::endl << " e.g route /eos/dummy/ foo.bar:1094:8000" << std::endl << std::endl << " route unlink " << std::endl << " remove routing matching path" << std::endl; std::cerr << oss.str() << std::endl; }