ircd: server connection configuration

Fix the server connection configuration so that it can simultaneously
handle a hostname/IPv4/IPv6 for connecting and a hostname/IPv4/IPv6
for binding. Maintains backwards compatibility for matching a hostname
with a mask.

Multiple host/vhost entries can be specified and the last value for
each address family is stored. Hostnames that resolve automatically
overwrite the IP address.

Server connections can now be made to either IPv4 or IPv6 at random
as well as preferring a specific address family.
This commit is contained in:
Simon Arlott 2016-04-24 17:05:05 +01:00
parent 65f43a4fc4
commit d4214e9445
No known key found for this signature in database
GPG key ID: C8975F2043CA5D24
9 changed files with 248 additions and 121 deletions

View file

@ -310,10 +310,6 @@ connect "irc.uplink.com" {
flags = compressed, topicburst;
#fingerprint = "c77106576abf7f9f90cca0f63874a60f2e40a64b";
/* If the connection is IPv6, uncomment below.
* Use 0::1, not ::1, for IPv6 localhost. */
#aftype = ipv6;
};
connect "ssl.uplink.com" {
@ -454,7 +450,7 @@ opm {
* to be effective.
* If omitted, it defaults to serverinfo::vhost6.
*/
#listen_ipv6 = "0::1";
#listen_ipv6 = "::1";
/* IPv6 port to listen on.
* This should not be the same as any existing listeners.

View file

@ -571,17 +571,14 @@ connect "irc.uplink.com" {
};
connect "ipv6.lame.server" {
/* Hosts that are IPv6 addresses must be in :: shortened form
* if applicable. Addresses starting with a colon get an extra
* zero prepended, for example: 0::1
*/
host = "192.0.2.1";
host = "2001:db8:3::8";
send_password = "password";
accept_password = "password";
port = 6666;
/* aftype: controls whether the connection uses "ipv4" or "ipv6".
* Default is ipv4.
/* aftype: controls whether the outgoing connection uses "ipv4" or "ipv6".
* Default is to try either at random.
*/
aftype = ipv6;
class = "server";
@ -930,7 +927,7 @@ opm {
* to be effective.
* If omitted, it defaults to serverinfo::vhost6.
*/
#listen_ipv6 = "0::1";
#listen_ipv6 = "::1";
/* IPv6 port to listen on.
* This should not be the same as any existing listeners.

View file

@ -105,7 +105,7 @@ serverinfo {
<term>vhost</term>
<listitem>
<para>
An optional text field which defines an IP from which to connect outward to other IRC servers.
An optional text field which defines an IPv4 address from which to connect outward to other IRC servers.
</para>
</listitem>
</varlistentry>
@ -113,7 +113,7 @@ serverinfo {
<term>vhost6</term>
<listitem>
<para>
An optional text field which defines an IPv6 IP from which to connect outward to other IRC servers.
An optional text field which defines an IPv6 address from which to connect outward to other IRC servers.
</para>
</listitem>
</varlistentry>
@ -599,10 +599,6 @@ connect "<replaceable>name</replaceable>" {
Furthermore, if a hostname is used, it must have an A or AAAA
record (no CNAME) and it must be the primary
hostname for inbound connections to work.
</para><para>
IPv6 addresses must be in :: shortened form; addresses which
then start with a colon must be prepended with a zero,
for example 0::1.
</para></note>
</listitem>
</varlistentry>
@ -662,7 +658,7 @@ connect "<replaceable>name</replaceable>" {
<varlistentry>
<term>aftype</term>
<listitem>
<para>The protocol that should be used to connect with, either ipv4 or ipv6. This defaults to ipv4 unless host is a numeric IPv6 address.</para>
<para>The protocol that must be used to connect with, either ipv4 or ipv6. This defaults to neither, allowing connection using either address family.</para>
</listitem>
</varlistentry>
</variablelist>

View file

@ -282,14 +282,10 @@ struct server_info
char *description;
char *network_name;
int hub;
struct sockaddr_in ip;
struct rb_sockaddr_storage bind4;
int default_max_clients;
#ifdef RB_IPV6
struct sockaddr_in6 ip6;
#endif
int specific_ipv4_vhost;
#ifdef RB_IPV6
int specific_ipv6_vhost;
struct rb_sockaddr_storage bind6;
#endif
char *ssl_private_key;
char *ssl_ca_cert;

View file

@ -178,7 +178,13 @@ extern const char *get_oper_privs(int flags);
struct server_conf
{
char *name;
char *host;
char *connect_host;
struct rb_sockaddr_storage connect4;
uint16_t dns_query_connect4;
#ifdef RB_IPV6
struct rb_sockaddr_storage connect6;
uint16_t dns_query_connect6;
#endif
char *passwd;
char *spasswd;
char *certfp;
@ -188,17 +194,20 @@ struct server_conf
time_t hold;
int aftype;
struct rb_sockaddr_storage my_ipnum;
char *bind_host;
struct rb_sockaddr_storage bind4;
uint16_t dns_query_bind4;
#ifdef RB_IPV6
struct rb_sockaddr_storage bind6;
uint16_t dns_query_bind6;
#endif
char *class_name;
struct Class *class;
rb_dlink_node node;
uint16_t dns_query;
};
#define SERVER_ILLEGAL 0x0001
#define SERVER_VHOSTED 0x0002
#define SERVER_ENCRYPTED 0x0004
#define SERVER_COMPRESSED 0x0008
#define SERVER_TB 0x0010
@ -206,7 +215,6 @@ struct server_conf
#define SERVER_SSL 0x0040
#define ServerConfIllegal(x) ((x)->flags & SERVER_ILLEGAL)
#define ServerConfVhosted(x) ((x)->flags & SERVER_VHOSTED)
#define ServerConfEncrypted(x) ((x)->flags & SERVER_ENCRYPTED)
#define ServerConfCompressed(x) ((x)->flags & SERVER_COMPRESSED)
#define ServerConfTb(x) ((x)->flags & SERVER_TB)

View file

@ -243,27 +243,31 @@ conf_set_serverinfo_network_name(void *data)
static void
conf_set_serverinfo_vhost(void *data)
{
if(rb_inet_pton(AF_INET, (char *) data, &ServerInfo.ip.sin_addr) <= 0)
struct rb_sockaddr_storage addr;
if(rb_inet_pton_sock(data, (struct sockaddr *)&addr) <= 0 || GET_SS_FAMILY(&addr) != AF_INET)
{
conf_report_error("Invalid IPv4 address for server vhost (%s)", (char *) data);
return;
}
ServerInfo.ip.sin_family = AF_INET;
ServerInfo.specific_ipv4_vhost = 1;
ServerInfo.bind4 = addr;
}
static void
conf_set_serverinfo_vhost6(void *data)
{
#ifdef RB_IPV6
if(rb_inet_pton(AF_INET6, (char *) data, &ServerInfo.ip6.sin6_addr) <= 0)
struct rb_sockaddr_storage addr;
if(rb_inet_pton_sock(data, (struct sockaddr *)&addr) <= 0 || GET_SS_FAMILY(&addr) != AF_INET6)
{
conf_report_error("Invalid IPv6 address for server vhost (%s)", (char *) data);
return;
}
ServerInfo.specific_ipv6_vhost = 1;
ServerInfo.ip6.sin6_family = AF_INET6;
ServerInfo.bind6 = addr;
#else
conf_report_error("Warning -- ignoring serverinfo::vhost6 -- IPv6 support not available.");
#endif
@ -1301,7 +1305,12 @@ conf_end_connect(struct TopConf *tc)
return 0;
}
if(EmptyString(yy_server->host))
if(EmptyString(yy_server->connect_host)
&& GET_SS_FAMILY(&yy_server->connect4) != AF_INET
#ifdef RB_IPV6
&& GET_SS_FAMILY(&yy_server->connect6) != AF_INET6
#endif
)
{
conf_report_error("Ignoring connect block for %s -- missing host.",
yy_server->name);
@ -1326,23 +1335,57 @@ conf_end_connect(struct TopConf *tc)
static void
conf_set_connect_host(void *data)
{
rb_free(yy_server->host);
yy_server->host = rb_strdup(data);
if (strchr(yy_server->host, ':'))
yy_server->aftype = AF_INET6;
struct rb_sockaddr_storage addr;
if(rb_inet_pton_sock(data, (struct sockaddr *)&addr) <= 0)
{
rb_free(yy_server->connect_host);
yy_server->connect_host = rb_strdup(data);
}
else if(GET_SS_FAMILY(&addr) == AF_INET)
{
yy_server->connect4 = addr;
}
#ifdef RB_IPV6
else if(GET_SS_FAMILY(&addr) == AF_INET6)
{
yy_server->connect6 = addr;
}
#endif
else
{
conf_report_error("Unsupported IP address for server connect host (%s)",
(char *)data);
return;
}
}
static void
conf_set_connect_vhost(void *data)
{
if(rb_inet_pton_sock(data, (struct sockaddr *)&yy_server->my_ipnum) <= 0)
struct rb_sockaddr_storage addr;
if(rb_inet_pton_sock(data, (struct sockaddr *)&addr) <= 0)
{
conf_report_error("Invalid IP address for server connect vhost (%s)",
(char *) data);
rb_free(yy_server->bind_host);
yy_server->bind_host = rb_strdup(data);
}
else if(GET_SS_FAMILY(&addr) == AF_INET)
{
yy_server->bind4 = addr;
}
#ifdef RB_IPV6
else if(GET_SS_FAMILY(&addr) == AF_INET6)
{
yy_server->bind6 = addr;
}
#endif
else
{
conf_report_error("Unsupported IP address for server connect vhost (%s)",
(char *)data);
return;
}
yy_server->flags |= SERVER_VHOSTED;
}
static void
@ -2071,7 +2114,7 @@ conf_end_opm(struct TopConf *tc)
else
{
char ip[HOSTIPLEN];
if(!rb_inet_ntop_sock((struct sockaddr *)&ServerInfo.ip, ip, sizeof(ip)))
if(!rb_inet_ntop_sock((struct sockaddr *)&ServerInfo.bind4, ip, sizeof(ip)))
conf_report_error("No opm::listen_ipv4 nor serverinfo::vhost directive; cannot listen on IPv4");
else
conf_create_opm_listener(ip, yy_opm_port_ipv4);
@ -2085,7 +2128,7 @@ conf_end_opm(struct TopConf *tc)
else
{
char ip[HOSTIPLEN];
if(!rb_inet_ntop_sock((struct sockaddr *)&ServerInfo.ip6, ip, sizeof(ip)))
if(!rb_inet_ntop_sock((struct sockaddr *)&ServerInfo.bind6, ip, sizeof(ip)))
conf_report_error("No opm::listen_ipv6 nor serverinfo::vhost directive; cannot listen on IPv6");
else
conf_create_opm_listener(ip, yy_opm_port_ipv6);

View file

@ -680,11 +680,11 @@ set_default_conf(void)
ServerInfo.description = NULL;
ServerInfo.network_name = NULL;
memset(&ServerInfo.ip, 0, sizeof(ServerInfo.ip));
ServerInfo.specific_ipv4_vhost = 0;
memset(&ServerInfo.bind4, 0, sizeof(ServerInfo.bind4));
SET_SS_FAMILY(&ServerInfo.bind4, AF_UNSPEC);
#ifdef RB_IPV6
memset(&ServerInfo.ip6, 0, sizeof(ServerInfo.ip6));
ServerInfo.specific_ipv6_vhost = 0;
memset(&ServerInfo.bind6, 0, sizeof(ServerInfo.bind6));
SET_SS_FAMILY(&ServerInfo.bind6, AF_UNSPEC);
#endif
AdminInfo.name = NULL;

View file

@ -324,8 +324,24 @@ struct server_conf *
make_server_conf(void)
{
struct server_conf *server_p = rb_malloc(sizeof(struct server_conf));
server_p->aftype = AF_INET;
return server_p;
SET_SS_FAMILY(&server_p->connect4, AF_UNSPEC);
SET_SS_LEN(&server_p->connect4, sizeof(struct sockaddr_in));
SET_SS_FAMILY(&server_p->bind4, AF_UNSPEC);
SET_SS_LEN(&server_p->bind4, sizeof(struct sockaddr_in));
#ifdef RB_IPV6
SET_SS_FAMILY(&server_p->connect6, AF_UNSPEC);
SET_SS_LEN(&server_p->connect6, sizeof(struct sockaddr_in6));
SET_SS_FAMILY(&server_p->bind6, AF_UNSPEC);
SET_SS_LEN(&server_p->bind6, sizeof(struct sockaddr_in6));
#endif
server_p->aftype = AF_UNSPEC;
return rb_malloc(sizeof(struct server_conf));
}
void
@ -348,13 +364,14 @@ free_server_conf(struct server_conf *server_p)
}
rb_free(server_p->name);
rb_free(server_p->host);
rb_free(server_p->connect_host);
rb_free(server_p->bind_host);
rb_free(server_p->class_name);
rb_free(server_p);
}
/*
* conf_dns_callback
* conf_connect_dns_callback
* inputs - pointer to struct ConfItem
* - pointer to adns reply
* output - none
@ -364,14 +381,59 @@ free_server_conf(struct server_conf *server_p)
* if successful save hp in the conf item it was called with
*/
static void
conf_dns_callback(const char *result, int status, int aftype, void *data)
conf_connect_dns_callback(const char *result, int status, int aftype, void *data)
{
struct server_conf *server_p = data;
if(status == 1)
rb_inet_pton_sock(result, (struct sockaddr *)&server_p->my_ipnum);
if(aftype == AF_INET)
{
if(status == 1)
rb_inet_pton_sock(result, (struct sockaddr *)&server_p->connect4);
server_p->dns_query = 0;
server_p->dns_query_connect4 = 0;
}
#ifdef RB_IPV6
else if(aftype == AF_INET6)
{
if(status == 1)
rb_inet_pton_sock(result, (struct sockaddr *)&server_p->connect6);
server_p->dns_query_connect6 = 0;
}
#endif
}
/*
* conf_bind_dns_callback
* inputs - pointer to struct ConfItem
* - pointer to adns reply
* output - none
* side effects - called when resolver query finishes
* if the query resulted in a successful search, hp will contain
* a non-null pointer, otherwise hp will be null.
* if successful save hp in the conf item it was called with
*/
static void
conf_bind_dns_callback(const char *result, int status, int aftype, void *data)
{
struct server_conf *server_p = data;
if(aftype == AF_INET)
{
if(status == 1)
rb_inet_pton_sock(result, (struct sockaddr *)&server_p->bind4);
server_p->dns_query_bind4 = 0;
}
#ifdef RB_IPV6
else if(aftype == AF_INET6)
{
if(status == 1)
rb_inet_pton_sock(result, (struct sockaddr *)&server_p->bind6);
server_p->dns_query_bind6 = 0;
}
#endif
}
void
@ -395,11 +457,25 @@ add_server_conf(struct server_conf *server_p)
server_p->class_name = rb_strdup("default");
}
if(strpbrk(server_p->host, "*?"))
return;
if(server_p->connect_host && !strpbrk(server_p->connect_host, "*?"))
{
server_p->dns_query_connect4 =
lookup_hostname(server_p->connect_host, AF_INET, conf_connect_dns_callback, server_p);
#ifdef RB_IPV6
server_p->dns_query_connect6 =
lookup_hostname(server_p->connect_host, AF_INET6, conf_connect_dns_callback, server_p);
#endif
}
server_p->dns_query =
lookup_hostname(server_p->host, GET_SS_FAMILY(&server_p->my_ipnum), conf_dns_callback, server_p);
if(server_p->bind_host)
{
server_p->dns_query_bind4 =
lookup_hostname(server_p->bind_host, AF_INET, conf_bind_dns_callback, server_p);
#ifdef RB_IPV6
server_p->dns_query_bind6 =
lookup_hostname(server_p->bind_host, AF_INET6, conf_bind_dns_callback, server_p);
#endif
}
}
struct server_conf *

View file

@ -363,6 +363,8 @@ check_server(const char *name, struct Client *client_p)
RB_DLINK_FOREACH(ptr, server_conf_list.head)
{
struct rb_sockaddr_storage client_addr;
tmp_p = ptr->data;
if(ServerConfIllegal(tmp_p))
@ -373,10 +375,19 @@ check_server(const char *name, struct Client *client_p)
name_matched = true;
/* XXX: Fix me for IPv6 */
/* XXX sockhost is the IPv4 ip as a string */
if(match(tmp_p->host, client_p->host) ||
match(tmp_p->host, client_p->sockhost))
if(rb_inet_pton_sock(client_p->sockhost, (struct sockaddr *)&client_addr) <= 0)
SET_SS_FAMILY(&client_addr, AF_UNSPEC);
if((tmp_p->connect_host && match(tmp_p->connect_host, client_p->host))
|| (GET_SS_FAMILY(&client_addr) == GET_SS_FAMILY(&tmp_p->connect4)
&& comp_with_mask_sock((struct sockaddr *)&client_addr,
(struct sockaddr *)&tmp_p->connect4, 32))
#ifdef RB_IPV6
|| (GET_SS_FAMILY(&client_addr) == GET_SS_FAMILY(&tmp_p->connect6)
&& comp_with_mask_sock((struct sockaddr *)&client_addr,
(struct sockaddr *)&tmp_p->connect6, 128))
#endif
)
{
host_matched = true;
@ -1010,7 +1021,8 @@ int
serv_connect(struct server_conf *server_p, struct Client *by)
{
struct Client *client_p;
struct rb_sockaddr_storage myipnum;
struct rb_sockaddr_storage sa_connect;
struct rb_sockaddr_storage sa_bind;
char note[HOSTLEN + 10];
rb_fde_t *F;
@ -1018,8 +1030,39 @@ serv_connect(struct server_conf *server_p, struct Client *by)
if(server_p == NULL)
return 0;
#ifdef RB_IPV6
if(server_p->aftype != AF_UNSPEC
&& GET_SS_FAMILY(&server_p->connect4) == AF_INET
&& GET_SS_FAMILY(&server_p->connect6) == AF_INET6)
{
if(rand() % 2 == 0)
{
sa_connect = server_p->connect4;
sa_bind = server_p->bind4;
}
else
{
sa_connect = server_p->connect6;
sa_bind = server_p->bind6;
}
}
else if(server_p->aftype == AF_INET || GET_SS_FAMILY(&server_p->connect4) == AF_INET)
#endif
{
sa_connect = server_p->connect4;
sa_bind = server_p->bind4;
}
#ifdef RB_IPV6
else if(server_p->aftype == AF_INET6 || GET_SS_FAMILY(&server_p->connect6) == AF_INET6)
{
sa_connect = server_p->connect6;
sa_bind = server_p->bind6;
}
#endif
/* log */
rb_inet_ntop_sock((struct sockaddr *)&server_p->my_ipnum, buf, sizeof(buf));
buf[0] = 0;
rb_inet_ntop_sock((struct sockaddr *)&sa_connect, buf, sizeof(buf));
ilog(L_SERVER, "Connect to *[%s] @%s", server_p->name, buf);
/*
@ -1037,7 +1080,12 @@ serv_connect(struct server_conf *server_p, struct Client *by)
}
/* create a socket for the server connection */
if((F = rb_socket(GET_SS_FAMILY(&server_p->my_ipnum), SOCK_STREAM, 0, NULL)) == NULL)
if(GET_SS_FAMILY(&sa_connect) == AF_UNSPEC)
{
ilog_error("unspecified socket address family");
return 0;
}
else if((F = rb_socket(GET_SS_FAMILY(&sa_connect), SOCK_STREAM, 0, NULL)) == NULL)
{
ilog_error("opening a stream socket");
return 0;
@ -1052,11 +1100,14 @@ serv_connect(struct server_conf *server_p, struct Client *by)
/* Copy in the server, hostname, fd */
rb_strlcpy(client_p->name, server_p->name, sizeof(client_p->name));
rb_strlcpy(client_p->host, server_p->host, sizeof(client_p->host));
if(server_p->connect_host)
rb_strlcpy(client_p->host, server_p->connect_host, sizeof(client_p->host));
else
rb_strlcpy(client_p->host, buf, sizeof(client_p->host));
rb_strlcpy(client_p->sockhost, buf, sizeof(client_p->sockhost));
client_p->localClient->F = F;
/* shove the port number into the sockaddr */
SET_SS_PORT(&server_p->my_ipnum, htons(server_p->port));
SET_SS_PORT(&sa_connect, htons(server_p->port));
/*
* Set up the initial server evilness, ripped straight from
@ -1091,58 +1142,22 @@ serv_connect(struct server_conf *server_p, struct Client *by)
SetConnecting(client_p);
rb_dlinkAddTail(client_p, &client_p->node, &global_client_list);
if(ServerConfVhosted(server_p))
if(GET_SS_FAMILY(&sa_bind) == AF_UNSPEC)
{
memcpy(&myipnum, &server_p->my_ipnum, sizeof(myipnum));
SET_SS_FAMILY(&myipnum, GET_SS_FAMILY(&server_p->my_ipnum));
SET_SS_PORT(&myipnum, 0);
}
else if(GET_SS_FAMILY(&server_p->my_ipnum) == AF_INET && ServerInfo.specific_ipv4_vhost)
{
memcpy(&myipnum, &ServerInfo.ip, sizeof(myipnum));
SET_SS_FAMILY(&myipnum, AF_INET);
SET_SS_PORT(&myipnum, 0);
SET_SS_LEN(&myipnum, sizeof(struct sockaddr_in));
}
if(GET_SS_FAMILY(&sa_connect) == GET_SS_FAMILY(&ServerInfo.bind4))
sa_bind = ServerInfo.bind4;
#ifdef RB_IPV6
else if((GET_SS_FAMILY(&server_p->my_ipnum) == AF_INET6) && ServerInfo.specific_ipv6_vhost)
{
memcpy(&myipnum, &ServerInfo.ip6, sizeof(myipnum));
SET_SS_FAMILY(&myipnum, AF_INET6);
SET_SS_PORT(&myipnum, 0);
SET_SS_LEN(&myipnum, sizeof(struct sockaddr_in6));
}
if(GET_SS_FAMILY(&sa_connect) == GET_SS_FAMILY(&ServerInfo.bind6))
sa_bind = ServerInfo.bind6;
#endif
else
{
if(ServerConfSSL(server_p))
{
rb_connect_tcp(client_p->localClient->F,
(struct sockaddr *)&server_p->my_ipnum, NULL, 0,
serv_connect_ssl_callback, client_p,
ConfigFileEntry.connect_timeout);
}
else
rb_connect_tcp(client_p->localClient->F,
(struct sockaddr *)&server_p->my_ipnum, NULL, 0,
serv_connect_callback, client_p,
ConfigFileEntry.connect_timeout);
return 1;
}
if(ServerConfSSL(server_p))
rb_connect_tcp(client_p->localClient->F, (struct sockaddr *)&server_p->my_ipnum,
(struct sockaddr *)&myipnum,
GET_SS_LEN(&myipnum), serv_connect_ssl_callback, client_p,
ConfigFileEntry.connect_timeout);
else
rb_connect_tcp(client_p->localClient->F, (struct sockaddr *)&server_p->my_ipnum,
(struct sockaddr *)&myipnum,
GET_SS_LEN(&myipnum), serv_connect_callback, client_p,
ConfigFileEntry.connect_timeout);
rb_connect_tcp(client_p->localClient->F,
(struct sockaddr *)&sa_connect,
GET_SS_FAMILY(&sa_bind) == AF_UNSPEC ? NULL : (struct sockaddr *)&sa_bind,
GET_SS_LEN(&sa_bind),
ServerConfSSL(server_p) ? serv_connect_ssl_callback : serv_connect_callback,
client_p, ConfigFileEntry.connect_timeout);
return 1;
}