summaryrefslogtreecommitdiffstats
path: root/src/bin/dhcp4/main.cc
blob: 949805680c35cf5dcdf24fad30b99575011a316e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>
#include <kea_version.h>

#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/parser_context.h>
#include <dhcp4/json_config_parser.h>
#include <dhcpsrv/cfgmgr.h>
#include <exceptions/exceptions.h>
#include <log/logger_support.h>
#include <log/logger_manager.h>
#include <log/output_option.h>
#include <process/cfgrpt/config_report.h>
#include <process/daemon.h>

#include <boost/lexical_cast.hpp>

#include <iostream>

using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::process;
using namespace std;

/// This file contains entry point (main() function) for standard DHCPv4 server
/// component of Kea software suite. It parses command-line arguments and
/// instantiates ControlledDhcpv4Srv class that is responsible for establishing
/// connection with msgq (receiving commands and configuration) and also
/// creating Dhcpv4 server object as well.
///
/// For detailed explanation or relations between main(), ControlledDhcpv4Srv,
/// Dhcpv4Srv and other classes, see \ref dhcpv4Session.

namespace {

const char* const DHCP4_NAME = "kea-dhcp4";

/// @brief Prints Kea Usage and exits
///
/// Note: This function never returns. It terminates the process.
void
usage() {
    cerr << "Kea DHCPv4 server, "
         << "version " << VERSION
         << " (" << PACKAGE_VERSION_TYPE << ")"
         << endl;
    cerr << endl;
    cerr << "Usage: " << DHCP4_NAME
         << " -[v|V|W] [-d] [-{c|t} cfgfile] [-p number] [-P number]" << endl;
    cerr << "  -v: print version number and exit" << endl;
    cerr << "  -V: print extended version and exit" << endl;
    cerr << "  -W: display the configuration report and exit" << endl;
    cerr << "  -d: debug mode with extra verbosity (former -v)" << endl;
    cerr << "  -c file: specify configuration file" << endl;
    cerr << "  -t file: check the configuration file syntax and exit" << endl;
    cerr << "  -p number: specify non-standard server port number 1-65535 "
         << "(useful for testing only)" << endl;
    cerr << "  -P number: specify non-standard client port number 1-65535 "
         << "(useful for testing only)" << endl;
    exit(EXIT_FAILURE);
}
}  // namespace

