// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*-
// Copyright (c) 2001-2007 International Computer Science Institute
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software")
// to deal in the Software without restriction, subject to the conditions
// listed in the XORP LICENSE file. These conditions include: you must
// preserve this copyright notice, and you cannot mention the copyright
// holders in advertising related to the Software without their permission.
// The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
// notice is a summary of the XORP LICENSE file; the license in that file is
// legally binding.
#ident "$XORP: xorp/fea/click_socket.cc,v 1.32 2007/02/16 22:45:36 pavlin Exp $"
#include "fea_module.h"
#include "libxorp/xorp.h"
#include "libxorp/xlog.h"
#include "libxorp/debug.h"
#include "libxorp/run_command.hh"
#include "libxorp/utils.hh"
#include <algorithm>
#ifdef HAVE_SYS_LINKER_H
#include <sys/linker.h>
#endif
#ifdef HAVE_SYS_MOUNT_H
#include <sys/mount.h>
#endif
#include "libcomm/comm_api.h"
#include "click_socket.hh"
const string ClickSocket::PROC_LINUX_MODULES_FILE = "/proc/modules";
const string ClickSocket::LINUX_COMMAND_LOAD_MODULE = "/sbin/insmod";
const string ClickSocket::LINUX_COMMAND_UNLOAD_MODULE = "/sbin/rmmod";
const string ClickSocket::CLICK_FILE_SYSTEM_TYPE = "click";
uint16_t ClickSocket::_instance_cnt = 0;
pid_t ClickSocket::_pid = getpid();
const TimeVal ClickSocket::USER_CLICK_STARTUP_MAX_WAIT_TIME = TimeVal(1, 0);
//
// Click Sockets communication with Click
//
ClickSocket::ClickSocket(EventLoop& eventloop)
: _eventloop(eventloop),
_seqno(0),
_instance_no(_instance_cnt++),
_is_enabled(false),
_duplicate_routes_to_kernel(false),
_is_kernel_click(false),
_is_user_click(false),
_kernel_click_install_on_startup(false),
_user_click_command_execute_on_startup(false),
_user_click_control_address(DEFAULT_USER_CLICK_CONTROL_ADDRESS()),
_user_click_control_socket_port(DEFAULT_USER_CLICK_CONTROL_SOCKET_PORT),
_user_click_run_command(NULL)
{
}
ClickSocket::~ClickSocket()
{
string error_msg;
if (stop(error_msg) != XORP_OK) {
XLOG_ERROR("Cannot stop the Click socket: %s", error_msg.c_str());
}
XLOG_ASSERT(_ol.empty());
}
int
ClickSocket::start(string& error_msg)
{
if (is_kernel_click() && !_kernel_fd.is_valid()) {
//
// Install kernel Click (if necessary)
//
if (_kernel_click_install_on_startup) {
string error_msg2;
// Load the kernel Click modules
if (load_kernel_click_modules(error_msg) != XORP_OK) {
unload_kernel_click_modules(error_msg2);
return (XORP_ERROR);
}
// Mount the Click file system
if (mount_click_file_system(error_msg) != XORP_OK) {
unload_kernel_click_modules(error_msg2);
return (XORP_ERROR);
}
}
#ifdef O_NONBLOCK
//
// Open the Click error file (for reading error messages)
//
string click_error_filename;
click_error_filename = _kernel_click_mount_directory + "/errors";
_kernel_fd = open(click_error_filename.c_str(), O_RDONLY | O_NONBLOCK);
if (!_kernel_fd.is_valid()) {
error_msg = c_format("Cannot open kernel Click error file %s: %s",
click_error_filename.c_str(),
strerror(errno));
return (XORP_ERROR);
}
#endif
}
if (is_user_click() && !_user_fd.is_valid()) {
//
// Execute the Click command (if necessary)
//
if (_user_click_command_execute_on_startup) {
// Compose the command and the arguments
string command = _user_click_command_file;
list<string> argument_list;
argument_list.push_back("-f");
argument_list.push_back(_user_click_startup_config_file);
argument_list.push_back("-p");
argument_list.push_back(c_format("%u",
_user_click_control_socket_port));
if (! _user_click_command_extra_arguments.empty()) {
list<string> l = split(_user_click_command_extra_arguments,
' ');
argument_list.insert(argument_list.end(), l.begin(), l.end());
}
if (execute_user_click_command(command, argument_list)
!= XORP_OK) {
error_msg = c_format("Could not execute the user-level Click");
return (XORP_ERROR);
}
}
//
// Open the socket
//
struct in_addr in_addr;
_user_click_control_address.copy_out(in_addr);
//
// TODO: XXX: get rid of this hackish mechanism of waiting
// pre-defined amount of time until the user-level Click program
// starts responding.
//
TimeVal max_wait_time = USER_CLICK_STARTUP_MAX_WAIT_TIME;
TimeVal curr_wait_time(0, 100000); // XXX: 100ms
TimeVal total_wait_time;
do {
//
// XXX: try-and-wait a number of times up to "max_wait_time",
// because the user-level Click program may not response
// immediately.
//
TimerList::system_sleep(curr_wait_time);
total_wait_time += curr_wait_time;
int in_progress = 0;
_user_fd = comm_connect_tcp4(&in_addr,
htons(_user_click_control_socket_port),
COMM_SOCK_BLOCKING, &in_progress);
if (_user_fd.is_valid())
break;
if (total_wait_time < max_wait_time) {
// XXX: exponentially increase the wait time
curr_wait_time += curr_wait_time;
if (total_wait_time + curr_wait_time > max_wait_time)
curr_wait_time = max_wait_time - total_wait_time;
XLOG_WARNING("Could not open user-level Click socket: %s. "
"Trying again...",
strerror(errno));
continue;
}
error_msg = c_format("Could not open user-level Click socket: %s",
strerror(errno));
terminate_user_click_command();
return (XORP_ERROR);
} while (true);
//
// Read the expected banner
//
vector<uint8_t> message;
string error_msg2;
if (force_read_message(_user_fd, message, error_msg2) != XORP_OK) {
error_msg = c_format("Could not read on startup from user-level "
"Click socket: %s", error_msg2.c_str());
terminate_user_click_command();
comm_close(_user_fd);
_user_fd.clear();
return (XORP_ERROR);
}
//
// Check the expected banner.
// The banner should look like: "Click::ControlSocket/1.1"
//
do {
string::size_type slash1, slash2, dot1, dot2;
string banner = string(reinterpret_cast<const char*>(&message[0]),
message.size());
string version;
int major, minor;
// Find the version number and check it.
slash1 = banner.find('/');
if (slash1 == string::npos) {
error_msg = c_format("Invalid user-level Click banner: %s",
banner.c_str());
goto error_label;
}
slash2 = banner.find('/', slash1 + 1);
if (slash2 != string::npos)
version = banner.substr(slash1 + 1, slash2 - slash1 - 1);
else
version = banner.substr(slash1 + 1);
dot1 = version.find('.');
if (dot1 == string::npos) {
error_msg = c_format("Invalid user-level Click version: %s",
version.c_str());
goto error_label;
}
dot2 = version.find('.', dot1 + 1);
major = atoi(version.substr(0, dot1).c_str());
if (dot2 != string::npos)
minor = atoi(version.substr(dot1 + 1, dot2 - dot1 - 1).c_str());
else
minor = atoi(version.substr(dot1 + 1).c_str());
if ((major < CLICK_MAJOR_VERSION)
|| ((major == CLICK_MAJOR_VERSION)
&& (minor < CLICK_MINOR_VERSION))) {
error_msg = c_format("Invalid user-level Click version: "
"expected at least %d.%d "
"(found %s)",
CLICK_MAJOR_VERSION, CLICK_MINOR_VERSION,
version.c_str());
goto error_label;
}
break;
error_label:
terminate_user_click_command();
comm_close(_user_fd);
_user_fd.clear();
return (XORP_ERROR);
} while (false);
//
// Add the socket to the event loop
//
if (_eventloop.add_ioevent_cb(_user_fd, IOT_READ,
callback(this, &ClickSocket::io_event))
== false) {
error_msg = c_format("Failed to add user-level Click socket "
"to EventLoop");
terminate_user_click_command();
comm_close(_user_fd);
_user_fd.clear();
return (XORP_ERROR);
}
}
return (XORP_OK);
}
int
ClickSocket::stop(string& error_msg)
{
#ifdef HOST_OS_WINDOWS
UNUSED(error_msg)
return (XORP_ERROR);
#else /* !HOST_OS_WINDOWS */
//
// XXX: First we should stop user-level Click, and then kernel-level Click.
// Otherwise, the user-level Click process may block the unmounting
// of the kernel-level Click file system. The reason for this blocking
// is unknown...
//
if (is_user_click()) {
terminate_user_click_command();
if (_user_fd >= 0) {
//
// Remove the socket from the event loop and close it
//
_eventloop.remove_ioevent_cb(_user_fd);
comm_close(_user_fd);
_user_fd.clear();
}
}
if (is_kernel_click()) {
//
// Close the Click error file (for reading error messages)
//
if (_kernel_fd.is_valid()) {
close(_kernel_fd);
_kernel_fd.clear();
}
if (unmount_click_file_system(error_msg) != XORP_OK) {
string error_msg2;
unload_kernel_click_modules(error_msg2);
return (XORP_ERROR);
}
if (unload_kernel_click_modules(error_msg) != XORP_OK) {
return (XORP_ERROR);
}
}
return (XORP_OK);
#endif /* HOST_OS_WINDOWS */
}
int
ClickSocket::load_kernel_click_modules(string& error_msg)
{
list<string>::iterator iter;
for (iter = _kernel_click_modules.begin();
iter != _kernel_click_modules.end();
++iter) {
const string& module_filename = *iter;
if (load_kernel_module(module_filename, error_msg) != XORP_OK)
return (XORP_ERROR);
}
return (XORP_OK);
}
int
ClickSocket::unload_kernel_click_modules(string& error_msg)
{
list<string>::reverse_iterator riter;
for (riter = _kernel_click_modules.rbegin();
riter != _kernel_click_modules.rend();
++riter) {
const string& module_filename = *riter;
if (unload_kernel_module(module_filename, error_msg) != XORP_OK)
return (XORP_ERROR);
}
return (XORP_OK);
}
int
ClickSocket::load_kernel_module(const string& module_filename,
string& error_msg)
{
if (module_filename.empty()) {
error_msg = c_format("Kernel module filename to load is empty");
return (XORP_ERROR);
}
if (find(_loaded_kernel_click_modules.begin(),
_loaded_kernel_click_modules.end(),
module_filename)
!= _loaded_kernel_click_modules.end()) {
return (XORP_OK); // Module already loaded
}
string module_name = kernel_module_filename2modulename(module_filename);
if (module_name.empty()) {
error_msg = c_format("Invalid kernel module filename: %s",
module_filename.c_str());
return (XORP_ERROR);
}
//
// Load the kernel module using system-specific mechanism
//
#ifdef HOST_OS_FREEBSD
//
// Test if the kernel module was installed already
//
if (kldfind(module_name.c_str()) >= 0) {
return (XORP_OK); // Module with the same name already loaded
}
//
// Load the kernel module
//
if (kldload(module_filename.c_str()) < 0) {
error_msg = c_format("Cannot load kernel module %s: %s",
module_filename.c_str(), strerror(errno));
return (XORP_ERROR);
}
_loaded_kernel_click_modules.push_back(module_filename);
return (XORP_OK);
#endif // HOST_OS_FREEBSD
#ifdef HOST_OS_LINUX
//
// Test if the kernel module was installed already
//
char buf[1024];
char name[1024];
FILE* fh = fopen(PROC_LINUX_MODULES_FILE.c_str(), "r");
if (fh == NULL) {
error_msg = c_format("Cannot open file %s for reading: %s",
PROC_LINUX_MODULES_FILE.c_str(), strerror(errno));
return (XORP_ERROR);
}
while (fgets(buf, sizeof(buf), fh) != NULL) {
char* n = name;
char* p = buf;
char* s = NULL;
// Get the module name: the first word in the line
do {
while (xorp_isspace(*p))
p++;
if (*p == '\0') {
s = NULL;
break;
}
while (*p) {
if (xorp_isspace(*p))
break;
*n++ = *p++;
}
*n++ = '\0';
s = p;
break;
} while (true);
if (s == NULL) {
XLOG_ERROR("%s: cannot get module name for line %s",
PROC_LINUX_MODULES_FILE.c_str(), buf);
continue;
}
if (module_name == string(name)) {
fclose(fh);
return (XORP_OK); // Module with the same name already loaded
}
}
fclose(fh);
//
// Load the kernel module
//
// XXX: unfortunately, Linux doesn't have a consistent system API
// for loading kernel modules, so we have to relay on user-land command
// to do this. Sigh...
//
string command_line = LINUX_COMMAND_LOAD_MODULE + " " + module_filename;
int ret_value = system(command_line.c_str());
if (ret_value != 0) {
if (ret_value < 0) {
error_msg = c_format("Cannot execute system command '%s': %s",
command_line.c_str(), strerror(errno));
} else {
error_msg = c_format("Executing system command '%s' "
"returned value '%d'",
command_line.c_str(), ret_value);
}
return (XORP_ERROR);
}
_loaded_kernel_click_modules.push_back(module_filename);
return (XORP_OK);
#endif // HOST_OS_LINUX
#ifdef HOST_OS_MACOSX
// TODO: implement it
error_msg = c_format("No mechanism to load a kernel module");
return (XORP_ERROR);
#endif // HOST_OS_MACOSX
#ifdef HOST_OS_NETBSD
// TODO: implement it
error_msg = c_format("No mechanism to load a kernel module");
return (XORP_ERROR);
#endif // HOST_OS_NETBSD
#ifdef HOST_OS_OPENBSD
// TODO: implement it
error_msg = c_format("No mechanism to load a kernel module");
return (XORP_ERROR);
#endif // HOST_OS_OPENBSD
#ifdef HOST_OS_SOLARIS
// TODO: implement it
error_msg = c_format("No mechanism to load a kernel module");
return (XORP_ERROR);
#endif // HOST_OS_SOLARIS
error_msg = c_format("No mechanism to load a kernel module");
return (XORP_ERROR);
}
int
ClickSocket::unload_kernel_module(const string& module_filename,
string& error_msg)
{
if (module_filename.empty()) {
error_msg = c_format("Kernel module filename to unload is empty");
return (XORP_ERROR);
}
if (find(_loaded_kernel_click_modules.begin(),
_loaded_kernel_click_modules.end(),
module_filename)
== _loaded_kernel_click_modules.end()) {
return (XORP_OK); // Module not loaded
}
string module_name = kernel_module_filename2modulename(module_filename);
if (module_name.empty()) {
error_msg = c_format("Invalid kernel module filename: %s",
module_filename.c_str());
return (XORP_ERROR);
}
//
// Unload the kernel module using system-specific mechanism
//
#ifdef HOST_OS_FREEBSD
//
// Find the kernel module ID.
//
int module_id = kldfind(module_name.c_str());
if (module_id < 0) {
error_msg = c_format("Cannot unload kernel module %s: "
"module ID not found: %s",
module_filename.c_str(), strerror(errno));
return (XORP_ERROR);
}
//
// Unload the kernel module
//
if (kldunload(module_id) < 0) {
error_msg = c_format("Cannot unload kernel module %s: %s",
module_filename.c_str(), strerror(errno));
return (XORP_ERROR);
}
// Remove the module filename from the list of loaded modules
list<string>::iterator iter;
iter = find(_loaded_kernel_click_modules.begin(),
_loaded_kernel_click_modules.end(),
module_filename);
XLOG_ASSERT(iter != _loaded_kernel_click_modules.end());
_loaded_kernel_click_modules.erase(iter);
return (XORP_OK);
#endif // HOST_OS_FREEBSD
#ifdef HOST_OS_LINUX
//
// Unload the kernel module
//
// XXX: unfortunately, Linux doesn't have a consistent system API
// for loading kernel modules, so we have to relay on user-land command
// to do this. Sigh...
//
string command_line = LINUX_COMMAND_UNLOAD_MODULE + " " + module_name;
int ret_value = system(command_line.c_str());
if (ret_value != 0) {
if (ret_value < 0) {
error_msg = c_format("Cannot execute system command '%s': %s",
command_line.c_str(), strerror(errno));
} else {
error_msg = c_format("Executing system command '%s' "
"returned value '%d'",
command_line.c_str(), ret_value);
}
return (XORP_ERROR);
}
// Remove the module filename from the list of loaded modules
list<string>::iterator iter;
iter = find(_loaded_kernel_click_modules.begin(),
_loaded_kernel_click_modules.end(),
module_filename);
XLOG_ASSERT(iter != _loaded_kernel_click_modules.end());
_loaded_kernel_click_modules.erase(iter);
return (XORP_OK);
#endif // HOST_OS_LINUX
#ifdef HOST_OS_MACOSX
// TODO: implement it
error_msg = c_format("No mechanism to unload a kernel module");
return (XORP_ERROR);
#endif // HOST_OS_MACOSX
#ifdef HOST_OS_NETBSD
// TODO: implement it
error_msg = c_format("No mechanism to unload a kernel module");
return (XORP_ERROR);
#endif // HOST_OS_NETBSD
#ifdef HOST_OS_OPENBSD
// TODO: implement it
error_msg = c_format("No mechanism to unload a kernel module");
return (XORP_ERROR);
#endif // HOST_OS_OPENBSD
#ifdef HOST_OS_SOLARIS
// TODO: implement it
error_msg = c_format("No mechanism to unload a kernel module");
return (XORP_ERROR);
#endif // HOST_OS_SOLARIS
error_msg = c_format("No mechanism to unload a kernel module");
return (XORP_ERROR);
}
string
ClickSocket::kernel_module_filename2modulename(const string& module_filename)
{
string filename, module_name;
string::size_type slash, dot;
list<string> suffix_list;
// Find the file name after the last '/'
slash = module_filename.rfind('/');
if (slash == string::npos)
filename = module_filename;
else
filename = module_filename.substr(slash + 1);
//
// Find the module name by excluding the suffix after the last '.'
// if that is a well-known suffix (e.g., ".o" or ".ko").
//
suffix_list.push_back(".o");
suffix_list.push_back(".ko");
module_name = filename;
list<string>::iterator iter;
for (iter = suffix_list.begin(); iter != suffix_list.end(); ++iter) {
string suffix = *iter;
dot = filename.rfind(suffix);
if (dot != string::npos) {
if (filename.substr(dot) == suffix) {
module_name = filename.substr(0, dot);
break;
}
}
}
return (module_name);
}
int
ClickSocket::mount_click_file_system(string& error_msg)
{
#if defined(HOST_OS_WINDOWS)
// Whilst Cygwin has a mount(), it is very different.
// Windows itself has no mount().
UNUSED(error_msg);
return (XORP_ERROR);
#else
if (_kernel_click_mount_directory.empty()) {
error_msg = c_format("Kernel Click mount directory is empty");
return (XORP_ERROR);
}
if (! _mounted_kernel_click_mount_directory.empty()) {
if (_kernel_click_mount_directory
== _mounted_kernel_click_mount_directory) {
return (XORP_OK); // Directory already mounted
}
error_msg = c_format("Cannot mount Click file system on directory %s: "
"Click file system already mounted on "
"directory %s",
_kernel_click_mount_directory.c_str(),
_mounted_kernel_click_mount_directory.c_str());
return (XORP_ERROR);
}
//
// Test if the Click file system has been installed already.
//
// We do this by tesing whether we can access a number of Click files
// within the Click file system.
//
list<string> click_files;
list<string>::iterator iter;
size_t files_found = 0;
click_files.push_back("/config");
click_files.push_back("/flatconfig");
click_files.push_back("/packages");
click_files.push_back("/version");
for (iter = click_files.begin(); iter != click_files.end(); ++iter) {
string click_filename = _kernel_click_mount_directory + *iter;
if (access(click_filename.c_str(), R_OK) == 0) {
files_found++;
}
}
if (files_found > 0) {
if (files_found == click_files.size()) {
return (XORP_OK); // Directory already mounted
}
error_msg = c_format("Click file system mount directory contains "
"some Click files");
return (XORP_ERROR);
}
//
// XXX: Linux has different mount(2) API, hence we need to take
// care of this.
//
int ret_value = -1;
#ifdef HOST_OS_LINUX
ret_value = mount("none", _kernel_click_mount_directory.c_str(),
CLICK_FILE_SYSTEM_TYPE.c_str(), 0, 0);
#else // ! HOST_OS_LINUX
ret_value = mount(CLICK_FILE_SYSTEM_TYPE.c_str(),
_kernel_click_mount_directory.c_str(), 0, 0);
#endif // ! HOST_OS_LINUX
if (ret_value != 0) {
error_msg = c_format("Cannot mount Click file system "
"on directory %s: %s",
_kernel_click_mount_directory.c_str(),
strerror(errno));
return (XORP_ERROR);
}
_mounted_kernel_click_mount_directory = _kernel_click_mount_directory;
return (XORP_OK);
#endif // HOST_OS_WINDOWS
}
int
ClickSocket::unmount_click_file_system(string& error_msg)
{
#ifdef HOST_OS_WINDOWS
UNUSED(error_msg);
return (XORP_OK);
#else
if (_mounted_kernel_click_mount_directory.empty())
return (XORP_OK); // Directory not mounted
//
// XXX: Linux and Solaris don't have unmount(2). Instead, they have
// umount(2), hence we need to take care of this.
//
int ret_value = -1;
#if defined(HOST_OS_LINUX) || defined(HOST_OS_SOLARIS)
ret_value = umount(_mounted_kernel_click_mount_directory.c_str());
#else
ret_value = unmount(_mounted_kernel_click_mount_directory.c_str(), 0);
#endif
if (ret_value != 0) {
error_msg = c_format("Cannot unmount Click file system "
"from directory %s: %s",
_mounted_kernel_click_mount_directory.c_str(),
strerror(errno));
return (XORP_ERROR);
}
_mounted_kernel_click_mount_directory.erase();
return (XORP_OK);
#endif // HOST_OS_WINDOWS
}
int
ClickSocket::execute_user_click_command(const string& command,
const list<string>& argument_list)
{
if (_user_click_run_command != NULL)
return (XORP_ERROR); // XXX: command is already running
_user_click_run_command = new RunCommand(_eventloop,
command,
argument_list,
callback(this, &ClickSocket::user_click_command_stdout_cb),
callback(this, &ClickSocket::user_click_command_stderr_cb),
callback(this, &ClickSocket::user_click_command_done_cb),
false /* redirect_stderr_to_stdout */);
if (_user_click_run_command->execute() != XORP_OK) {
delete _user_click_run_command;
_user_click_run_command = NULL;
return (XORP_ERROR);
}
return (XORP_OK);
}
void
ClickSocket::terminate_user_click_command()
{
if (_user_click_run_command != NULL) {
delete _user_click_run_command;
_user_click_run_command = NULL;
}
}
void
ClickSocket::user_click_command_stdout_cb(RunCommand* run_command,
const string& output)
{
XLOG_ASSERT(_user_click_run_command == run_command);
XLOG_INFO("User-level Click stdout output: %s", output.c_str());
}
void
ClickSocket::user_click_command_stderr_cb(RunCommand* run_command,
const string& output)
{
XLOG_ASSERT(_user_click_run_command == run_command);
XLOG_ERROR("User-level Click stderr output: %s", output.c_str());
}
void
ClickSocket::user_click_command_done_cb(RunCommand* run_command, bool success,
const string& error_msg)
{
XLOG_ASSERT(_user_click_run_command == run_command);
if (! success) {
//
// XXX: if error_msg is empty, the real error message has been
// printed already when it was received on stderr.
//
string msg = c_format("User-level Click command (%s) failed",
run_command->command().c_str());
if (error_msg.size())
msg += c_format(": %s", error_msg.c_str());
XLOG_ERROR("%s", msg.c_str());
}
delete _user_click_run_command;
_user_click_run_command = NULL;
}
int
ClickSocket::write_config(const string& element, const string& handler,
bool has_kernel_config, const string& kernel_config,
bool has_user_config, const string& user_config,
string& error_msg)
{
#ifdef HOST_OS_WINDOWS
UNUSED(element);
UNUSED(handler);
UNUSED(has_kernel_config);
UNUSED(kernel_config);
UNUSED(has_user_config);
UNUSED(user_config);
UNUSED(error_msg);
return (0);
#else /* !HOST_OS_WINDOWS */
if (is_kernel_click() && has_kernel_config) {
//
// Prepare the output handler name
//
string output_handler = element;
if (! output_handler.empty())
output_handler += "/" + handler;
else
output_handler = handler;
output_handler = _kernel_click_mount_directory + "/" + output_handler;
//
// Prepare the socket to write the configuration
//
int openflags = O_WRONLY | O_TRUNC;
#ifdef O_FSYNC
openflags |= O_FSYNC;
#endif
XorpFd fd = ::open(output_handler.c_str(), openflags);
if (!fd.is_valid()) {
error_msg = c_format("Cannot open kernel Click handler '%s' "
"for writing: %s",
output_handler.c_str(), strerror(errno));
return (XORP_ERROR);
}
//
// Write the configuration
//
if (::write(fd, kernel_config.c_str(), kernel_config.size())
!= static_cast<ssize_t>(kernel_config.size())) {
error_msg = c_format("Error writing to kernel Click "
"handler '%s': %s",
output_handler.c_str(), strerror(errno));
return (XORP_ERROR);
}
// XXX: we must close the socket before checking the result
int close_ret_value = close(fd);
//
// Check the command status
//
char error_buf[8 * 1024];
int error_bytes;
error_bytes = ::read(_kernel_fd, error_buf, sizeof(error_buf));
if (error_bytes < 0) {
error_msg = c_format("Error verifying the command status after "
"writing to kernel Click handler: %s",
strerror(errno));
return (XORP_ERROR);
}
if (error_bytes > 0) {
error_msg = c_format("Kernel Click command error: %s", error_buf);
return (XORP_ERROR);
}
if (close_ret_value < 0) {
//
// XXX: If the close() system call returned an error, this is
// an indication that the write to the Click handler failed.
//
// Note that typically we should have caught any errors by
// reading the "<click>/errors" handler, so the check for
// the return value of close() is the last line of defense.
//
// The errno should be EINVAL, but we are liberal about this
// additional check.
//
error_msg = c_format("Kernel Click command error: unknown");
return (XORP_ERROR);
}
}
if (is_user_click() && has_user_config) {
//
// Prepare the output handler name
//
string output_handler = element;
if (! output_handler.empty())
output_handler += "." + handler;
else
output_handler = handler;
//
// Prepare the configuration to write
//
string config = c_format("WRITEDATA %s %u\n",
output_handler.c_str(),
XORP_UINT_CAST(user_config.size()));
config += user_config;
//
// Write the configuration
//
if (ClickSocket::write(_user_fd, config.c_str(), config.size())
!= static_cast<ssize_t>(config.size())) {
error_msg = c_format("Error writing to user-level "
"Click socket: %s",
strerror(errno));
return (XORP_ERROR);
}
//
// Check the command status
//
bool is_warning, is_error;
string command_warning, command_error;
if (check_user_command_status(is_warning, command_warning,
is_error, command_error,
error_msg)
!= XORP_OK) {
error_msg = c_format("Error verifying the command status after "
"writing to user-level Click socket: %s",
error_msg.c_str());
return (XORP_ERROR);
}
if (is_warning) {
XLOG_WARNING("User-level Click command warning: %s",
command_warning.c_str());
}
if (is_error) {
error_msg = c_format("User-level Click command error: %s",
command_error.c_str());
return (XORP_ERROR);
}
}
return (XORP_OK);
#endif /* HOST_OS_WINDOWS */
}
ssize_t
ClickSocket::write(XorpFd fd, const void* data, size_t nbytes)
{
#ifdef HOST_OS_WINDOWS
UNUSED(fd);
UNUSED(data);
UNUSED(nbytes);
return (0);
#else
_seqno++;
return ::write(fd, data, nbytes);
#endif
}
int
ClickSocket::check_user_command_status(bool& is_warning,
string& command_warning,
bool& is_error,
string& command_error,
string& error_msg)
{
vector<uint8_t> buffer;
is_warning = false;
is_error = false;
if (force_read_message(_user_fd, buffer, error_msg) != XORP_OK)
return (XORP_ERROR);
//
// Split the message into lines
//
string buffer_str = string(reinterpret_cast<char *>(&buffer[0]));
list<string> lines;
do {
string::size_type idx = buffer_str.find("\n");
if (idx == string::npos) {
if (! buffer_str.empty())
lines.push_back(buffer_str);
break;
}
string line = buffer_str.substr(0, idx + 1);
lines.push_back(line);
buffer_str = buffer_str.substr(idx + 1);
} while (true);
//
// Parse the message line-by-line
//
list<string>::const_iterator iter;
bool is_last_command_response = false;
for (iter = lines.begin(); iter != lines.end(); ++iter) {
const string& line = *iter;
if (is_last_command_response)
break;
if (line.size() < CLICK_COMMAND_RESPONSE_MIN_SIZE) {
error_msg = c_format(
"User-level Click command line response is too short "
"(expected min size %u received %u): %s",
XORP_UINT_CAST(CLICK_COMMAND_RESPONSE_MIN_SIZE),
XORP_UINT_CAST(line.size()),
line.c_str());
return (XORP_ERROR);
}
//
// Get the error code
//
char separator = line[CLICK_COMMAND_RESPONSE_CODE_SEPARATOR_INDEX];
if ((separator != ' ') && (separator != '-')) {
error_msg = c_format("Invalid user-level Click command line "
"response (missing code separator): %s",
line.c_str());
return (XORP_ERROR);
}
int error_code = atoi(line.substr(0, CLICK_COMMAND_RESPONSE_CODE_SEPARATOR_INDEX).c_str());
if (separator == ' ')
is_last_command_response = true;
//
// Test the error code
//
if (error_code == CLICK_COMMAND_CODE_OK)
continue;
if ((error_code >= CLICK_COMMAND_CODE_WARNING_MIN)
&& (error_code <= CLICK_COMMAND_CODE_WARNING_MAX)) {
is_warning = true;
command_warning += line;
continue;
}
if ((error_code >= CLICK_COMMAND_CODE_ERROR_MIN)
&& (error_code <= CLICK_COMMAND_CODE_ERROR_MAX)) {
is_error = true;
command_error += line;
continue;
}
// Unknown error code
error_msg = c_format("Unknown user-level Click error code: %s",
line.c_str());
return (XORP_ERROR);
}
return (XORP_OK);
}
int
ClickSocket::force_read(XorpFd fd, string& error_msg)
{
vector<uint8_t> message;
if (force_read_message(fd, message, error_msg) != XORP_OK)
return (XORP_ERROR);
//
// Notify observers
//
for (ObserverList::iterator i = _ol.begin(); i != _ol.end(); i++) {
(*i)->clsock_data(&message[0], message.size());
}
return (XORP_OK);
}
int
ClickSocket::force_read_message(XorpFd fd, vector<uint8_t>& message,
string& error_msg)
{
#ifdef HOST_OS_WINDOWS
UNUSED(fd);
UNUSED(message);
UNUSED(error_msg);
return (XORP_ERROR);
#else /* !HOST_OS_WINDOWS */
vector<uint8_t> buffer(CLSOCK_BYTES);
for ( ; ; ) {
ssize_t got;
// Find how much data is queued in the first message
do {
got = recv(fd, XORP_SOCKOPT_CAST(&buffer[0]),
buffer.size(),
#ifdef MSG_DONTWAIT
MSG_DONTWAIT |
#endif
MSG_PEEK);
if ((got < 0) && (errno == EINTR))
continue; // XXX: the receive was interrupted by a signal
if ((got < 0) || (got < (ssize_t)buffer.size()))
break; // The buffer is big enough
buffer.resize(buffer.size() + CLSOCK_BYTES);
} while (true);
got = read(fd, &buffer[0], buffer.size());
if (got < 0) {
if (errno == EINTR)
continue;
error_msg = c_format("Click socket read error: %s",
strerror(errno));
return (XORP_ERROR);
}
message.resize(got);
memcpy(&message[0], &buffer[0], got);
break;
}
return (XORP_OK);
#endif /* HOST_OS_WINDOWS */
}
void
ClickSocket::io_event(XorpFd fd, IoEventType type)
{
string error_msg;
XLOG_ASSERT((fd == _kernel_fd) || (fd == _user_fd));
XLOG_ASSERT(type == IOT_READ);
if (force_read(fd, error_msg) != XORP_OK) {
XLOG_ERROR("Error force_read() from Click socket: %s",
error_msg.c_str());
}
}
//
// Observe Click sockets activity
//
struct ClickSocketPlumber {
typedef ClickSocket::ObserverList ObserverList;
static void
plumb(ClickSocket& r, ClickSocketObserver* o)
{
ObserverList& ol = r._ol;
ObserverList::iterator i = find(ol.begin(), ol.end(), o);
debug_msg("Plumbing ClickSocketObserver %p to ClickSocket%p\n",
o, &r);
XLOG_ASSERT(i == ol.end());
ol.push_back(o);
}
static void
unplumb(ClickSocket& r, ClickSocketObserver* o)
{
ObserverList& ol = r._ol;
debug_msg("Unplumbing ClickSocketObserver %p from "
"ClickSocket %p\n", o, &r);
ObserverList::iterator i = find(ol.begin(), ol.end(), o);
XLOG_ASSERT(i != ol.end());
ol.erase(i);
}
};
ClickSocketObserver::ClickSocketObserver(ClickSocket& cs)
: _cs(cs)
{
ClickSocketPlumber::plumb(cs, this);
}
ClickSocketObserver::~ClickSocketObserver()
{
ClickSocketPlumber::unplumb(_cs, this);
}
ClickSocket&
ClickSocketObserver::click_socket()
{
return _cs;
}
ClickSocketReader::ClickSocketReader(ClickSocket& cs)
: ClickSocketObserver(cs),
_cs(cs),
_cache_valid(false),
_cache_seqno(0)
{
}
ClickSocketReader::~ClickSocketReader()
{
}
/**
* Force the reader to receive kernel-level Click data from the specified
* Click socket.
*
* @param cs the Click socket to receive the data from.
* @param seqno the sequence number of the data to receive.
* @param error_msg the error message (if error).
* @return XORP_OK on success, otherwise XORP_ERROR.
*/
int
ClickSocketReader::receive_kernel_click_data(ClickSocket& cs, uint32_t seqno,
string& error_msg)
{
_cache_seqno = seqno;
_cache_valid = false;
while (_cache_valid == false) {
if (cs.force_kernel_click_read(error_msg) != XORP_OK)
return (XORP_ERROR);
}
return (XORP_OK);
}
/**
* Force the reader to receive user-level Click data from the specified
* Click socket.
*
* @param cs the Click socket to receive the data from.
* @param seqno the sequence number of the data to receive.
* @param error_msg the error message (if error).
* @return XORP_OK on success, otherwise XORP_ERROR.
*/
int
ClickSocketReader::receive_user_click_data(ClickSocket& cs, uint32_t seqno,
string& error_msg)
{
_cache_seqno = seqno;
_cache_valid = false;
while (_cache_valid == false) {
if (cs.force_user_click_read(error_msg) != XORP_OK)
return (XORP_ERROR);
}
return (XORP_OK);
}
/**
* Receive data from the Click socket.
*
* Note that this method is called asynchronously when the Click socket
* has data to receive, therefore it should never be called directly by
* anything else except the Click socket facility itself.
*
* @param data the buffer with the received data.
* @param nbytes the number of bytes in the @param data buffer.
*/
void
ClickSocketReader::clsock_data(const uint8_t* data, size_t nbytes)
{
//
// Copy data that has been requested to be cached
//
_cache_data = string(reinterpret_cast<const char*>(data), nbytes);
// memcpy(&_cache_data[0], data, nbytes);
_cache_valid = true;
}
syntax highlighted by Code2HTML, v. 0.9.1