Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduced named network namespace for replayshell #106

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions src/frontend/replayshell.cc
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/* -*-mode:c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

#include <net/route.h>
#include <fcntl.h>

#include <vector>
#include <set>
#include <string>
#include <net/route.h>
#include <fcntl.h>

#include "util.hh"
#include "netdevice.hh"
Expand All @@ -16,6 +15,7 @@
#include "http_response.hh"
#include "dns_server.hh"
#include "exception.hh"
#include "network_namespace.hh"

#include "http_record.pb.h"

Expand Down Expand Up @@ -61,22 +61,31 @@ int main( int argc, char *argv[] )
/* get working directory */
const string working_directory { get_working_directory() };


/* chdir to result of getcwd just in case */
SystemCall( "chdir", chdir( working_directory.c_str() ) );

const string netns_name = string("mahimahi.") + to_string( getpid() );

NetworkNamespace network_namespace( netns_name );

/* Setup our own resolvconf with nameserver 8.8.8.8 */
network_namespace.create_resolvconf( "8.8.8.8" );

/* Switch to the newly created network namespace */
network_namespace.enter();

/* what command will we run inside the container? */
vector< string > command;
if ( argc == 2 ) {
command.push_back( shell_path() );
} else {

for ( int i = 2; i < argc; i++ ) {
command.push_back( argv[ i ] );
}
}

/* create a new network namespace */
SystemCall( "unshare", unshare( CLONE_NEWNET ) );

/* bring up localhost */
interface_ioctl( SIOCSIFFLAGS, "lo",
[] ( ifreq &ifr ) { ifr.ifr_flags = IFF_UP; } );
Expand Down Expand Up @@ -143,7 +152,6 @@ int main( int argc, char *argv[] )
const string interface_name = "nameserver" + to_string( server_num );
add_dummy_interface( interface_name, nameservers.at( server_num ) );
}

/* start dnsmasq */
event_loop.add_child_process( start_dnsmasq( dnsmasq_args ) );

Expand Down
3 changes: 2 additions & 1 deletion src/util/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ noinst_LIBRARIES = libutil.a

libutil_a_SOURCES = exception.hh ezio.cc ezio.hh \
file_descriptor.hh file_descriptor.cc netdevice.cc netdevice.hh \
timestamp.cc timestamp.hh \
timestamp.cc timestamp.hh \
network_namespace.cc network_namespace.hh \
child_process.hh child_process.cc signalfd.hh signalfd.cc \
socket.cc socket.hh address.cc address.hh \
system_runner.hh system_runner.cc nat.hh nat.cc \
Expand Down
168 changes: 168 additions & 0 deletions src/util/network_namespace.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/* -*-mode:c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "network_namespace.hh"

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sched.h>

#include "config.h"
#include "system_runner.hh"
#include "exception.hh"
#include "file_descriptor.hh"

using namespace std;

NetworkNamespace::NetworkNamespace( const std::string & name )
: name_(name), has_own_resolvconf_(false), has_entered_(false)
{
/* create a new network namespace */
run( { IP, "netns", "add", name_ } );
}


