/mandos/release

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/release

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2023-02-07 23:03:33 UTC
  • mto: This revision was merged to the branch mainline in revision 408.
  • Revision ID: teddy@recompile.se-20230207230333-5halrp7943pgb3w1
Server: Bug fix: Stagger checker runs when creating clients

* mandos (Client.enable()): Do not set self.expires here; move it to
  "init_checker".
  (Client.init_checker()): Take new "randomize_start" argument.  If
  True, randomize delay before starting checker.  Also, do not start
  checker right now, but instead extend expire time so that the
  scheduled checker always has time to run.
  (Checker.start_checker): Take new "start_was_randomized" argument.
  If True, reset scheduled checker runs to be 'interval' apart,
  instead of using the initial delay.  (Bug fix)
  (main): On startup, pass argument randomize_start=True to
  client.init_checker() when initizlizing checkers for all enabled
  clients.

Reported-by: Louis Charreau <Louis.Charreau@vadesecure.com>
Suggested-by: Louis Charreau <Louis.Charreau@vadesecure.com>
Fixes: 1200 ("Server: Stagger checker runs when creating clients")

Show diffs side-by-side

added added

removed removed

Lines of Context:
1045
1045
        if getattr(self, "enabled", False):
1046
1046
            # Already enabled
1047
1047
            return
1048
 
        self.expires = datetime.datetime.utcnow() + self.timeout
1049
1048
        self.enabled = True
1050
1049
        self.last_enabled = datetime.datetime.utcnow()
1051
1050
        self.init_checker()
1074
1073
    def __del__(self):
1075
1074
        self.disable()
1076
1075
 
1077
 
    def init_checker(self):
1078
 
        # Schedule a new checker to be started an 'interval' from now,
1079
 
        # and every interval from then on.
 
1076
    def init_checker(self, randomize_start=False):
 
1077
        # Schedule a new checker to be started a randomly selected
 
1078
        # time (a fraction of 'interval') from now.  This spreads out
 
1079
        # the startup of checkers over time when the server is
 
1080
        # started.
1080
1081
        if self.checker_initiator_tag is not None:
1081
1082
            GLib.source_remove(self.checker_initiator_tag)
 
1083
        interval_milliseconds = int(self.interval.total_seconds()
 
1084
                                    * 1000)
 
1085
        if randomize_start:
 
1086
            delay_milliseconds = random.randrange(
 
1087
                interval_milliseconds + 1)
 
1088
        else:
 
1089
            delay_milliseconds = interval_milliseconds
1082
1090
        self.checker_initiator_tag = GLib.timeout_add(
1083
 
            random.randrange(int(self.interval.total_seconds() * 1000
1084
 
                                 + 1)),
1085
 
            self.start_checker)
1086
 
        # Schedule a disable() when 'timeout' has passed
 
1091
            delay_milliseconds, self.start_checker, randomize_start)
 
1092
        delay = datetime.timedelta(0, 0, 0, delay_milliseconds)
 
1093
        # A checker might take up to an 'interval' of time, so we can
 
1094
        # expire at the soonest one interval after a checker was
 
1095
        # started.  Since the initial checker is delayed, the expire
 
1096
        # time might have to be extended.
 
1097
        now = datetime.datetime.utcnow()
 
1098
        self.expires = now + delay + self.interval
 
1099
        # Schedule a disable() at expire time
1087
1100
        if self.disable_initiator_tag is not None:
1088
1101
            GLib.source_remove(self.disable_initiator_tag)
1089
1102
        self.disable_initiator_tag = GLib.timeout_add(
1090
 
            int(self.timeout.total_seconds() * 1000), self.disable)
1091
 
        # Also start a new checker *right now*.
1092
 
        self.start_checker()
 
1103
            int((self.expires - now).total_seconds() * 1000),
 
1104
            self.disable)
1093
1105
 
1094
1106
    def checker_callback(self, source, condition, connection,
1095
1107
                         command):
1138
1150
    def need_approval(self):
1139
1151
        self.last_approval_request = datetime.datetime.utcnow()
1140
1152
 
1141
 
    def start_checker(self):
 
1153
    def start_checker(self, start_was_randomized=False):
1142
1154
        """Start a new checker subprocess if one is not running.
1143
1155
 
1144
1156
        If a checker already exists, leave it running and do
1194
1206
                GLib.IOChannel.unix_new(pipe[0].fileno()),
1195
1207
                GLib.PRIORITY_DEFAULT, GLib.IO_IN,
1196
1208
                self.checker_callback, pipe[0], command)
 
1209
        if start_was_randomized:
 
1210
            # We were started after a random delay; Schedule a new
 
1211
            # checker to be started an 'interval' from now, and every
 
1212
            # interval from then on.
 
1213
            now = datetime.datetime.utcnow()
 
1214
            self.checker_initiator_tag = GLib.timeout_add(
 
1215
                int(self.interval.total_seconds() * 1000),
 
1216
                self.start_checker)
 
1217
            self.expires = max(self.expires, now + self.interval)
 
1218
            # Don't start a new checker again after same random delay
 
1219
            return False
1197
1220
        # Re-run this periodically if run by GLib.timeout_add
1198
1221
        return True
1199
1222
 
3609
3632
            mandos_dbus_service.client_added_signal(client)
3610
3633
        # Need to initiate checking of clients
3611
3634
        if client.enabled:
3612
 
            client.init_checker()
 
3635
            client.init_checker(randomize_start=True)
3613
3636
 
3614
3637
    tcp_server.enable()
3615
3638
    tcp_server.server_activate()