int
main(int argc, char* argv[]) {
    int ch;
    // The default. Any other values are useful for testing only.
    int server_port_number = DHCP4_SERVER_PORT;
    // Not zero values are useful for testing only.
    int client_port_number = 0;
    bool verbose_mode = false; // Should server be verbose?
    bool check_mode = false;   // Check syntax

    // The standard config file
    std::string config_file("");

    while ((ch = getopt(argc, argv, "dvVWc:p:P:t:")) != -1) {
        switch (ch) {
        case 'd':
            verbose_mode = true;
            break;

        case 'v':
            cout << Dhcpv4Srv::getVersion(false) << endl;
            return (EXIT_SUCCESS);

        case 'V':
            cout << Dhcpv4Srv::getVersion(true) << endl;
            return (EXIT_SUCCESS);

        case 'W':
            cout << isc::detail::getConfigReport() << endl;
            return (EXIT_SUCCESS);

        case 't':
            check_mode = true;
            // falls through

        case 'c': // config file
            config_file = optarg;
            break;

        case 'p': // server port number
            try {
                server_port_number = boost::lexical_cast<int>(optarg);
            } catch (const boost::bad_lexical_cast &) {
                cerr << "Failed to parse server port number: [" << optarg
                     << "], 1-65535 allowed." << endl;
                usage();
            }
            if (server_port_number <= 0 || server_port_number > 65535) {
                cerr << "Failed to parse server port number: [" << optarg
                     << "], 1-65535 allowed." << endl;
                usage();
            }
            break;

        case 'P': // client port number
            try {
                client_port_number = boost::lexical_cast<int>(optarg);
            } catch (const boost::bad_lexical_cast &) {
                cerr << "Failed to parse client port number: [" << optarg
                     << "], 1-65535 allowed." << endl;
                usage();
            }
            if (client_port_number <= 0 || client_port_number > 65535) {
                cerr << "Failed to parse client port number: [" << optarg
                     << "], 1-65535 allowed." << endl;
                usage();
            }
            break;

        default:
            usage();
        }
    }

    // Check for extraneous parameters.
    if (argc > optind) {
        usage();
    }

    // Configuration file is required.
    if (config_file.empty()) {
        cerr << "Configuration file not specified." << endl;
        usage();
    }

    // This is the DHCPv4 server
    CfgMgr::instance().setFamily(AF_INET);

    if (check_mode) {
        try {
            // We need to initialize logging, in case any error messages are to be printed.
            // This is just a test, so we don't care about lockfile.
            setenv("KEA_LOCKFILE_DIR", "none", 0);
            Daemon::setDefaultLoggerName(DHCP4_ROOT_LOGGER_NAME);
            Daemon::loggerInit(DHCP4_ROOT_LOGGER_NAME, verbose_mode);

            // Check the syntax first.
            Parser4Context parser;
            ConstElementPtr json;
            json = parser.parseFile(config_file, Parser4Context::PARSER_DHCP4);
            if (!json) {
                cerr << "No configuration found" << endl;
                return (EXIT_FAILURE);
            }
            if (verbose_mode) {
                cerr << "Syntax check OK" << endl;
            }

            // Check the logic next.
            ConstElementPtr dhcp4 = json->get("Dhcp4");
            if (!dhcp4) {
                cerr << "Missing mandatory Dhcp4 element" << endl;
                return (EXIT_FAILURE);
            }
            ControlledDhcpv4Srv server(0);
            ConstElementPtr answer;

            // Now we pass the Dhcp4 configuration to the server, but
            // tell it to check the configuration only (check_only = true)
            answer = configureDhcp4Server(server, dhcp4, true);

            int status_code = 0;
            answer = isc::config::parseAnswer(status_code, answer);
            if (status_code == 0) {
                return (EXIT_SUCCESS);
            } else {
                cerr << "Error encountered: " << answer->stringValue() << endl;
                return (EXIT_FAILURE);
            }
        } catch (const std::exception& ex) {
            cerr << "Syntax check failed with: " << ex.what() << endl;
        }
        return (EXIT_FAILURE);
    }

    int ret = EXIT_SUCCESS;
    try {
        // It is important that we set a default logger name because this name
        // will be used when the user doesn't provide the logging configuration
        // in the Kea configuration file.
        Daemon::setDefaultLoggerName(DHCP4_ROOT_LOGGER_NAME);

        // Initialize logging.  If verbose, we'll use maximum verbosity.
        Daemon::loggerInit(DHCP4_ROOT_LOGGER_NAME, verbose_mode);
        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)
            .arg(getpid())
            .arg(server_port_number)
            .arg(client_port_number)
            .arg(verbose_mode ? "yes" : "no");

        LOG_INFO(dhcp4_logger, DHCP4_STARTING)
            .arg(VERSION)
            .arg(PACKAGE_VERSION_TYPE);

        if (string(PACKAGE_VERSION_TYPE) == "development") {
            LOG_WARN(dhcp4_logger, DHCP4_DEVELOPMENT_VERSION);
        }

        // Create the server instance.
        ControlledDhcpv4Srv server(server_port_number, client_port_number);

        // Remember verbose-mode
        server.setVerbose(verbose_mode);

        // Create our PID file.
        server.setProcName(DHCP4_NAME);
        server.setConfigFile(config_file);
        server.createPIDFile();

        try {
            // Initialize the server.
            server.init(config_file);
        } catch (const std::exception& ex) {

            // Let's log out what went wrong.
            try {
                // Log with the current logger, but only if it's not
                // configured with console output so as to not log twice.
                if (!dhcp4_logger.hasAppender(isc::log::OutputOption::DEST_CONSOLE)) {
                    LOG_ERROR(dhcp4_logger, DHCP4_INIT_FAIL).arg(ex.what());
                }

                // Log on the console as well.
                isc::log::LoggerManager log_manager;
                log_manager.process();
                LOG_ERROR(dhcp4_logger, DHCP4_INIT_FAIL).arg(ex.what());
            } catch (...) {
                // The exception thrown during the initialization could
                // originate from logger subsystem. Therefore LOG_ERROR()
                // may fail as well.
                cerr << "Failed to initialize server: " << ex.what() << endl;
            }

            return (EXIT_FAILURE);
        }

        // Tell the admin we are ready to process packets
        LOG_INFO(dhcp4_logger, DHCP4_STARTED).arg(VERSION);

        // And run the main loop of the server.
        ret = server.run();

        LOG_INFO(dhcp4_logger, DHCP4_SHUTDOWN);

    } catch (const isc::process::DaemonPIDExists& ex) {
        // First, we print the error on stderr (that should always work)
        cerr << DHCP4_NAME << " already running? " << ex.what()
             << endl;

        // Let's also try to log it using logging system, but we're not
        // sure if it's usable (the exception may have been thrown from
        // the logger subsystem)
        try {
            LOG_FATAL(dhcp4_logger, DHCP4_ALREADY_RUNNING)
                .arg(DHCP4_NAME).arg(ex.what());
        } catch (...) {
            // Already logged so ignore
        }
        ret = EXIT_FAILURE;
    } catch (const std::exception& ex) {
        // First, we print the error on stderr (that should always work)
        cerr << DHCP4_NAME << ": Fatal error during start up: " << ex.what()
             << endl;

        // Let's also try to log it using logging system, but we're not
        // sure if it's usable (the exception may have been thrown from
        // the logger subsystem)
        try {
            LOG_FATAL(dhcp4_logger, DHCP4_SERVER_FAILED).arg(ex.what());
        } catch (...) {
            // Already logged so ignore
        }
        ret = EXIT_FAILURE;
    } catch (...) {
        cerr << DHCP4_NAME << ": Fatal error during start up"
             << endl;
        ret = EXIT_FAILURE;
    }

    return (ret);
}