1
0
mirror of https://github.com/irssi/irssi.git synced 2024-06-23 06:35:36 +00:00

Initial work to make irssi respect the resolved ip order

Ip's aren't selected using random() anymore, also select the ip version
by using getaddrinfo and some proper hints.
This commit is contained in:
LemonBoy 2015-09-21 13:54:13 +02:00
parent 0912a11050
commit ffaa890e99
17 changed files with 87 additions and 236 deletions

View File

@ -128,10 +128,10 @@ static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr,
host = g_hash_table_lookup(optlist, "host");
if (host != NULL && *host != '\0') {
IPADDR ip4, ip6;
IPADDR ip;
if (net_gethostbyname(host, &ip4, &ip6) == 0)
server_connect_own_ip_save(conn, &ip4, &ip6);
if (net_gethostbyname(host, &ip) == 0)
server_connect_own_ip_save(conn, &ip);
}
cmd_params_free(free_arg);

View File

@ -9,4 +9,4 @@ char *realname;
char *own_host; /* address to use when connecting this server */
char *autosendcmd; /* command to send after connecting to this ircnet */
IPADDR *own_ip4, *own_ip6; /* resolved own_address if not NULL */
IPADDR *own_ip; /* resolved own_address if not NULL */

View File

@ -103,15 +103,12 @@ int net_gethostbyname_nonblock(const char *addr, GIOChannel *pipe,
srand(time(NULL));
memset(&rec, 0, sizeof(rec));
rec.error = net_gethostbyname(addr, &rec.ip4, &rec.ip6);
rec.error = net_gethostbyname(addr, &rec.ip);
if (rec.error == 0) {
errorstr = NULL;
if (reverse_lookup) {
/* reverse lookup the IP, ignore any error */
if (rec.ip4.family != 0)
net_gethostbyaddr(&rec.ip4, &rec.host4);
if (rec.ip6.family != 0)
net_gethostbyaddr(&rec.ip6, &rec.host6);
net_gethostbyaddr(&rec.ip, &rec.host);
}
} else {
errorstr = net_gethosterror(rec.error);
@ -122,18 +119,11 @@ int net_gethostbyname_nonblock(const char *addr, GIOChannel *pipe,
if (rec.errlen != 0)
g_io_channel_write_block(pipe, (void *) errorstr, rec.errlen);
else {
if (rec.host4) {
len = strlen(rec.host4) + 1;
if (rec.host) {
len = strlen(rec.host) + 1;
g_io_channel_write_block(pipe, (void *) &len,
sizeof(int));
g_io_channel_write_block(pipe, (void *) rec.host4,
len);
}
if (rec.host6) {
len = strlen(rec.host6) + 1;
g_io_channel_write_block(pipe, (void *) &len,
sizeof(int));
g_io_channel_write_block(pipe, (void *) rec.host6,
g_io_channel_write_block(pipe, (void *) rec.host,
len);
}
}
@ -154,8 +144,7 @@ int net_gethostbyname_return(GIOChannel *pipe, RESOLVED_IP_REC *rec)
rec->error = -1;
rec->errorstr = NULL;
rec->host4 = NULL;
rec->host6 = NULL;
rec->host = NULL;
#ifndef WIN32
fcntl(g_io_channel_unix_get_fd(pipe), F_SETFL, O_NONBLOCK);
@ -174,15 +163,10 @@ int net_gethostbyname_return(GIOChannel *pipe, RESOLVED_IP_REC *rec)
rec->errorstr = g_malloc0(rec->errlen+1);
g_io_channel_read_block(pipe, rec->errorstr, rec->errlen);
} else {
if (rec->host4) {
if (rec->host) {
g_io_channel_read_block(pipe, &len, sizeof(int));
rec->host4 = g_malloc0(len);
g_io_channel_read_block(pipe, rec->host4, len);
}
if (rec->host6) {
g_io_channel_read_block(pipe, &len, sizeof(int));
rec->host6 = g_malloc0(len);
g_io_channel_read_block(pipe, rec->host6, len);
rec->host = g_malloc0(len);
g_io_channel_read_block(pipe, rec->host, len);
}
}
@ -227,7 +211,6 @@ static void simple_readpipe(SIMPLE_THREAD_REC *rec, GIOChannel *pipe)
{
RESOLVED_IP_REC iprec;
GIOChannel *handle;
IPADDR *ip;
g_return_if_fail(rec != NULL);
@ -241,9 +224,8 @@ static void simple_readpipe(SIMPLE_THREAD_REC *rec, GIOChannel *pipe)
g_io_channel_shutdown(rec->pipes[1], TRUE, NULL);
g_io_channel_unref(rec->pipes[1]);
ip = iprec.ip4.family != 0 ? &iprec.ip4 : &iprec.ip6;
handle = iprec.error == -1 ? NULL :
net_connect_ip(ip, rec->port, rec->my_ip);
net_connect_ip(&iprec.ip, rec->port, rec->my_ip);
g_free_not_null(rec->my_ip);

View File

@ -4,12 +4,12 @@
#include "network.h"
typedef struct {
IPADDR ip4, ip6; /* resolved ip addresses */
IPADDR ip; /* resolved ip address */
int error; /* error, 0 = no error, -1 = error: */
int errlen; /* error text length */
char *errorstr; /* error string - dynamically allocated, you'll
need to free() it yourself unless it's NULL */
char *host4, *host6; /* dito */
char *host; /* dito */
} RESOLVED_IP_REC;
typedef struct {

View File

@ -137,35 +137,14 @@ static int sin_get_port(union sockaddr_union *so)
/* Connect to socket */
GIOChannel *net_connect(const char *addr, int port, IPADDR *my_ip)
{
IPADDR ip4, ip6, *ip;
IPADDR ip;
g_return_val_if_fail(addr != NULL, NULL);
if (net_gethostbyname(addr, &ip4, &ip6) == -1)
if (net_gethostbyname(addr, &ip) == -1)
return NULL;
if (my_ip == NULL) {
/* prefer IPv4 addresses */
ip = ip4.family != 0 ? &ip4 : &ip6;
} else if (IPADDR_IS_V6(my_ip)) {
/* my_ip is IPv6 address, use it if possible */
if (ip6.family != 0)
ip = &ip6;
else {
my_ip = NULL;
ip = &ip4;
}
} else {
/* my_ip is IPv4 address, use it if possible */
if (ip4.family != 0)
ip = &ip4;
else {
my_ip = NULL;
ip = &ip6;
}
}
return net_connect_ip(ip, port, my_ip);
return net_connect_ip(&ip, port, my_ip);
}
/* Connect to socket with ip address */
@ -413,82 +392,35 @@ int net_getsockname(GIOChannel *handle, IPADDR *addr, int *port)
/* Get IP addresses for host, both IPv4 and IPv6 if possible.
If ip->family is 0, the address wasn't found.
Returns 0 = ok, others = error code for net_gethosterror() */
int net_gethostbyname(const char *addr, IPADDR *ip4, IPADDR *ip6)
int net_gethostbyname(const char *addr, IPADDR *ip)
{
#ifdef HAVE_IPV6
union sockaddr_union *so;
struct addrinfo hints, *ai, *ailist;
int ret, count_v4, count_v6, use_v4, use_v6;
#else
struct hostent *hp;
int count;
#endif
struct addrinfo hints, *ailist;
int ret;
g_return_val_if_fail(addr != NULL, -1);
memset(ip4, 0, sizeof(IPADDR));
memset(ip6, 0, sizeof(IPADDR));
memset(ip, 0, sizeof(IPADDR));
#ifdef HAVE_IPV6
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM;
#ifdef HAVE_IPV6
hints.ai_family = AF_UNSPEC;
#else
hints.ai_family = AF_INET;
#endif
hints.ai_flags = (AI_V4MAPPED | AI_ADDRCONFIG);
/* save error to host_error for later use */
ret = getaddrinfo(addr, NULL, &hints, &ailist);
if (ret != 0)
return ret;
/* count IPs */
count_v4 = count_v6 = 0;
for (ai = ailist; ai != NULL; ai = ai->ai_next) {
if (ai->ai_family == AF_INET)
count_v4++;
else if (ai->ai_family == AF_INET6)
count_v6++;
}
if (count_v4 == 0 && count_v6 == 0)
return HOST_NOT_FOUND; /* shouldn't happen? */
/* if there are multiple addresses, return random one */
use_v4 = count_v4 <= 1 ? 0 : rand() % count_v4;
use_v6 = count_v6 <= 1 ? 0 : rand() % count_v6;
count_v4 = count_v6 = 0;
for (ai = ailist; ai != NULL; ai = ai->ai_next) {
so = (union sockaddr_union *) ai->ai_addr;
if (ai->ai_family == AF_INET) {
if (use_v4 == count_v4)
sin_get_ip(so, ip4);
count_v4++;
} else if (ai->ai_family == AF_INET6) {
if (use_v6 == count_v6)
sin_get_ip(so, ip6);
count_v6++;
}
}
so = (const union sockaddr_union *)ailist->ai_addr;
sin_get_ip(so, ip);
freeaddrinfo(ailist);
return 0;
#else
hp = gethostbyname(addr);
if (hp == NULL)
return h_errno;
/* count IPs */
count = 0;
while (hp->h_addr_list[count] != NULL)
count++;
if (count == 0)
return HOST_NOT_FOUND; /* shouldn't happen? */
/* if there are multiple addresses, return random one */
ip4->family = AF_INET;
memcpy(&ip4->ip, hp->h_addr_list[rand() % count], 4);
return 0;
#endif
}
/* Get name for host, *name should be g_free()'d unless it's NULL.

View File

@ -71,7 +71,7 @@ int net_transmit(GIOChannel *handle, const char *data, int len);
/* Get IP addresses for host, both IPv4 and IPv6 if possible.
If ip->family is 0, the address wasn't found.
Returns 0 = ok, others = error code for net_gethosterror() */
int net_gethostbyname(const char *addr, IPADDR *ip4, IPADDR *ip6);
int net_gethostbyname(const char *addr, IPADDR *ip);
/* Get name for host, *name should be g_free()'d unless it's NULL.
Return values are the same as with net_gethostbyname() */
int net_gethostbyaddr(IPADDR *ip, char **name);

View File

@ -16,7 +16,7 @@ char *address;
int port;
char *chatnet;
IPADDR *own_ip4, *own_ip6;
IPADDR *own_ip;
char *password;
char *nick;

View File

@ -19,7 +19,7 @@ char *ssl_capath;
char *ssl_ciphers;
char *own_host; /* address to use when connecting this server */
IPADDR *own_ip4, *own_ip6; /* resolved own_address if not NULL */
IPADDR *own_ip; /* resolved own_address if not NULL */
time_t last_connect; /* to avoid reconnecting too fast.. */

View File

@ -177,13 +177,9 @@ server_connect_copy_skeleton(SERVER_CONNECT_REC *src, int connect_info)
dest->username = g_strdup(src->username);
dest->realname = g_strdup(src->realname);
if (src->own_ip4 != NULL) {
dest->own_ip4 = g_new(IPADDR, 1);
memcpy(dest->own_ip4, src->own_ip4, sizeof(IPADDR));
}
if (src->own_ip6 != NULL) {
dest->own_ip6 = g_new(IPADDR, 1);
memcpy(dest->own_ip6, src->own_ip6, sizeof(IPADDR));
if (src->own_ip != NULL) {
dest->own_ip = g_new(IPADDR, 1);
memcpy(dest->own_ip, src->own_ip, sizeof(IPADDR));
}
dest->channels = g_strdup(src->channels);

View File

@ -33,32 +33,22 @@ GSList *setupservers;
static char *old_source_host;
int source_host_ok; /* Use source_host_ip .. */
IPADDR *source_host_ip4, *source_host_ip6; /* Resolved address */
IPADDR source_host_ip; /* Resolved address */
static void save_ips(IPADDR *ip4, IPADDR *ip6,
IPADDR **save_ip4, IPADDR **save_ip6)
static void save_ips(IPADDR *ip, IPADDR **save_ip)
{
if (ip4->family == 0)
g_free_and_null(*save_ip4);
if (ip->family == 0)
g_free_and_null(*save_ip);
else {
if (*save_ip4 == NULL)
*save_ip4 = g_new(IPADDR, 1);
memcpy(*save_ip4, ip4, sizeof(IPADDR));
}
if (ip6->family == 0)
g_free_and_null(*save_ip6);
else {
if (*save_ip6 == NULL)
*save_ip6 = g_new(IPADDR, 1);
memcpy(*save_ip6, ip6, sizeof(IPADDR));
if (*save_ip == NULL)
*save_ip = g_new(IPADDR, 1);
memcpy(*save_ip, ip, sizeof(IPADDR));
}
}
static void get_source_host_ip(void)
{
const char *hostname;
IPADDR ip4, ip6;
if (source_host_ok)
return;
@ -66,28 +56,21 @@ static void get_source_host_ip(void)
/* FIXME: This will block! */
hostname = settings_get_str("hostname");
source_host_ok = *hostname != '\0' &&
net_gethostbyname(hostname, &ip4, &ip6) == 0;
if (source_host_ok)
save_ips(&ip4, &ip6, &source_host_ip4, &source_host_ip6);
else {
g_free_and_null(source_host_ip4);
g_free_and_null(source_host_ip6);
}
net_gethostbyname(hostname, &source_host_ip) == 0;
}
static void conn_set_ip(SERVER_CONNECT_REC *conn, const char *own_host,
IPADDR **own_ip4, IPADDR **own_ip6)
IPADDR **own_ip)
{
IPADDR ip4, ip6;
IPADDR ip;
if (*own_ip4 == NULL && *own_ip6 == NULL) {
if (*own_ip == NULL) {
/* resolve the IP */
if (net_gethostbyname(own_host, &ip4, &ip6) == 0)
save_ips(&ip4, &ip6, own_ip4, own_ip6);
if (net_gethostbyname(own_host, &ip) == 0)
save_ips(&ip, own_ip);
}
server_connect_own_ip_save(conn, *own_ip4, *own_ip6);
server_connect_own_ip_save(conn, *own_ip);
}
/* Fill information to connection from server setup record */
@ -98,8 +81,7 @@ void server_setup_fill_reconn(SERVER_CONNECT_REC *conn,
g_return_if_fail(IS_SERVER_SETUP(sserver));
if (sserver->own_host != NULL) {
conn_set_ip(conn, sserver->own_host,
&sserver->own_ip4, &sserver->own_ip6);
conn_set_ip(conn, sserver->own_host, &sserver->own_ip);
}
if (sserver->chatnet != NULL && conn->chatnet == NULL)
@ -139,13 +121,9 @@ static void server_setup_fill(SERVER_CONNECT_REC *conn,
}
/* source IP */
if (source_host_ip4 != NULL) {
conn->own_ip4 = g_new(IPADDR, 1);
memcpy(conn->own_ip4, source_host_ip4, sizeof(IPADDR));
}
if (source_host_ip6 != NULL) {
conn->own_ip6 = g_new(IPADDR, 1);
memcpy(conn->own_ip6, source_host_ip6, sizeof(IPADDR));
if (source_host_ok) {
conn->own_ip = g_new(IPADDR, 1);
memcpy(conn->own_ip, &source_host_ip, sizeof(IPADDR));
}
signal_emit("server setup fill connect", 1, conn);
@ -206,8 +184,7 @@ static void server_setup_fill_chatnet(SERVER_CONNECT_REC *conn,
conn->realname = g_strdup(chatnet->realname);;
}
if (chatnet->own_host != NULL) {
conn_set_ip(conn, chatnet->own_host,
&chatnet->own_ip4, &chatnet->own_ip6);
conn_set_ip(conn, chatnet->own_host, &chatnet->own_ip);
}
signal_emit("server setup fill chatnet", 2, conn, chatnet);
@ -481,8 +458,7 @@ static void server_setup_destroy(SERVER_SETUP_REC *rec)
signal_emit("server setup destroyed", 1, rec);
g_free_not_null(rec->own_host);
g_free_not_null(rec->own_ip4);
g_free_not_null(rec->own_ip6);
g_free_not_null(rec->own_ip);
g_free_not_null(rec->chatnet);
g_free_not_null(rec->password);
g_free_not_null(rec->ssl_cert);
@ -556,7 +532,6 @@ void servers_setup_init(void)
settings_add_str("proxy", "proxy_password", "");
setupservers = NULL;
source_host_ip4 = source_host_ip6 = NULL;
old_source_host = NULL;
read_settings();
@ -567,8 +542,6 @@ void servers_setup_init(void)
void servers_setup_deinit(void)
{
g_free_not_null(source_host_ip4);
g_free_not_null(source_host_ip6);
g_free_not_null(old_source_host);
while (setupservers != NULL)

View File

@ -16,7 +16,7 @@ struct _SERVER_SETUP_REC {
extern GSList *setupservers;
extern IPADDR *source_host_ip4, *source_host_ip6; /* Resolved address */
extern IPADDR source_host_ip; /* Resolved address */
extern int source_host_ok; /* Use source_host_ip .. */
/* Fill reconnection specific information to connection

View File

@ -218,7 +218,7 @@ static void server_real_connect(SERVER_REC *server, IPADDR *ip,
return;
if (ip != NULL) {
own_ip = IPADDR_IS_V6(ip) ? server->connrec->own_ip6 : server->connrec->own_ip4;
own_ip = server->connrec->own_ip;
port = server->connrec->proxy != NULL ?
server->connrec->proxy_port : server->connrec->port;
handle = server->connrec->use_ssl ?
@ -280,30 +280,15 @@ static void server_connect_callback_readpipe(SERVER_REC *server)
server->connect_pipe[0] = NULL;
server->connect_pipe[1] = NULL;
/* figure out if we should use IPv4 or v6 address */
if (iprec.error != 0) {
/* error */
ip = NULL;
} else if (server->connrec->family == AF_INET) {
/* force IPv4 connection */
ip = iprec.ip4.family == 0 ? NULL : &iprec.ip4;
servername = iprec.host4;
} else if (server->connrec->family == AF_INET6) {
/* force IPv6 connection */
ip = iprec.ip6.family == 0 ? NULL : &iprec.ip6;
servername = iprec.host6;
} else {
/* pick the one that was found, or if both do it like
/SET resolve_prefer_ipv6 says. */
if (iprec.ip4.family == 0 ||
(iprec.ip6.family != 0 &&
settings_get_bool("resolve_prefer_ipv6"))) {
ip = &iprec.ip6;
servername = iprec.host6;
} else {
ip = &iprec.ip4;
servername = iprec.host4;
}
ip = NULL;
if (iprec.error == 0) {
// FIXME : REMOVE THIS BEFORE MERGE
if (server->connrec->family)
g_assert(server->connrec->family == iprec.ip.family);
ip = &iprec.ip;
servername = iprec.host;
}
if (ip != NULL) {
@ -337,8 +322,7 @@ static void server_connect_callback_readpipe(SERVER_REC *server)
}
g_free(iprec.errorstr);
g_free(iprec.host4);
g_free(iprec.host6);
g_free(iprec.host);
}
SERVER_REC *server_connect(SERVER_CONNECT_REC *conn)
@ -623,8 +607,7 @@ void server_connect_unref(SERVER_CONNECT_REC *conn)
g_free_not_null(conn->address);
g_free_not_null(conn->chatnet);
g_free_not_null(conn->own_ip4);
g_free_not_null(conn->own_ip6);
g_free_not_null(conn->own_ip);
g_free_not_null(conn->password);
g_free_not_null(conn->nick);
@ -654,26 +637,15 @@ void server_change_nick(SERVER_REC *server, const char *nick)
}
/* Update own IPv4 and IPv6 records */
void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
IPADDR *ip4, IPADDR *ip6)
void server_connect_own_ip_save(SERVER_CONNECT_REC *conn, IPADDR *ip)
{
if (ip4 == NULL || ip4->family == 0)
g_free_and_null(conn->own_ip4);
if (ip6 == NULL || ip6->family == 0)
g_free_and_null(conn->own_ip6);
if (ip == NULL || ip->family == 0)
g_free_and_null(conn->own_ip);
if (ip4 != NULL && ip4->family != 0) {
/* IPv4 address was found */
if (conn->own_ip4 == NULL)
conn->own_ip4 = g_new0(IPADDR, 1);
memcpy(conn->own_ip4, ip4, sizeof(IPADDR));
}
if (ip6 != NULL && ip6->family != 0) {
/* IPv6 address was found */
if (conn->own_ip6 == NULL)
conn->own_ip6 = g_new0(IPADDR, 1);
memcpy(conn->own_ip6, ip6, sizeof(IPADDR));
if (ip != NULL && ip->family != 0) {
if (conn->own_ip == NULL)
conn->own_ip = g_new0(IPADDR, 1);
memcpy(conn->own_ip, ip, sizeof(IPADDR));
}
}

View File

@ -67,8 +67,7 @@ void server_connect_failed(SERVER_REC *server, const char *msg);
void server_change_nick(SERVER_REC *server, const char *nick);
/* Update own IPv4 and IPv6 records */
void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
IPADDR *ip4, IPADDR *ip6);
void server_connect_own_ip_save(SERVER_CONNECT_REC *conn, IPADDR *ip);
/* `optlist' should contain only one unknown key - the server tag.
returns NULL if there was unknown -option */

View File

@ -138,7 +138,7 @@ static void cmd_server_add(const char *data)
if (*password != '\0') g_free_and_null(rec->password);
if (g_hash_table_lookup(optlist, "host")) {
g_free_and_null(rec->own_host);
rec->own_ip4 = rec->own_ip6 = NULL;
rec->own_ip = NULL;
}
}
@ -193,7 +193,7 @@ static void cmd_server_add(const char *data)
value = g_hash_table_lookup(optlist, "host");
if (value != NULL && *value != '\0') {
rec->own_host = g_strdup(value);
rec->own_ip4 = rec->own_ip6 = NULL;
rec->own_ip = NULL;
}
signal_emit("server add fill", 2, rec, optlist);

View File

@ -108,7 +108,7 @@ static void cmd_network_add(const char *data)
if (g_hash_table_lookup(optlist, "realname")) g_free_and_null(rec->realname);
if (g_hash_table_lookup(optlist, "host")) {
g_free_and_null(rec->own_host);
rec->own_ip4 = rec->own_ip6 = NULL;
rec->own_ip = NULL;
}
if (g_hash_table_lookup(optlist, "usermode")) g_free_and_null(rec->usermode);
if (g_hash_table_lookup(optlist, "autosendcmd")) g_free_and_null(rec->autosendcmd);
@ -140,7 +140,7 @@ static void cmd_network_add(const char *data)
value = g_hash_table_lookup(optlist, "host");
if (value != NULL && *value != '\0') {
rec->own_host = g_strdup(value);
rec->own_ip4 = rec->own_ip6 = NULL;
rec->own_ip = NULL;
}
value = g_hash_table_lookup(optlist, "usermode");

View File

@ -264,12 +264,12 @@ GIOChannel *dcc_connect_ip(IPADDR *ip, int port)
}
if (own_ip == NULL)
own_ip = IPADDR_IS_V6(ip) ? source_host_ip6 : source_host_ip4;
own_ip = &source_host_ip;
handle = net_connect_ip(ip, port, own_ip);
if (handle == NULL && errno == EADDRNOTAVAIL && own_ip != NULL) {
/* dcc_own_ip is external address */
own_ip = IPADDR_IS_V6(ip) ? source_host_ip6 : source_host_ip4;
own_ip = &source_host_ip;
handle = net_connect_ip(ip, port, own_ip);
}
return handle;

View File

@ -585,7 +585,7 @@ static LISTEN_REC *find_listen(const char *ircnet, int port)
static void add_listen(const char *ircnet, int port)
{
LISTEN_REC *rec;
IPADDR ip4, ip6, *my_ip;
IPADDR ip, *my_ip;
if (port <= 0 || *ircnet == '\0')
return;
@ -593,16 +593,13 @@ static void add_listen(const char *ircnet, int port)
/* bind to specific host/ip? */
my_ip = NULL;
if (*settings_get_str("irssiproxy_bind") != '\0') {
if (net_gethostbyname(settings_get_str("irssiproxy_bind"),
&ip4, &ip6) != 0) {
if (net_gethostbyname(settings_get_str("irssiproxy_bind"), &ip) != 0) {
printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
"Proxy: can not resolve '%s' - aborting",
settings_get_str("irssiproxy_bind"));
return;
}
my_ip = ip6.family == 0 ? &ip4 : ip4.family == 0 ||
settings_get_bool("resolve_prefer_ipv6") ? &ip6 : &ip4;
my_ip = &ip;
}
rec = g_new0(LISTEN_REC, 1);