const struct dhcp_msg *dhclient_get_config(const struct dhclient *);
int dhclient_configure_netdev(struct dhclient *);
+int dhclient_update_resolv_conf(struct dhclient *);
void dhclient_run(struct dhclient *);
void dhclient_wait(struct dhclient *);
#include <limits.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
#include <time.h>
+#include <unistd.h>
#include "buffer.h"
#include "csum.h"
#include "dhcp.h"
return error;
}
+
+/* If 'cli' is bound and the binding includes DNS domain parameters, updates
+ * /etc/resolv.conf will be updated to match the received parameters. Returns
+ * 0 if successful, otherwise a positive errno value. */
+int
+dhclient_update_resolv_conf(struct dhclient *cli)
+{
+ uint32_t dns_server;
+ char *domain_name;
+ bool has_domain_name;
+ char new_name[128];
+ FILE *old, *new;
+ int i;
+
+ if (!dhclient_is_bound(cli)) {
+ return 0;
+ }
+ if (!dhcp_msg_get_ip(cli->binding, DHCP_CODE_DNS_SERVER, 0, &dns_server)) {
+ VLOG_DBG("binding does not include any DNS servers");
+ return 0;
+ }
+
+ sprintf(new_name, "/etc/resolv.conf.tmp%ld", (long int) getpid());
+ new = fopen(new_name, "w");
+ if (!new) {
+ VLOG_WARN("%s: create: %s", new_name, strerror(errno));
+ return errno;
+ }
+
+ domain_name = dhcp_msg_get_string(cli->binding, DHCP_CODE_DOMAIN_NAME);
+ has_domain_name = domain_name != NULL;
+ if (domain_name) {
+ if (strspn(domain_name, "-_.0123456789abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ") == strlen(domain_name)) {
+ fprintf(new, "domain %s\n", domain_name);
+ } else {
+ VLOG_WARN("ignoring invalid domain name %s", domain_name);
+ has_domain_name = false;
+ }
+ } else {
+ VLOG_DBG("binding does not include domain name");
+ }
+ free(domain_name);
+
+ for (i = 0; dhcp_msg_get_ip(cli->binding, DHCP_CODE_DNS_SERVER,
+ i, &dns_server); i++) {
+ fprintf(new, "nameserver "IP_FMT"\n", IP_ARGS(&dns_server));
+ }
+
+ old = fopen("/etc/resolv.conf", "r");
+ if (old) {
+ char line[128];
+
+ while (fgets(line, sizeof line, old)) {
+ char *kw = xmemdup0(line, strcspn(line, " \t\r\n"));
+ if (strcmp(kw, "nameserver")
+ && (!has_domain_name
+ || (strcmp(kw, "domain") && strcmp(kw, "search")))) {
+ fputs(line, new);
+ }
+ free(kw);
+ }
+ fclose(old);
+ } else {
+ VLOG_DBG("/etc/resolv.conf: open: %s", strerror(errno));
+ }
+
+ if (fclose(new) < 0) {
+ VLOG_WARN("%s: close: %s", new_name, strerror(errno));
+ return errno;
+ }
+
+ if (rename(new_name, "/etc/resolv.conf") < 0) {
+ VLOG_WARN("failed to rename %s to /etc/resolv.conf: %s",
+ new_name, strerror(errno));
+ return errno;
+ }
+
+ return 0;
+}
\f
/* DHCP protocol. */
When controller discovery is not performed, this option has no effect.
+.TP
+\fB--no-resolv-conf\fR
+When \fBsecchan\fR performs controller discovery (see \fBCONTACTING
+THE CONTROLLER\fR, above, for more information about controller
+discovery), by default it overwrites the system's
+\fB/etc/resolv.conf\fR with domain information and DNS servers
+obtained via DHCP. If the location of the controller is specified
+using a hostname, rather than an IP address, and the network's DNS
+servers ever change, this behavior is essential. But because it also
+interferes with any administrator or process that manages
+\fB/etc/resolv.conf\fR, when this option is specified, \fBsecchan\fR
+will not modify \fB/etc/resolv.conf\fR.
+
+\fBsecchan\fR will only modify \fBresolv.conf\fR if the DHCP response
+that it receives specifies one or more DNS servers.
+
+When controller discovery is not performed, this option has no effect.
+
.TP
\fB-f\fR, \fB--fail=\fR[\fBopen\fR|\fBclosed\fR]
The controller is, ordinarily, responsible for setting up all flows on
static const char *accept_controller_re;
static regex_t accept_controller_regex;
+/* --no-resolv-conf: Update resolv.conf upon successful controller
+ * discovery? */
+static bool update_resolv_conf = true;
+
static void parse_options(int argc, char *argv[]);
static void usage(void) NO_RETURN;
dhclient_run(dhcp);
if (dhclient_changed(dhcp)) {
dhclient_configure_netdev(dhcp);
- free(controller_name);
+ if (update_resolv_conf) {
+ dhclient_update_resolv_conf(dhcp);
+ }
+
if (dhclient_is_bound(dhcp)) {
+ free(controller_name);
controller_name = dhcp_msg_get_string(
dhclient_get_config(dhcp),
DHCP_CODE_OFP_CONTROLLER_VCONN);
} else if (controller_name) {
VLOG_WARN("%s: discover controller no longer available",
controller_name);
+ free(controller_name);
controller_name = NULL;
rconn_disconnect(remote_rconn);
}
{
enum {
OPT_ACCEPT_VCONN = UCHAR_MAX + 1,
+ OPT_NO_RESOLV_CONF,
OPT_INACTIVITY_PROBE,
OPT_MAX_IDLE,
OPT_MAX_BACKOFF
};
static struct option long_options[] = {
{"accept-vconn", required_argument, 0, OPT_ACCEPT_VCONN},
+ {"no-resolv-conf", no_argument, 0, OPT_NO_RESOLV_CONF},
{"fail", required_argument, 0, 'f'},
{"inactivity-probe", required_argument, 0, OPT_INACTIVITY_PROBE},
{"max-idle", required_argument, 0, OPT_MAX_IDLE},
: xasprintf("^%s", optarg));
break;
+ case OPT_NO_RESOLV_CONF:
+ update_resolv_conf = false;
+ break;
+
case 'f':
if (!strcmp(optarg, "open")) {
fail_mode = FAIL_OPEN;
"omitted, then secchan performs controller autodiscovery.\n",
program_name, program_name);
vconn_usage(true, true);
- printf("\nNetworking options:\n"
+ printf("\nController discovery options:\n"
" --accept-vconn=REGEX accept matching discovered controllers\n"
+ " --no-resolv-conf do not update /etc/resolv.conf\n"
+ "\nNetworking options:\n"
" -f, --fail=open|closed when controller connection fails:\n"
" closed: drop all packets\n"
" open (default): act as learning switch\n"
* vendor class string is included. */
static const char *vendor_class;
+/* --no-resolv-conf: Update /etc/resolv.conf to match DHCP reply? */
+static bool update_resolv_conf = true;
+
static void parse_options(int argc, char *argv[]);
static void usage(void);
static void release(void *cli_);
dhclient_run(cli);
if (dhclient_changed(cli)) {
dhclient_configure_netdev(cli);
+ if (update_resolv_conf) {
+ dhclient_update_resolv_conf(cli);
+ }
}
dhclient_wait(cli);
fatal_signal_unblock();
{
enum {
OPT_REQUEST_IP = UCHAR_MAX + 1,
- OPT_VENDOR_CLASS
+ OPT_VENDOR_CLASS,
+ OPT_NO_RESOLV_CONF
};
static struct option long_options[] = {
{"request-ip", required_argument, 0, OPT_REQUEST_IP },
{"vendor-class", required_argument, 0, OPT_VENDOR_CLASS },
+ {"no-resolv-conf", no_argument, 0, OPT_NO_RESOLV_CONF},
{"verbose", optional_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'V'},
vendor_class = optarg;
break;
+ case OPT_NO_RESOLV_CONF:
+ update_resolv_conf = false;
+ break;
+
case 'h':
usage();
" do not request a specific IP)\n"
" --vendor-class=STRING use STRING as vendor class (default:\n"
" none); use OpenFlow to imitate secchan\n"
+ " --no-resolv-conf do not update /etc/resolv.conf\n"
"\nOther options:\n"
" -v, --verbose=MODULE[:FACILITY[:LEVEL]] set logging levels\n"
" -v, --verbose set maximum verbosity level\n"
/* Configure network device. */
if (!exit_without_bind) {
dhclient_configure_netdev(iface->dhcp);
+ dhclient_update_resolv_conf(iface->dhcp);
}
if (is_bound) {