1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-31 17:18:04 +03:00
samba-mirror/script/traffic_replay
Tim Beale b40daca6d4 traffic_replay: Generate users faster by writing to local DB
We can create user accounts much faster if the LDB connection talks
directly to the local sam.ldb file rather than going via LDAP. This
patch allows the 'host' argument to the tool to be a .ldb file (e.g.
"/usr/local/samba/private/sam.ldb") instead of a server name/IP.

In most cases, the traffic_replay tool wants to run on a remote device
(because the point of it is to send traffic to the DC). However, the
--generate-users-only is one case where the tool can be run locally,
directly on the test DC. (The traffic_replay user generation is handy
for standalone testing, because it also handles assigning group
memberships to the generated user accounts).

Note that you also need to use '--option="ldb:nosync = true"' to get
the improvement in performance.

Signed-off-by: Tim Beale <timbeale@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
2018-10-31 00:30:16 +01:00

400 lines
16 KiB
Python
Executable File

#!/usr/bin/env python
# Generates samba network traffic
#
# Copyright (C) Catalyst IT Ltd. 2017
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import print_function
import sys
import os
import optparse
import tempfile
import shutil
import random
sys.path.insert(0, "bin/python")
from samba import gensec, get_debug_level
from samba.emulate import traffic
import samba.getopt as options
from samba.logger import get_samba_logger
from samba.samdb import SamDB
from samba.auth import system_session
def print_err(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
def main():
desc = ("Generates network traffic 'conversations' based on <summary-file>"
" (which should be the output file produced by either traffic_learner"
" or traffic_summary.pl). This traffic is sent to <dns-hostname>,"
" which is the full DNS hostname of the DC being tested.")
parser = optparse.OptionParser(
"%prog [--help|options] <summary-file> <dns-hostname>",
description=desc)
parser.add_option('--dns-rate', type='float', default=0,
help='fire extra DNS packets at this rate')
parser.add_option('-B', '--badpassword-frequency',
type='float', default=0.0,
help='frequency of connections with bad passwords')
parser.add_option('-K', '--prefer-kerberos',
action="store_true",
help='prefer kerberos when authenticating test users')
parser.add_option('-I', '--instance-id', type='int', default=0,
help='Instance number, when running multiple instances')
parser.add_option('-t', '--timing-data',
help=('write individual message timing data here '
'(- for stdout)'))
parser.add_option('--preserve-tempdir', default=False, action="store_true",
help='do not delete temporary files')
parser.add_option('-F', '--fixed-password',
type='string', default=None,
help=('Password used for the test users created. '
'Required'))
parser.add_option('-c', '--clean-up',
action="store_true",
help='Clean up the generated groups and user accounts')
parser.add_option('--random-seed', type='int', default=0,
help='Use to keep randomness consistent across multiple runs')
model_group = optparse.OptionGroup(parser, 'Traffic Model Options',
'These options alter the traffic '
'generated when the summary-file is a '
'traffic-model (produced by '
'traffic_learner)')
model_group.add_option('-S', '--scale-traffic', type='float', default=1.0,
help='Increase the number of conversations by '
'this factor')
model_group.add_option('-D', '--duration', type='float', default=None,
help=('Run model for this long (approx). '
'Default 60s for models'))
model_group.add_option('-r', '--replay-rate', type='float', default=1.0,
help='Replay the traffic faster by this factor')
model_group.add_option('--traffic-summary',
help=('Generate a traffic summary file and write '
'it here (- for stdout)'))
parser.add_option_group(model_group)
user_gen_group = optparse.OptionGroup(parser, 'Generate User Options',
"Add extra user/groups on the DC to "
"increase the DB size. These extra "
"users aren't used for traffic "
"generation.")
user_gen_group.add_option('-G', '--generate-users-only',
action="store_true",
help='Generate the users, but do not replay '
'the traffic')
user_gen_group.add_option('-n', '--number-of-users', type='int', default=0,
help='Total number of test users to create')
user_gen_group.add_option('--number-of-groups', type='int', default=0,
help='Create this many groups')
user_gen_group.add_option('--average-groups-per-user',
type='int', default=0,
help='Assign the test users to this '
'many groups on average')
user_gen_group.add_option('--group-memberships', type='int', default=0,
help='Total memberships to assign across all '
'test users and all groups')
parser.add_option_group(user_gen_group)
sambaopts = options.SambaOptions(parser)
parser.add_option_group(sambaopts)
parser.add_option_group(options.VersionOptions(parser))
credopts = options.CredentialsOptions(parser)
parser.add_option_group(credopts)
# the --no-password credential doesn't make sense for this tool
if parser.has_option('-N'):
parser.remove_option('-N')
opts, args = parser.parse_args()
# First ensure we have reasonable arguments
if len(args) == 1:
summary = None
host = args[0]
elif len(args) == 2:
summary, host = args
else:
parser.print_usage()
return
debuglevel = get_debug_level()
logger = get_samba_logger(name=__name__,
verbose=debuglevel > 3,
quiet=debuglevel < 1)
traffic.DEBUG_LEVEL = debuglevel
# pass log level down to traffic module to make sure level is controlled
traffic.LOGGER.setLevel(logger.getEffectiveLevel())
if opts.clean_up:
logger.info("Removing user and machine accounts")
lp = sambaopts.get_loadparm()
creds = credopts.get_credentials(lp)
creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
ldb = traffic.openLdb(host, creds, lp)
traffic.clean_up_accounts(ldb, opts.instance_id)
exit(0)
if summary:
if not os.path.exists(summary):
logger.error("Summary file %s doesn't exist" % summary)
sys.exit(1)
# the summary-file can be ommitted for --generate-users-only and
# --cleanup-up, but it should be specified in all other cases
elif not opts.generate_users_only:
logger.error("No summary-file specified to replay traffic from")
sys.exit(1)
if not opts.fixed_password:
logger.error(("Please use --fixed-password to specify a password"
" for the users created as part of this test"))
sys.exit(1)
if opts.random_seed:
random.seed(opts.random_seed)
lp = sambaopts.get_loadparm()
creds = credopts.get_credentials(lp)
creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
domain = creds.get_domain()
if domain:
lp.set("workgroup", domain)
else:
domain = lp.get("workgroup")
if domain == "WORKGROUP":
logger.error(("NETBIOS domain does not appear to be "
"specified, use the --workgroup option"))
sys.exit(1)
if not opts.realm and not lp.get('realm'):
logger.error("Realm not specified, use the --realm option")
sys.exit(1)
if opts.generate_users_only and not (opts.number_of_users or
opts.number_of_groups):
logger.error(("Please specify the number of users and/or groups "
"to generate."))
sys.exit(1)
if opts.group_memberships and opts.average_groups_per_user:
logger.error(("--group-memberships and --average-groups-per-user"
" are incompatible options - use one or the other"))
sys.exit(1)
if not opts.number_of_groups and opts.average_groups_per_user:
logger.error(("--average-groups-per-user requires "
"--number-of-groups"))
sys.exit(1)
if opts.number_of_groups and opts.average_groups_per_user:
if opts.number_of_groups < opts.average_groups_per_user:
logger.error(("--average-groups-per-user can not be more than "
"--number-of-groups"))
sys.exit(1)
if not opts.number_of_groups and opts.group_memberships:
logger.error("--group-memberships requires --number-of-groups")
sys.exit(1)
if opts.timing_data not in ('-', None):
try:
open(opts.timing_data, 'w').close()
except IOError:
# exception info will be added to log automatically
logger.exception(("the supplied timing data destination "
"(%s) is not writable" % opts.timing_data))
sys.exit()
if opts.traffic_summary not in ('-', None):
try:
open(opts.traffic_summary, 'w').close()
except IOError:
# exception info will be added to log automatically
logger.exception(("the supplied traffic summary destination "
"(%s) is not writable" % opts.traffic_summary))
sys.exit()
duration = opts.duration
if duration is None:
duration = 60.0
# ingest the model or traffic summary
if summary:
try:
conversations, interval, duration, dns_counts = \
traffic.ingest_summaries([summary])
logger.info(("Using conversations from the traffic summary "
"file specified"))
# honour the specified duration if it's different to the
# capture duration
if opts.duration is not None:
duration = opts.duration
except ValueError as e:
if not e.message.startswith('need more than'):
raise
model = traffic.TrafficModel()
try:
model.load(summary)
except ValueError:
logger.error(("Could not parse %s. The summary file "
"should be the output from either the "
"traffic_summary.pl or "
"traffic_learner scripts.") % summary)
sys.exit()
logger.info(("Using the specified model file to "
"generate conversations"))
conversations = model.generate_conversations(opts.scale_traffic,
duration,
opts.replay_rate)
else:
conversations = []
if debuglevel > 5:
for c in conversations:
for p in c.packets:
print(" ", p, file=sys.stderr)
print('=' * 72, file=sys.stderr)
if opts.number_of_users and opts.number_of_users < len(conversations):
logger.error(("--number-of-users (%d) is less than the "
"number of conversations to replay (%d)"
% (opts.number_of_users, len(conversations))))
sys.exit(1)
number_of_users = max(opts.number_of_users, len(conversations))
max_memberships = number_of_users * opts.number_of_groups
if not opts.group_memberships and opts.average_groups_per_user:
opts.group_memberships = opts.average_groups_per_user * number_of_users
logger.info(("Using %d group-memberships based on %u average "
"memberships for %d users"
% (opts.group_memberships,
opts.average_groups_per_user, number_of_users)))
if opts.group_memberships > max_memberships:
logger.error(("The group memberships specified (%d) exceeds "
"the total users (%d) * total groups (%d)"
% (opts.group_memberships, number_of_users,
opts.number_of_groups)))
sys.exit(1)
# Get an LDB connection.
try:
# if we're only adding users, then it's OK to pass a sam.ldb filepath
# as the host, which creates the users much faster. In all other cases
# we should be connecting to a remote DC
if opts.generate_users_only and os.path.isfile(host):
ldb = SamDB(url="ldb://{0}".format(host),
session_info=system_session(), lp=lp)
else:
ldb = traffic.openLdb(host, creds, lp)
except:
logger.error(("\nInitial LDAP connection failed! Did you supply "
"a DNS host name and the correct credentials?"))
sys.exit(1)
if opts.generate_users_only:
traffic.generate_users_and_groups(ldb,
opts.instance_id,
opts.fixed_password,
opts.number_of_users,
opts.number_of_groups,
opts.group_memberships)
sys.exit()
tempdir = tempfile.mkdtemp(prefix="samba_tg_")
logger.info("Using temp dir %s" % tempdir)
traffic.generate_users_and_groups(ldb,
opts.instance_id,
opts.fixed_password,
number_of_users,
opts.number_of_groups,
opts.group_memberships)
accounts = traffic.generate_replay_accounts(ldb,
opts.instance_id,
len(conversations),
opts.fixed_password)
statsdir = traffic.mk_masked_dir(tempdir, 'stats')
if opts.traffic_summary:
if opts.traffic_summary == '-':
summary_dest = sys.stdout
else:
summary_dest = open(opts.traffic_summary, 'w')
logger.info("Writing traffic summary")
summaries = []
for c in conversations:
summaries += c.replay_as_summary_lines()
summaries.sort()
for (time, line) in summaries:
print(line, file=summary_dest)
exit(0)
traffic.replay(conversations, host,
lp=lp,
creds=creds,
accounts=accounts,
dns_rate=opts.dns_rate,
duration=duration,
badpassword_frequency=opts.badpassword_frequency,
prefer_kerberos=opts.prefer_kerberos,
statsdir=statsdir,
domain=domain,
base_dn=ldb.domain_dn(),
ou=traffic.ou_name(ldb, opts.instance_id),
tempdir=tempdir,
domain_sid=ldb.get_domain_sid())
if opts.timing_data == '-':
timing_dest = sys.stdout
elif opts.timing_data is None:
timing_dest = None
else:
timing_dest = open(opts.timing_data, 'w')
logger.info("Generating statistics")
traffic.generate_stats(statsdir, timing_dest)
if not opts.preserve_tempdir:
logger.info("Removing temporary directory")
shutil.rmtree(tempdir)
main()