NetworkNamespace::~NetworkNamespace()
{
if ( has_entered_ ) {

char etc_netns_path[PATH_MAX];
char netns_name[PATH_MAX];
char etc_name[PATH_MAX];
struct dirent *entry;
DIR *dir;

snprintf( etc_netns_path, sizeof( etc_netns_path ), "%s/%s", NETNS_ETC_DIR.c_str(), name_.c_str() );
dir = opendir( etc_netns_path );
if (!dir )
return;

while ( ( entry = readdir(dir) ) != NULL ) {

if ( strcmp(entry->d_name, ".") == 0 )
continue;
if ( strcmp(entry->d_name, "..") == 0 )
continue;

snprintf(netns_name, sizeof(netns_name), "%s/%s", etc_netns_path, entry->d_name);
snprintf(etc_name, sizeof(etc_name), "/etc/%s", entry->d_name);

if ( umount( etc_name ) < 0 ) {
throw runtime_error( string("Unmounting... ") + netns_name + " -> " + etc_name + " failed: " + strerror(errno) + "\n" );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line causes the compile to fail on g++ 6.2. (Can't throw from a destructor.)

network_namespace.cc: In destructor ‘NetworkNamespace::~NetworkNamespace()’: network_namespace.cc:55:135: error: throw will always call terminate() [-Werror=terminate] . ") + netns_name + " -> " + etc_name + " failed: " + strerror(errno) + "\n" ); ^ network_namespace.cc:55:135: note: in C++11 destructors default to noexcept

}
}

}

if ( has_own_resolvconf_ ) {
const string netns_dir = NETNS_ETC_DIR + "/" + name_;
const string resolvconf_path = netns_dir + "/resolv.conf";

SystemCall("unlink", unlink ( resolvconf_path.c_str() ) );
SystemCall("unlink", rmdir ( netns_dir.c_str() ) );
}

/*char netns_path[PATH_MAX];
snprintf(netns_path, sizeof(netns_path), "%s/%s", NETNS_DIR.c_str(), name_.c_str());
umount2(netns_path, MNT_FORCE);
if (unlink(netns_path) < 0) {
throw runtime_error(string("Cannot remove namespace file manually \"")+netns_path+"\":" + strerror(errno) + "\n" );
}*/


run( { IP, "netns", "delete", name_ } );
}


void NetworkNamespace::create_resolvconf( const std::string & nameserver )
{
has_own_resolvconf_ = true;
/* create an individual resolv.conf for this network namespace */
const string netns_dir = NETNS_ETC_DIR + "/" + name_;
const string resolvconf_path = netns_dir + "/resolv.conf";

SystemCall("mkdir", mkdir( netns_dir.c_str() , S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fails for me on a default Ubuntu 16.10 installation (there's no NETNS_ETC_DIR, aka /etc/netns, so the mkdir fails). We might need more like a mkdir -p, except how do you safely clean that up at the end of the program?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For applications that are aware of network namespaces, the convention
is to look for global network configuration files first in
/etc/netns/NAME/ then in /etc/. For example, if you want a different
version of /etc/resolv.conf for a network namespace used to isolate
your vpn you would name it /etc/netns/myvpn/resolv.conf.

http://man7.org/linux/man-pages/man8/ip-netns.8.html

My bad. It isn't there on Ubuntu 16.04 per default as well. I guess i must have created the /etc/netns folder manually during working on an earlier prototype.
However, the way the code works right now i guess it does not matter where the resolv.conf resides as long as we can mount it later. So the question is adopt that naming pattern from the manpage or create a temporary directory?

Copy link
Collaborator

@keithw keithw Nov 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a tempfile is fine -- as I said on the other thread, I don't know of any "applications that are aware of network namespaces." glibc's resolver doesn't seem to know about this, and that's what most applications use. If we're going to use a bind mount (and a mount namespace) to replace resolv.conf inside the container, then I don't think this convention matters -- we could put it anywhere that can be reliably cleaned up at exit (normal or abnormal).


FileDescriptor fd( SystemCall( "open", open( resolvconf_path.c_str(),
O_WRONLY | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) ) ) ;

fd.write( "nameserver " + nameserver + "\n" );
}


void NetworkNamespace::enter()
{
char net_path[PATH_MAX];
int netns;

snprintf( net_path, sizeof(net_path), "%s/%s", NETNS_DIR.c_str(), name_.c_str() );
netns = open( net_path, O_RDONLY | O_CLOEXEC );

if ( netns < 0 ) {
throw runtime_error( "Cannot open network namespace \"" + name_ + "\": " + strerror(errno) + "\n " );
}

if ( setns(netns, CLONE_NEWNET) < 0 ) {
throw runtime_error( "setting the network namespace \"" + name_ + "\" failed: " + strerror(errno) + "\n " );
}
close( netns );

has_entered_ = true;


if ( unshare(CLONE_NEWNS) < 0 ) {
throw runtime_error( string("unshare failed: ") + strerror(errno) + "\n " );
}

/* Don't let any mounts propagate back to the parent */
/*
if ( mount( "", "/", "none", MS_SLAVE | MS_REC, NULL ) ) {
throw runtime_error( string("\"mount --make-rslave /\" failed: ") + strerror(errno) + "\n " );
}*/

/* Mount a version of /sys that describes the network namespace */
if ( umount2( "/sys", MNT_DETACH) < 0 ) {
throw runtime_error( string("umount of /sys failed: ") + strerror(errno) + "\n" );
}

if ( mount( name_.c_str(), "/sys", "sysfs", 0, NULL ) < 0 ) {
throw runtime_error( string("mount of /sys failed: ") + strerror(errno) + "\n" );
}


/* Setup bind mounts for config files in /etc */
char etc_netns_path[PATH_MAX];
char netns_name[PATH_MAX];
char etc_name[PATH_MAX];
struct dirent *entry;
DIR *dir;

snprintf( etc_netns_path, sizeof( etc_netns_path ), "%s/%s", NETNS_ETC_DIR.c_str(), name_.c_str() );
dir = opendir( etc_netns_path );
if (!dir )
return;

while ( ( entry = readdir(dir) ) != NULL ) {

if ( strcmp(entry->d_name, ".") == 0 )
continue;
if ( strcmp(entry->d_name, "..") == 0 )
continue;

snprintf(netns_name, sizeof(netns_name), "%s/%s", etc_netns_path, entry->d_name);
snprintf(etc_name, sizeof(etc_name), "/etc/%s", entry->d_name);

if ( mount( netns_name, etc_name, "none", MS_BIND, NULL ) < 0 ) {
throw runtime_error( string("Bind ") + netns_name + " -> " + etc_name + " failed: " + strerror(errno) + "\n" );
}
}

closedir( dir );


}
27 changes: 27 additions & 0 deletions src/util/network_namespace.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* -*-mode:c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

#ifndef NETNAMESPACE_HH
#define NETNAMESPACE_HH

#include <string>

class NetworkNamespace
{
private:
std::string name_;
bool has_own_resolvconf_;
bool has_entered_;

public:

const std::string NETNS_DIR = "/var/run/netns";
const std::string NETNS_ETC_DIR = "/etc/netns";

NetworkNamespace( const std::string & name );
~NetworkNamespace();

void create_resolvconf( const std::string & nameserver );
void enter( void );
};

#endif