From dcecf316a0813ae009af5aaa2ca938a53c1a3835 Mon Sep 17 00:00:00 2001 From: Michael Poole Date: Thu, 7 Dec 2006 05:14:51 +0000 Subject: [PATCH] Allow per-port specification of address family, and use separate sockets for IPv4 and IPv6 listeners (SF#1571346). git-svn-id: file:///home/klmitch/undernet-ircu/undernet-ircu-svn/ircu2/branches/u2_10_12_branch@1732 c9e4aea6-c8fd-4c43-8297-357d70d61c8c --- ChangeLog | 38 ++++++++++++++++++++ doc/example.conf | 12 ++++--- include/listener.h | 10 ++++-- ircd/ircd_lexer.l | 8 +++-- ircd/ircd_parser.y | 42 +++++++++++++++++++--- ircd/listener.c | 90 +++++++++++++++++++++++++++++++++------------- ircd/os_generic.c | 4 +-- 7 files changed, 163 insertions(+), 41 deletions(-) diff --git a/ChangeLog b/ChangeLog index 7cb258c..93f78e0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,41 @@ +2006-12-07 Michael Poole + + * doc/example.conf (Port): Document the method to select IPv4 or + IPv6 restriction for a port. + + * include/listener.h (LISTEN_IPV4): New listener flag. + (LISTEN_IPV6): New listener flag. + (struct Listener): Split 'fd' and 'socket' fields into two each. + + * ircd/ircd_lexer.l (gb): Move to be alphabetical. + (gigabytes): Likewise. + (ipv4): New token. + (ipv6): Likewise. + Adapted word matcher to handle digits in the non-leading character. + + * ircd/ircd_parser.y (USE_IPV4): New macro. + (USE_IPV6): Likewise. + (TOK_IPV4): New token. + (TOK_IPV6): Likewise. + (address_family): New non-terminal rule. + (portblock): Default to listening on both IPv4 and IPv6. + (portnumber): Add address_family element and use it. + (portvhost): Likewise. + + * ircd/listener.c (make_listener): Adjust for newly split fields + in struct Listener. + (inetport): Likewise. Adjust return value as well. + (add_listener): Update to handle both IPv4 and IPV6 support. + (close_listener): Likewise. + (accept_connection): Because each listener has two sockets, it is + no longer safe to free the listener when one is destroyed -- so + don't. Also accept() on the file descriptor from the incoming + event rather than on a fixed fd. + + * ircd/os_generic.c (os_socket): For platforms with IPV6_V6ONLY, + enable it for AF_INET6 sockets rather than disabling it for + unspecified sockets. + 2006-12-06 Michael Poole * include/listener.h (enum ListenerFlag): New enum. diff --git a/doc/example.conf b/doc/example.conf index cbb4d7e..ae0aa5c 100644 --- a/doc/example.conf +++ b/doc/example.conf @@ -650,16 +650,20 @@ Operator { # so we don't do that. # # Port { -# port = number; +# port = [ipv4] [ipv6] number; # mask = "ipmask"; # # Use this to control the interface you bind to. -# vhost = "virtualhostip"; +# vhost = [ipv4] [ipv6] "virtualhostip"; # # Setting to yes makes this server only. # server = yes; # # Setting to yes makes the port "hidden" from stats. # hidden = yes; # }; # +# The port and vhost lines allow you to specify one or both of "ipv4" +# and "ipv6" as address families to use for the port. The default is +# to listen on both IPv4 and IPv6. +# # The mask setting allows you to specify a range of IP addresses that # you will allow connections from. This should only contain IP addresses # and '*' if used. This field only uses IP addresses. This does not use @@ -679,11 +683,11 @@ Port { port = 4400; }; -# This is a Server port that is Hidden +# This is an IPv4-only Server port that is Hidden Port { server = yes; hidden = yes; - port = 4401; + port = ipv4 4401; }; # The following are normal client ports diff --git a/include/listener.h b/include/listener.h index 4dd74db..47dfff1 100644 --- a/include/listener.h +++ b/include/listener.h @@ -48,6 +48,10 @@ enum ListenerFlag { LISTEN_HIDDEN, /** Port accepts only server connections. */ LISTEN_SERVER, + /** Port listens for IPv4 connections. */ + LISTEN_IPV4, + /** Port listens for IPv6 connections. */ + LISTEN_IPV6, /** Sentinel for counting listener flags. */ LISTEN_LAST_FLAG }; @@ -58,14 +62,16 @@ DECLARE_FLAGSET(ListenerFlags, LISTEN_LAST_FLAG); struct Listener { struct Listener* next; /**< list node pointer */ struct ListenerFlags flags; /**< on-off flags for listener */ - int fd; /**< file descriptor */ + int fd_v4; /**< file descriptor for IPv4 */ + int fd_v6; /**< file descriptor for IPv6 */ int ref_count; /**< number of connection references */ unsigned char mask_bits; /**< number of bits in mask address */ int index; /**< index into poll array */ time_t last_accept; /**< last time listener accepted */ struct irc_sockaddr addr; /**< virtual address and port */ struct irc_in_addr mask; /**< listener hostmask */ - struct Socket socket; /**< describe socket to event system */ + struct Socket socket_v4; /**< describe IPv4 socket to event system */ + struct Socket socket_v6; /**< describe IPv6 socket to event system */ }; #define listener_server(LISTENER) FlagHas(&(LISTENER)->flags, LISTEN_SERVER) diff --git a/ircd/ircd_lexer.l b/ircd/ircd_lexer.l index d2e2443..231c64b 100644 --- a/ircd/ircd_lexer.l +++ b/ircd/ircd_lexer.l @@ -116,7 +116,11 @@ static struct lexer_token { { "file", TFILE }, { "force_local_opmode", TPRIV_FORCE_LOCAL_OPMODE }, { "force_opmode", TPRIV_FORCE_OPMODE }, + { "gb", GBYTES }, + { "gigabytes", GBYTES }, { "gline", TPRIV_GLINE }, + { "ipv4", TOK_IPV4 }, + { "ipv6", TOK_IPV6 }, { "kb", KBYTES }, { "kilobytes", KBYTES }, { "list_chan", TPRIV_LIST_CHAN }, @@ -128,8 +132,6 @@ static struct lexer_token { { "mb", MBYTES }, { "megabytes", MBYTES }, { "mode_lchan", TPRIV_MODE_LCHAN }, - { "gb", GBYTES }, - { "gigabytes", GBYTES }, { "operator", OPER }, { "opmode", TPRIV_OPMODE }, { "password", PASS }, @@ -213,6 +215,6 @@ QSTRING \"[^"\n]+[\"\n] {WHITE} ; {SHCOMMENT} ; -[a-zA-Z_]+ { int res = find_token(yytext); if (res) return res; else REJECT; } +[a-zA-Z_][a-zA-Z_0-9]* { int res = find_token(yytext); if (res) return res; else REJECT; } \n lineno++; . return yytext[0]; diff --git a/ircd/ircd_parser.y b/ircd/ircd_parser.y index 2b7103d..2c0dddc 100644 --- a/ircd/ircd_parser.y +++ b/ircd/ircd_parser.y @@ -58,7 +58,11 @@ #include #include #include + #define MAX_STRINGS 80 /* Maximum number of feature params. */ +#define USE_IPV4 (1 << 0) +#define USE_IPV6 (1 << 1) + extern struct LocalConf localConf; extern struct DenyConf* denyConfList; extern struct CRuleConf* cruleConfList; @@ -158,6 +162,7 @@ static void parse_error(char *pattern,...) { %token FAST %token AUTOCONNECT %token PROGRAM +%token TOK_IPV4 TOK_IPV6 /* and now a lot of privileges... */ %token TPRIV_CHAN_LIMIT TPRIV_MODE_LCHAN TPRIV_DEOP_LCHAN TPRIV_WALK_LCHAN %token TPRIV_LOCAL_KILL TPRIV_REHASH TPRIV_RESTART TPRIV_DIE @@ -170,7 +175,7 @@ static void parse_error(char *pattern,...) { /* and some types... */ %type sizespec %type timespec timefactor factoredtimes factoredtime -%type expr yesorno privtype +%type expr yesorno privtype address_family %left '+' '-' %left '*' '/' @@ -631,9 +636,26 @@ privtype: TPRIV_CHAN_LIMIT { $$ = PRIV_CHAN_LIMIT; } | yesorno: YES { $$ = 1; } | NO { $$ = 0; }; +/* not a recursive definition because some pedant will just come along + * and whine that the parser accepts "ipv4 ipv4 ipv4 ipv4" + */ +address_family: + { $$ = 0; } + | TOK_IPV4 { $$ = USE_IPV4; } + | TOK_IPV6 { $$ = USE_IPV6; } + | TOK_IPV4 TOK_IPV6 { $$ = USE_IPV4 | USE_IPV6; } + | TOK_IPV6 TOK_IPV4 { $$ = USE_IPV6 | USE_IPV4; } + ; + /* The port block... */ portblock: PORT '{' portitems '}' ';' { + if (!FlagHas(&listen_flags, LISTEN_IPV4) + && !FlagHas(&listen_flags, LISTEN_IPV6)) + { + FlagSet(&listen_flags, LISTEN_IPV4); + FlagSet(&listen_flags, LISTEN_IPV6); + } if (port > 0 && port <= 0xFFFF) add_listener(port, host, pass, &listen_flags); else @@ -646,15 +668,25 @@ portblock: PORT '{' portitems '}' ';' }; portitems: portitem portitems | portitem; portitem: portnumber | portvhost | portmask | portserver | porthidden; -portnumber: PORT '=' NUMBER ';' +portnumber: PORT '=' address_family NUMBER ';' { - port = $3; + int families = $3; + if (families & USE_IPV4) + FlagSet(&listen_flags, LISTEN_IPV4); + else if (families & USE_IPV6) + FlagSet(&listen_flags, LISTEN_IPV6); + port = $4; }; -portvhost: VHOST '=' QSTRING ';' +portvhost: VHOST '=' address_family QSTRING ';' { + int families = $3; + if (families & USE_IPV4) + FlagSet(&listen_flags, LISTEN_IPV4); + else if (families & USE_IPV6) + FlagSet(&listen_flags, LISTEN_IPV6); MyFree(host); - host = $3; + host = $4; }; portmask: MASK '=' QSTRING ';' diff --git a/ircd/listener.c b/ircd/listener.c index 0f30a3d..31d1ba5 100644 --- a/ircd/listener.c +++ b/ircd/listener.c @@ -70,7 +70,8 @@ static struct Listener* make_listener(int port, const struct irc_in_addr *addr) memset(listener, 0, sizeof(struct Listener)); - listener->fd = -1; + listener->fd_v4 = -1; + listener->fd_v6 = -1; listener->addr.port = port; memcpy(&listener->addr.addr, addr, sizeof(listener->addr.addr)); @@ -210,35 +211,36 @@ static int set_listener_options(struct Listener *listener, int fd) /** Open listening socket for \a listener. * @param[in,out] listener Listener to make a socket for. - * @return Non-zero on success, zero on failure. + * @param[in] family Socket address family to use. + * @return Negative on failure, file descriptor on success. */ -static int inetport(struct Listener* listener) +static int inetport(struct Listener* listener, int family) { + struct Socket *sock; int fd; /* * At first, open a new socket */ - fd = os_socket(&listener->addr, SOCK_STREAM, get_listener_name(listener), 0); + fd = os_socket(&listener->addr, SOCK_STREAM, get_listener_name(listener), family); if (fd < 0) - return 0; + return -1; if (!os_set_listen(fd, HYBRID_SOMAXCONN)) { report_error(LISTEN_ERROR_MSG, get_listener_name(listener), errno); close(fd); - return 0; + return -1; } if (!set_listener_options(listener, fd)) - return 0; - if (!socket_add(&listener->socket, accept_connection, (void*) listener, + return -1; + sock = (family == AF_INET) ? &listener->socket_v4 : &listener->socket_v6; + if (!socket_add(sock, accept_connection, (void*) listener, SS_LISTENING, 0, fd)) { /* Error should already have been reported to the logs */ close(fd); - return 0; + return -1; } - listener->fd = fd; - - return 1; + return fd; } /** Find the listener (if any) for a particular port and address. @@ -270,6 +272,9 @@ void add_listener(int port, const char* vhost_ip, const char* mask, { struct Listener* listener; struct irc_in_addr vaddr; + int okay = 0; + int new_listener = 0; + int fd; /* * if no port in conf line, don't bother @@ -286,7 +291,10 @@ void add_listener(int port, const char* vhost_ip, const char* mask, listener = find_listener(port, &vaddr); if (!listener) + { + new_listener = 1; listener = make_listener(port, &vaddr); + } memcpy(&listener->flags, flags, sizeof(listener->flags)); FlagSet(&listener->flags, LISTEN_ACTIVE); if (mask) @@ -294,18 +302,42 @@ void add_listener(int port, const char* vhost_ip, const char* mask, else listener->mask_bits = 0; - if (listener->fd >= 0) { - /* If the listener is already open, do not try to re-open. - * Only update the socket options. - */ - set_listener_options(listener, listener->fd); +#ifdef IPV6 + if (FlagHas(&listener->flags, LISTEN_IPV6)) { + if (listener->fd_v6 >= 0) { + set_listener_options(listener, listener->fd_v6); + okay = 1; + } else if ((fd = inetport(listener, AF_INET6)) >= 0) { + listener->fd_v6 = fd; + okay = 1; + } + } else if (-1 < listener->fd_v6) { + close(listener->fd_v6); + socket_del(&listener->socket_v6); + listener->fd_v6 = -1; } - else if (inetport(listener)) { +#endif + + if (FlagHas(&listener->flags, LISTEN_IPV4)) { + if (listener->fd_v4 >= 0) { + set_listener_options(listener, listener->fd_v4); + okay = 1; + } else if ((fd = inetport(listener, AF_INET)) >= 0) { + listener->fd_v4 = fd; + okay = 1; + } + } else if (-1 < listener->fd_v4) { + close(listener->fd_v4); + socket_del(&listener->socket_v4); + listener->fd_v4 = -1; + } + + if (!okay) + free_listener(listener); + else if (new_listener) { listener->next = ListenerPollList; ListenerPollList = listener; } - else - free_listener(listener); } /** Mark all listeners as closing (inactive). @@ -338,9 +370,17 @@ void close_listener(struct Listener* listener) } } } - if (-1 < listener->fd) - close(listener->fd); - socket_del(&listener->socket); + if (-1 < listener->fd_v4) { + close(listener->fd_v4); + socket_del(&listener->socket_v4); + listener->fd_v4 = -1; + } + if (-1 < listener->fd_v6) { + close(listener->fd_v6); + socket_del(&listener->socket_v6); + listener->fd_v6 = -1; + } + free_listener(listener); } /** Close all inactive listeners. */ @@ -384,7 +424,7 @@ static void accept_connection(struct Event* ev) listener = (struct Listener*) s_data(ev_socket(ev)); if (ev_type(ev) == ET_DESTROY) /* being destroyed */ - free_listener(listener); + return; else { assert(ev_type(ev) == ET_ACCEPT || ev_type(ev) == ET_ERROR); @@ -409,7 +449,7 @@ static void accept_connection(struct Event* ev) */ while (1) { - if ((fd = os_accept(listener->fd, &addr)) == -1) + if ((fd = os_accept(s_fd(ev_socket(ev)), &addr)) == -1) { if (errno == EAGAIN || #ifdef EWOULDBLOCK diff --git a/ircd/os_generic.c b/ircd/os_generic.c index bdcda7c..72fba12 100644 --- a/ircd/os_generic.c +++ b/ircd/os_generic.c @@ -605,8 +605,8 @@ int os_socket(const struct irc_sockaddr* local, int type, const char* port_name, } if (local) { #if defined(IPV6_V6ONLY) - int on = 0; - if (family == 0 && irc_in_addr_unspec(&local->addr)) + int on = 1; + if (family == AF_INET6 && irc_in_addr_unspec(&local->addr)) setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); #endif if (bind(fd, (struct sockaddr*)&addr, size)) { -- 2.20.1