9 #include <sys/socket.h>
12 #include <unordered_map>
25 #define DEFAULT_BACKLOG_SIZE 10485760
29 vector<string> arguments;
30 unordered_map<string, string> parameters;
35 bool parse_hostport(const string &hostport, sockaddr_in6 *addr)
37 memset(addr, 0, sizeof(*addr));
38 addr->sin6_family = AF_INET6;
42 // See if the argument if on the type [ipv6addr]:port.
43 if (!hostport.empty() && hostport[0] == '[') {
44 size_t split = hostport.find("]:");
45 if (split == string::npos) {
46 log(ERROR, "address '%s' is malformed; must be either [ipv6addr]:port or ipv4addr:port");
50 string host(hostport.begin() + 1, hostport.begin() + split);
51 port_string = hostport.substr(split + 2);
53 if (inet_pton(AF_INET6, host.c_str(), &addr->sin6_addr) != 1) {
54 log(ERROR, "'%s' is not a valid IPv6 address");
58 // OK, then it must be ipv4addr:port.
59 size_t split = hostport.find(":");
60 if (split == string::npos) {
61 log(ERROR, "address '%s' is malformed; must be either [ipv6addr]:port or ipv4addr:port");
65 string host(hostport.begin(), hostport.begin() + split);
66 port_string = hostport.substr(split + 1);
68 // Parse to an IPv4 address, then construct a mapped-v4 address from that.
71 if (inet_pton(AF_INET, host.c_str(), &addr4) != 1) {
72 log(ERROR, "'%s' is not a valid IPv4 address");
76 addr->sin6_addr.s6_addr32[2] = htonl(0xffff);
77 addr->sin6_addr.s6_addr32[3] = addr4.s_addr;
80 int port = stoi(port_string);
81 if (port < 1 || port >= 65536) {
82 log(ERROR, "port %d is out of range (must be [1,65536>).", port);
85 addr->sin6_port = ntohs(port);
90 bool read_config(const string &filename, vector<ConfigLine> *lines)
92 FILE *fp = fopen(filename.c_str(), "r");
94 log_perror(filename.c_str());
100 if (fgets(buf, sizeof(buf), fp) == nullptr) {
104 // Chop off the string at the first #, \r or \n.
105 buf[strcspn(buf, "#\r\n")] = 0;
107 // Remove all whitespace from the end of the string.
108 size_t len = strlen(buf);
109 while (len > 0 && isspace(buf[len - 1])) {
113 // If the line is now all blank, ignore it.
118 vector<string> tokens = split_tokens(buf);
119 assert(!tokens.empty());
122 line.keyword = tokens[0];
124 for (size_t i = 1; i < tokens.size(); ++i) {
125 // foo=bar is a parameter; anything else is an argument.
126 size_t equals_pos = tokens[i].find_first_of('=');
127 if (equals_pos == string::npos) {
128 line.arguments.push_back(tokens[i]);
130 string key = tokens[i].substr(0, equals_pos);
131 string value = tokens[i].substr(equals_pos + 1, string::npos);
132 line.parameters.insert(make_pair(key, value));
136 lines->push_back(line);
139 if (fclose(fp) == EOF) {
140 log_perror(filename.c_str());
146 bool fetch_config_string(const vector<ConfigLine> &config, const string &keyword, string *value)
148 for (const ConfigLine &line : config) {
149 if (line.keyword != keyword) {
152 if (line.parameters.size() > 0 ||
153 line.arguments.size() != 1) {
154 log(ERROR, "'%s' takes one argument and no parameters", keyword.c_str());
157 *value = line.arguments[0];
163 bool fetch_config_int(const vector<ConfigLine> &config, const string &keyword, int *value)
165 for (const ConfigLine &line : config) {
166 if (line.keyword != keyword) {
169 if (line.parameters.size() > 0 ||
170 line.arguments.size() != 1) {
171 log(ERROR, "'%s' takes one argument and no parameters", keyword.c_str());
174 *value = stoi(line.arguments[0]); // TODO: verify int validity.
180 bool load_file_to_string(const string &filename, size_t max_size, string *contents)
184 FILE *fp = fopen(filename.c_str(), "r");
186 log_perror(filename.c_str());
192 size_t ret = fread(buf, 1, sizeof(buf), fp);
194 contents->append(buf, buf + ret);
197 log_perror(filename.c_str());
205 if (contents->size() > max_size) {
206 log(ERROR, "%s was longer than the maximum allowed %zu bytes", filename.c_str(), max_size);
215 bool parse_tls_parameters(const unordered_map<string, string> ¶meters, AcceptorConfig *acceptor)
217 bool has_cert = false, has_key = false;
219 auto tls_cert_it = parameters.find("tls_cert");
220 if (tls_cert_it != parameters.end()) {
221 if (!load_file_to_string(tls_cert_it->second, 1048576, &acceptor->certificate_chain)) {
225 // Verify that the certificate is valid.
226 bool is_server = true;
227 TLSContext *server_context = tls_create_context(is_server, TLS_V12);
228 int num_cert = tls_load_certificates(
230 reinterpret_cast<const unsigned char *>(acceptor->certificate_chain.data()),
231 acceptor->certificate_chain.size());
233 log_tls_error(tls_cert_it->second.c_str(), num_cert);
234 tls_destroy_context(server_context);
236 } else if (num_cert == 0) {
237 log(ERROR, "%s did not contain any certificates", tls_cert_it->second.c_str());
240 tls_destroy_context(server_context);
244 auto tls_key_it = parameters.find("tls_key");
245 if (tls_key_it != parameters.end()) {
246 if (!load_file_to_string(tls_key_it->second, 1048576, &acceptor->private_key)) {
250 // Verify that the key is valid.
251 bool is_server = true;
252 TLSContext *server_context = tls_create_context(is_server, TLS_V12);
253 int num_keys = tls_load_private_key(
255 reinterpret_cast<const unsigned char *>(acceptor->private_key.data()),
256 acceptor->private_key.size());
258 log_tls_error(tls_key_it->second.c_str(), num_keys);
259 tls_destroy_context(server_context);
261 } else if (num_keys == 0) {
262 log(ERROR, "%s did not contain any private keys", tls_key_it->second.c_str());
265 tls_destroy_context(server_context);
269 if (has_cert != has_key) {
270 log(ERROR, "Only one of tls_cert= and tls_key= was given, needs zero or both");
278 bool parse_port(const ConfigLine &line, Config *config)
280 if (line.arguments.size() != 1) {
281 log(ERROR, "'port' takes exactly one argument");
285 int port = stoi(line.arguments[0]);
286 if (port < 1 || port >= 65536) {
287 log(ERROR, "port %d is out of range (must be [1,65536>).", port);
291 AcceptorConfig acceptor;
292 acceptor.addr = create_any_address(port);
294 if (!parse_tls_parameters(line.parameters, &acceptor)) {
297 config->acceptors.push_back(acceptor);
301 bool parse_listen(const ConfigLine &line, Config *config)
303 if (line.arguments.size() != 1) {
304 log(ERROR, "'listen' takes exactly one argument");
308 AcceptorConfig acceptor;
309 if (!parse_hostport(line.arguments[0], &acceptor.addr)) {
312 if (!parse_tls_parameters(line.parameters, &acceptor)) {
315 config->acceptors.push_back(acceptor);
319 bool parse_stream(const ConfigLine &line, Config *config)
321 if (line.arguments.size() != 1) {
322 log(ERROR, "'stream' takes exactly one argument");
327 stream.url = line.arguments[0];
329 const auto src_it = line.parameters.find("src");
330 bool input_is_udp = false;
331 if (src_it == line.parameters.end()) {
332 log(WARNING, "stream '%s' has no src= attribute, clients will not get any data.",
335 stream.src = src_it->second;
337 string protocol, user, host, port, path;
338 if (!parse_url(stream.src, &protocol, &user, &host, &port, &path)) {
339 log(ERROR, "could not parse URL '%s'", stream.src.c_str());
342 if (protocol == "udp") {
347 const auto backlog_it = line.parameters.find("backlog_size");
348 if (backlog_it == line.parameters.end()) {
349 stream.backlog_size = DEFAULT_BACKLOG_SIZE;
351 stream.backlog_size = stoll(backlog_it->second);
354 const auto prebuffer_it = line.parameters.find("force_prebuffer");
355 if (prebuffer_it == line.parameters.end()) {
356 stream.prebuffering_bytes = 0;
358 stream.prebuffering_bytes = stoll(prebuffer_it->second);
361 // Parse output encoding.
362 const auto encoding_parm_it = line.parameters.find("encoding");
363 if (encoding_parm_it == line.parameters.end() ||
364 encoding_parm_it->second == "raw") {
365 stream.encoding = StreamConfig::STREAM_ENCODING_RAW;
366 } else if (encoding_parm_it->second == "metacube") {
367 stream.encoding = StreamConfig::STREAM_ENCODING_METACUBE;
369 log(ERROR, "Parameter 'encoding' must be either 'raw' (default) or 'metacube'");
373 // Parse input encoding.
374 const auto src_encoding_parm_it = line.parameters.find("src_encoding");
375 if (src_encoding_parm_it == line.parameters.end()) {
376 stream.src_encoding = input_is_udp ? StreamConfig::STREAM_ENCODING_RAW : StreamConfig::STREAM_ENCODING_METACUBE;
377 } else if (src_encoding_parm_it->second == "metacube") {
379 log(ERROR, "UDP streams cannot have Metacube input");
382 stream.src_encoding = StreamConfig::STREAM_ENCODING_METACUBE;
383 } else if (src_encoding_parm_it->second == "raw") {
384 stream.src_encoding = StreamConfig::STREAM_ENCODING_RAW;
386 log(ERROR, "Parameter 'src_encoding' must be either 'raw' (default for UDP) or 'metacube' (default for HTTP)");
390 // Parse the pacing rate, converting from kilobits to bytes as needed.
391 const auto pacing_rate_it = line.parameters.find("pacing_rate_kbit");
392 if (pacing_rate_it == line.parameters.end()) {
393 stream.pacing_rate = ~0U;
395 stream.pacing_rate = stoll(pacing_rate_it->second.c_str()) * 1024 / 8;
398 // Parse the HLS URL, if any.
399 const auto hls_url_it = line.parameters.find("hls_playlist");
400 if (hls_url_it != line.parameters.end()) {
401 stream.hls_url = hls_url_it->second;
402 if (stream.hls_url.empty()) {
403 log(ERROR, "Parameter 'hls_playlist' was given but empty");
406 if (stream.encoding == StreamConfig::STREAM_ENCODING_METACUBE) {
407 log(ERROR, "HLS cannot be used with Metacube output");
412 // Parse the HLS fragment duration, if any.
413 const auto hls_frag_duration_it = line.parameters.find("hls_frag_duration");
414 if (hls_frag_duration_it != line.parameters.end()) {
415 if (stream.hls_url.empty()) {
416 log(ERROR, "Parameter 'hls_frag_duration' given, but no 'hls_playlist' given");
419 stream.hls_frag_duration = stoi(hls_frag_duration_it->second);
420 if (stream.hls_frag_duration <= 0) {
421 log(ERROR, "'hls_frag_duration' must be a strictly positive integer");
426 // Parse the HLS backlog margin, if any.
427 const auto hls_backlog_margin_it = line.parameters.find("hls_backlog_margin");
428 if (hls_backlog_margin_it != line.parameters.end()) {
429 if (stream.hls_url.empty()) {
430 log(ERROR, "Parameter 'hls_backlog_margin' given, but no 'hls_playlist' given");
433 stream.hls_backlog_margin = stoi(hls_backlog_margin_it->second);
434 if (stream.hls_backlog_margin >= stream.backlog_size) {
435 log(ERROR, "'hls_backlog_margin' must be nonnegative, but less than the backlog size");
440 // Parse the CORS origin, if it exists.
441 const auto allow_origin_it = line.parameters.find("allow_origin");
442 if (allow_origin_it != line.parameters.end()) {
443 stream.allow_origin = allow_origin_it->second;
446 config->streams.push_back(stream);
450 bool parse_udpstream(const ConfigLine &line, Config *config)
452 if (line.arguments.size() != 1) {
453 log(ERROR, "'udpstream' takes exactly one argument");
457 UDPStreamConfig udpstream;
459 string hostport = line.arguments[0];
460 if (!parse_hostport(hostport, &udpstream.dst)) {
464 const auto src_it = line.parameters.find("src");
465 if (src_it == line.parameters.end()) {
466 // This is pretty meaningless, but OK, consistency is good.
467 log(WARNING, "udpstream to %s has no src= attribute, clients will not get any data.",
470 udpstream.src = src_it->second;
471 // TODO: Verify that the URL is parseable?
474 // Parse the pacing rate, converting from kilobits to bytes as needed.
475 const auto pacing_rate_it = line.parameters.find("pacing_rate_kbit");
476 if (pacing_rate_it == line.parameters.end()) {
477 udpstream.pacing_rate = ~0U;
479 udpstream.pacing_rate = stoi(pacing_rate_it->second) * 1024 / 8;
482 // Parse the TTL. The same value is used for unicast and multicast.
483 const auto ttl_it = line.parameters.find("ttl");
484 if (ttl_it == line.parameters.end()) {
487 udpstream.ttl = stoi(ttl_it->second);
490 // Parse the multicast interface index.
491 const auto multicast_iface_it = line.parameters.find("multicast_output_interface");
492 if (multicast_iface_it == line.parameters.end()) {
493 udpstream.multicast_iface_index = -1;
495 udpstream.multicast_iface_index = if_nametoindex(multicast_iface_it->second.c_str());
496 if (udpstream.multicast_iface_index == 0) {
497 log(ERROR, "Interface '%s' does not exist", multicast_iface_it->second.c_str());
502 config->udpstreams.push_back(udpstream);
506 bool parse_gen204(const ConfigLine &line, Config *config)
508 if (line.arguments.size() != 1) {
509 log(ERROR, "'gen204' takes exactly one argument");
514 gen204.url = line.arguments[0];
516 // Parse the CORS origin, if it exists.
517 const auto allow_origin_it = line.parameters.find("allow_origin");
518 if (allow_origin_it != line.parameters.end()) {
519 gen204.allow_origin = allow_origin_it->second;
522 config->pings.push_back(gen204);
526 bool parse_error_log(const ConfigLine &line, Config *config)
528 if (line.arguments.size() != 0) {
529 log(ERROR, "'error_log' takes no arguments (only parameters type= and filename=)");
533 LogConfig log_config;
534 const auto type_it = line.parameters.find("type");
535 if (type_it == line.parameters.end()) {
536 log(ERROR, "'error_log' has no type= parameter");
540 string type = type_it->second;
541 if (type == "file") {
542 log_config.type = LogConfig::LOG_TYPE_FILE;
543 } else if (type == "syslog") {
544 log_config.type = LogConfig::LOG_TYPE_SYSLOG;
545 } else if (type == "console") {
546 log_config.type = LogConfig::LOG_TYPE_CONSOLE;
548 log(ERROR, "Unknown log type '%s'", type.c_str());
552 if (log_config.type == LogConfig::LOG_TYPE_FILE) {
553 const auto filename_it = line.parameters.find("filename");
554 if (filename_it == line.parameters.end()) {
555 log(ERROR, "error_log type 'file' with no filename= parameter");
558 log_config.filename = filename_it->second;
561 config->log_destinations.push_back(log_config);
567 bool parse_config(const string &filename, Config *config)
569 vector<ConfigLine> lines;
570 if (!read_config(filename, &lines)) {
574 config->daemonize = false;
576 if (!fetch_config_int(lines, "num_servers", &config->num_servers)) {
577 log(ERROR, "Missing 'num_servers' statement in config file.");
580 if (config->num_servers < 1 || config->num_servers >= 20000) { // Insanely high max limit.
581 log(ERROR, "'num_servers' is %d, needs to be in [1, 20000>.", config->num_servers);
585 // See if the user wants stats.
586 config->stats_interval = 60;
587 bool has_stats_file = fetch_config_string(lines, "stats_file", &config->stats_file);
588 bool has_stats_interval = fetch_config_int(lines, "stats_interval", &config->stats_interval);
589 if (has_stats_interval && !has_stats_file) {
590 log(WARNING, "'stats_interval' given, but no 'stats_file'. No client statistics will be written.");
593 config->input_stats_interval = 60;
594 bool has_input_stats_file = fetch_config_string(lines, "input_stats_file", &config->input_stats_file);
595 bool has_input_stats_interval = fetch_config_int(lines, "input_stats_interval", &config->input_stats_interval);
596 if (has_input_stats_interval && !has_input_stats_file) {
597 log(WARNING, "'input_stats_interval' given, but no 'input_stats_file'. No input statistics will be written.");
600 fetch_config_string(lines, "access_log", &config->access_log_file);
602 for (const ConfigLine &line : lines) {
603 if (line.keyword == "num_servers" ||
604 line.keyword == "stats_file" ||
605 line.keyword == "stats_interval" ||
606 line.keyword == "input_stats_file" ||
607 line.keyword == "input_stats_interval" ||
608 line.keyword == "access_log") {
609 // Already taken care of, above.
610 } else if (line.keyword == "port") {
611 if (!parse_port(line, config)) {
614 } else if (line.keyword == "listen") {
615 if (!parse_listen(line, config)) {
618 } else if (line.keyword == "stream") {
619 if (!parse_stream(line, config)) {
622 } else if (line.keyword == "udpstream") {
623 if (!parse_udpstream(line, config)) {
626 } else if (line.keyword == "gen204") {
627 if (!parse_gen204(line, config)) {
630 } else if (line.keyword == "error_log") {
631 if (!parse_error_log(line, config)) {
634 } else if (line.keyword == "daemonize") {
635 config->daemonize = true;
637 log(ERROR, "Unknown configuration keyword '%s'.",
638 line.keyword.c_str());