[or-cvs] Add new config.c function to set options that can fail, and...

Nick Mathewson nickm at seul.org
Wed Sep 14 02:36:31 UTC 2005


Update of /home/or/cvsroot/tor/src/or
In directory moria:/tmp/cvs-serv25809/src/or

Modified Files:
	config.c connection.c control.c main.c or.h 
Log Message:
Add new config.c function to set options that can fail, and roll back if they do.  This should solve the setconf-an-impossible-port bug.

Index: config.c
===================================================================
RCS file: /home/or/cvsroot/tor/src/or/config.c,v
retrieving revision 1.419
retrieving revision 1.420
diff -u -d -r1.419 -r1.420
--- config.c	14 Sep 2005 02:35:06 -0000	1.419
+++ config.c	14 Sep 2005 02:36:29 -0000	1.420
@@ -266,6 +266,7 @@
                           or_options_t *o1, or_options_t *o2, const char *name);
 static or_options_t *options_dup(config_format_t *fmt, or_options_t *old);
 static int options_validate(or_options_t *options);
+static int options_act_reversible(or_options_t *old_options);
 static int options_act(or_options_t *old_options);
 static int options_transition_allowed(or_options_t *old, or_options_t *new);
 static int options_transition_affects_workers(or_options_t *old_options,
@@ -363,19 +364,25 @@
  * their current value; take action based on the new value; free the old value
  * as necessary.
  */
-void
+int
 set_options(or_options_t *new_val)
 {
   or_options_t *old_options = global_options;
   global_options = new_val;
   /* Note that we pass the *old* options below, for comparison. It
    * pulls the new options directly out of global_options. */
+  if (options_act_reversible(old_options)<0) {
+    global_options = old_options;
+    return -1;
+  }
   if (options_act(old_options) < 0) { /* acting on the options failed. die. */
     log_fn(LOG_ERR,"Acting on config options left us in a broken state. Dying.");
     exit(1);
   }
   if (old_options)
     config_free(&options_format, old_options);
+
+  return 0;
 }
 
 void
@@ -403,6 +410,98 @@
  * things we do should survive being done repeatedly.  If present,
  * <b>old_options</b> contains the previous value of the options.
  *
+ * Return 0 if all goes well, return -1 if things went badly.
+ */
+static int
+options_act_reversible(or_options_t *old_options)
+{
+  smartlist_t *new_listeners = smartlist_create();
+  smartlist_t *replaced_listeners = smartlist_create();
+  static int libevent_initialized = 0;
+  or_options_t *options = get_options();
+  int running_tor = options->command == CMD_RUN_TOR;
+  int set_conn_limit = 0;
+  int r = -1;
+
+  if (running_tor && options->RunAsDaemon) {
+    /* No need to roll back, since you can't change the value. */
+    start_daemon();
+  }
+
+  /* Setuid/setgid as appropriate */
+  if (options->User || options->Group) {
+    if (switch_id(options->User, options->Group) != 0) {
+      /* No need to roll back, since you can't change the value. */
+      goto done;
+    }
+  }
+
+  /* Set up libevent. */
+  if (running_tor && !libevent_initialized) {
+    if (init_libevent())
+      goto done;
+    libevent_initialized = 1;
+  }
+
+  /* Ensure data directory is private; create if possible. */
+  if (check_private_dir(options->DataDirectory, CPD_CREATE)<0) {
+    log_fn(LOG_ERR, "Couldn't access/create private data directory \"%s\"",
+           options->DataDirectory);
+    /* No need to roll back, since you can't change the value. */
+    goto done;
+  }
+
+  /* Bail out at this point if we're not going to be a client or server:
+   * we don't run  */
+  if (options->command != CMD_RUN_TOR)
+    goto commit;
+
+  options->_ConnLimit =
+    set_max_file_descriptors((unsigned)options->ConnLimit, MAXCONNECTIONS);
+  if (options->_ConnLimit < 0)
+    goto rollback;
+  set_conn_limit = 1;
+
+  if (retry_all_listeners(0, replaced_listeners, new_listeners) < 0) {
+    log_fn(LOG_ERR,"Failed to bind one of the listener ports.");
+    goto rollback;
+  }
+
+ commit:
+  r = 0;
+  SMARTLIST_FOREACH(replaced_listeners, connection_t *, conn,
+  {
+    log_fn(LOG_NOTICE, "Closing old %s on %s:%d",
+           conn_type_to_string(conn->type), conn->address, conn->port);
+    connection_close_immediate(conn);
+    connection_mark_for_close(conn);
+  });
+  goto done;
+
+ rollback:
+  r = -1;
+
+  if (set_conn_limit && old_options)
+    set_max_file_descriptors((unsigned)old_options->ConnLimit,MAXCONNECTIONS);
+
+  SMARTLIST_FOREACH(new_listeners, connection_t *, conn,
+  {
+    log_fn(LOG_NOTICE, "Closing %s on %s:%d",
+           conn_type_to_string(conn->type), conn->address, conn->port);
+    connection_close_immediate(conn);
+    connection_mark_for_close(conn);
+  });
+
+ done:
+  smartlist_free(new_listeners);
+  smartlist_free(replaced_listeners);
+  return r;
+}
+
+/** Fetch the active option list, and take actions based on it. All of the
+ * things we do should survive being done repeatedly.  If present,
+ * <b>old_options</b> contains the previous value of the options.
+ *
  * Return 0 if all goes well, return -1 if it's time to die.
  *
  * Note 2: We haven't moved all the "act on new configuration" logic
@@ -416,11 +515,6 @@
   size_t len;
   or_options_t *options = get_options();
   int running_tor = options->command == CMD_RUN_TOR;
-  static int libevent_initialized = 0;
-
-  if (running_tor && options->RunAsDaemon) {
-    start_daemon();
-  }
 
   clear_trusted_dir_servers();
   for (cl = options->DirServers; cl; cl = cl->next) {
@@ -440,19 +534,6 @@
   if (options->EntryNodes && strlen(options->EntryNodes))
     options->UseHelperNodes = 0;
 
-  /* Setuid/setgid as appropriate */
-  if (options->User || options->Group) {
-    if (switch_id(options->User, options->Group) != 0) {
-      return -1;
-    }
-  }
-
-  /* Ensure data directory is private; create if possible. */
-  if (check_private_dir(options->DataDirectory, CPD_CREATE) != 0) {
-    log_fn(LOG_ERR, "Couldn't access/create private data directory \"%s\"",
-           options->DataDirectory);
-    return -1;
-  }
   if (running_tor) {
     len = strlen(options->DataDirectory)+32;
     fn = tor_malloc(len);
@@ -481,23 +562,11 @@
   add_callback_log(LOG_ERR, LOG_ERR, control_event_logmsg);
   control_adjust_event_log_severity();
 
-  /* Set up libevent. */
-  if (running_tor && !libevent_initialized) {
-    if (init_libevent())
-      return -1;
-    libevent_initialized = 1;
-  }
-
   /* Load state */
   if (! global_state)
     if (or_state_load())
       return -1;
 
-  options->_ConnLimit =
-    set_max_file_descriptors((unsigned)options->ConnLimit, MAXCONNECTIONS);
-  if (options->_ConnLimit < 0)
-    return -1;
-
   {
     smartlist_t *sl = smartlist_create();
     for (cl = options->RedirectExit; cl; cl = cl->next) {
@@ -545,11 +614,6 @@
   if (!running_tor)
     return 0;
 
-  if (!we_are_hibernating() && retry_all_listeners(0) < 0) {
-    log_fn(LOG_ERR,"Failed to bind one of the listener ports.");
-    return -1;
-  }
-
   /* Check for transitions that need action. */
   if (old_options) {
     if (options_transition_affects_workers(old_options, options)) {
@@ -1138,7 +1202,7 @@
  * options, assigning list to the new one, then validating it. If it's
  * ok, then throw out the old one and stick with the new one. Else,
  * revert to old and return failure.  Return 0 on success, -1 on bad
- * keys, -2 on bad values, -3 on bad transition.
+ * keys, -2 on bad values, -3 on bad transition, and -4 on failed-to-set.
  */
 int
 options_trial_assign(config_line_t *list, int use_defaults, int clear_first)
@@ -1162,7 +1226,12 @@
     return -3;
   }
 
-  set_options(trial_options); /* we liked it. put it in place. */
+  if (set_options(trial_options)<0) {
+    config_free(&options_format, trial_options);
+    return -4;
+  }
+
+  /* we liked it. put it in place. */
   return 0;
 }
 
@@ -2332,7 +2401,8 @@
   if (options_transition_allowed(oldoptions, newoptions) < 0)
     goto err;
 
-  set_options(newoptions); /* frees and replaces old options */
+  if (set_options(newoptions))
+    goto err; /* frees and replaces old options */
   tor_free(torrc_fname);
   torrc_fname = fname;
   return 0;

Index: connection.c
===================================================================
RCS file: /home/or/cvsroot/tor/src/or/connection.c,v
retrieving revision 1.398
retrieving revision 1.399
diff -u -d -r1.398 -r1.399
--- connection.c	12 Sep 2005 07:36:26 -0000	1.398
+++ connection.c	14 Sep 2005 02:36:29 -0000	1.399
@@ -13,8 +13,8 @@
 
 #include "or.h"
 
-static int connection_create_listener(const char *bindaddress,
-                                      uint16_t bindport, int type);
+static connection_t *connection_create_listener(const char *bindaddress,
+                                                uint16_t bindport, int type);
 static int connection_init_accepted_conn(connection_t *conn);
 static int connection_handle_listener_read(connection_t *conn, int new_type);
 static int connection_receiver_bucket_should_increase(connection_t *conn);
@@ -474,8 +474,9 @@
  * If <b>bindaddress</b> includes a port, we bind on that port; otherwise, we
  * use bindport.
  */
-static int
-connection_create_listener(const char *bindaddress, uint16_t bindport, int type)
+static connection_t *
+connection_create_listener(const char *bindaddress, uint16_t bindport,
+                           int type)
 {
   struct sockaddr_in bindaddr; /* where to bind */
   char *address = NULL;
@@ -490,7 +491,7 @@
   memset(&bindaddr,0,sizeof(struct sockaddr_in));
   if (parse_addr_port(bindaddress, &address, &addr, &usePort)<0) {
     log_fn(LOG_WARN, "Error parsing/resolving BindAddress %s",bindaddress);
-    return -1;
+    return NULL;
   }
 
   if (usePort==0)
@@ -551,11 +552,11 @@
   conn->state = LISTENER_STATE_READY;
   connection_start_reading(conn);
 
-  return 0;
+  return conn;
 
  err:
   tor_free(address);
-  return -1;
+  return NULL;
 }
 
 /** Do basic sanity checking on a newly received socket. Return 0
@@ -804,10 +805,15 @@
  * Otherwise, only relaunch the listeners of this type if the number of
  * existing connections is not as configured (e.g., because one died),
  * or if the existing connections do not match those configured.
+ *
+ * Add all old conns that should be closed to <b>replaced_conns</b>.
+ * Add all new connections to <b>new_conns</b>.
  */
 static int
 retry_listeners(int type, config_line_t *cfg,
-                int port_option, const char *default_addr, int force)
+                int port_option, const char *default_addr, int force,
+                smartlist_t *replaced_conns,
+                smartlist_t *new_conns)
 {
   smartlist_t *launch = smartlist_create();
   int free_launch_elts = 1;
@@ -829,6 +835,11 @@
     smartlist_add(launch, line);
   }
 
+  /*
+  SMARTLIST_FOREACH(launch, config_line_t *, l,
+                    log_fn(LOG_NOTICE, "#%s#%s", l->key, l->value));
+  */
+
   get_connection_array(&carray,&n_conn);
   for (i=0; i < n_conn; ++i) {
     conn = carray[i];
@@ -862,8 +873,12 @@
       /* This one isn't configured. Close it. */
       log_fn(LOG_NOTICE, "Closing %s on %s:%d",
              conn_type_to_string(type), conn->address, conn->port);
-      connection_close_immediate(conn);
-      connection_mark_for_close(conn);
+      if (replaced_conns) {
+        smartlist_add(replaced_conns, conn);
+      } else {
+        connection_close_immediate(conn);
+        connection_mark_for_close(conn);
+      }
     } else {
       /* It's configured; we don't need to launch it. */
       log_fn(LOG_INFO, "Already have %s on %s:%d",
@@ -876,9 +891,14 @@
   i = 0;
   SMARTLIST_FOREACH(launch, config_line_t *, cfg,
     {
-      if (connection_create_listener(cfg->value, (uint16_t) port_option,
-                                     type)<0)
+      conn = connection_create_listener(cfg->value, (uint16_t) port_option,
+                                        type);
+      if (!conn) {
         i = -1;
+      } else {
+        if (new_conns)
+          smartlist_add(new_conns, conn);
+      }
   });
 
   if (free_launch_elts) {
@@ -894,24 +914,32 @@
  * <b>force</b> is true, close and relaunch all listeners. If <b>force</b>
  * is false, then only relaunch listeners when we have the wrong number of
  * connections for a given type.
+ *
+ * Add all old conns that should be closed to <b>replaced_conns</b>.
+ * Add all new connections to <b>new_conns</b>.
  */
 int
-retry_all_listeners(int force)
+retry_all_listeners(int force, smartlist_t *replaced_conns,
+                    smartlist_t *new_conns)
 {
   or_options_t *options = get_options();
 
   if (server_mode(options) &&
       retry_listeners(CONN_TYPE_OR_LISTENER, options->ORBindAddress,
-                      options->ORPort, "0.0.0.0", force)<0)
+                      options->ORPort, "0.0.0.0", force,
+                      replaced_conns, new_conns)<0)
     return -1;
   if (retry_listeners(CONN_TYPE_DIR_LISTENER, options->DirBindAddress,
-                      options->DirPort, "0.0.0.0", force)<0)
+                      options->DirPort, "0.0.0.0", force,
+                      replaced_conns, new_conns)<0)
     return -1;
   if (retry_listeners(CONN_TYPE_AP_LISTENER, options->SocksBindAddress,
-                      options->SocksPort, "127.0.0.1", force)<0)
+                      options->SocksPort, "127.0.0.1", force,
+                      replaced_conns, new_conns)<0)
     return -1;
   if (retry_listeners(CONN_TYPE_CONTROL_LISTENER, NULL,
-                      options->ControlPort, "127.0.0.1", force)<0)
+                      options->ControlPort, "127.0.0.1", force,
+                      replaced_conns, new_conns)<0)
     return -1;
 
   return 0;

Index: control.c
===================================================================
RCS file: /home/or/cvsroot/tor/src/or/control.c,v
retrieving revision 1.129
retrieving revision 1.130
diff -u -d -r1.129 -r1.130
--- control.c	14 Sep 2005 02:07:35 -0000	1.129
+++ control.c	14 Sep 2005 02:36:29 -0000	1.130
@@ -668,18 +668,32 @@
   }
 
   if ((r=options_trial_assign(lines, use_defaults, clear_first)) < 0) {
+    int v0_err;
+    const char *msg;
     log_fn(LOG_WARN,"Controller gave us config lines that didn't validate.");
-    if (r==-1) {
-      if (v0)
-        send_control0_error(conn, ERR_UNRECOGNIZED_CONFIG_KEY,
-                            "Unrecognized option");
-      else
-        connection_write_str_to_buf("552 Unrecognized option\r\n", conn);
+    switch (r) {
+      case -1:
+        v0_err = ERR_UNRECOGNIZED_CONFIG_KEY;
+        msg = "Unrecognized option";
+        break;
+      case -2:
+        v0_err = ERR_INVALID_CONFIG_VALUE;
+        msg = "Unrecognized option value";
+        break;
+      case -3:
+        v0_err = ERR_INVALID_CONFIG_VALUE;
+        msg = "Transition not allowed";
+        break;
+      case -4:
+      default:
+        v0_err = ERR_INVALID_CONFIG_VALUE;
+        msg = "Unable to set option";
+        break;
+    }
+    if (v0) {
+      send_control0_error(conn, v0_err, msg);
     } else {
-      if (v0)
-        send_control0_error(conn,ERR_INVALID_CONFIG_VALUE,"Invalid option value");
-      else
-        connection_write_str_to_buf("552 Invalid option value\r\n", conn);
+      connection_printf_to_buf(conn, "552 %s\r\n", msg);
     }
     config_free_lines(lines);
     return 0;

Index: main.c
===================================================================
RCS file: /home/or/cvsroot/tor/src/or/main.c,v
retrieving revision 1.548
retrieving revision 1.549
diff -u -d -r1.548 -r1.549
--- main.c	8 Sep 2005 20:18:14 -0000	1.548
+++ main.c	14 Sep 2005 02:36:29 -0000	1.549
@@ -760,7 +760,8 @@
 
   /** 3d. And every 60 seconds, we relaunch listeners if any died. */
   if (!we_are_hibernating() && time_to_check_listeners < now) {
-    retry_all_listeners(0); /* 0 means "only if some died." */
+    /* 0 means "only launch the ones that died." */
+    retry_all_listeners(0, NULL, NULL);
     time_to_check_listeners = now+60;
   }
 

Index: or.h
===================================================================
RCS file: /home/or/cvsroot/tor/src/or/or.h,v
retrieving revision 1.679
retrieving revision 1.680
diff -u -d -r1.679 -r1.680
--- or.h	14 Sep 2005 02:28:35 -0000	1.679
+++ or.h	14 Sep 2005 02:36:29 -0000	1.680
@@ -1439,7 +1439,7 @@
 /********************************* config.c ***************************/
 
 or_options_t *get_options(void);
-void set_options(or_options_t *new_val);
+int set_options(or_options_t *new_val);
 void config_free_all(void);
 const char *safe_str(const char *address);
 
@@ -1492,7 +1492,8 @@
 void connection_expire_held_open(void);
 
 int connection_connect(connection_t *conn, char *address, uint32_t addr, uint16_t port);
-int retry_all_listeners(int force);
+int retry_all_listeners(int force, smartlist_t *replaced_conns,
+                        smartlist_t *new_conns);
 
 void connection_bucket_init(void);
 void connection_bucket_refill(struct timeval *now);



More information about the tor-commits mailing list