Commit bff7ecc7 authored by zhenwei pi's avatar zhenwei pi
Browse files

Introduce connAddr



Originally, connPeerToString is designed to get the address info from
socket only(for both TCP & TLS), and the API 'connPeerToString' is
oriented to operate a FD like:
int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port) {
    return anetFdToString(conn ? conn->fd : -1, ip, ip_len, port, FD_TO_PEER_NAME);
}

Introduce connAddr and implement .addr method for socket and TLS,
thus the API 'connAddr' and 'connFormatAddr' become oriented to a
connection like:
static inline int connAddr(connection *conn, char *ip, size_t ip_len, int *port, int remote) {
    if (conn && conn->type->addr) {
        return conn->type->addr(conn, ip, ip_len, port, remote);
    }

    return -1;
}

Also remove 'FD_TO_PEER_NAME' & 'FD_TO_SOCK_NAME', use a boolean type
'remote' to get local/remote address of a connection.

With these changes, it's possible to support the other connection
types which does not use socket(Ex, RDMA).

Thanks to Oran for suggestions!
Signed-off-by: default avatarzhenwei pi <pizhenwei@bytedance.com>
parent b9d77288
...@@ -579,11 +579,11 @@ int anetUnixAccept(char *err, int s) { ...@@ -579,11 +579,11 @@ int anetUnixAccept(char *err, int s) {
return fd; return fd;
} }
int anetFdToString(int fd, char *ip, size_t ip_len, int *port, int fd_to_str_type) { int anetFdToString(int fd, char *ip, size_t ip_len, int *port, int remote) {
struct sockaddr_storage sa; struct sockaddr_storage sa;
socklen_t salen = sizeof(sa); socklen_t salen = sizeof(sa);
if (fd_to_str_type == FD_TO_PEER_NAME) { if (remote) {
if (getpeername(fd, (struct sockaddr *)&sa, &salen) == -1) goto error; if (getpeername(fd, (struct sockaddr *)&sa, &salen) == -1) goto error;
} else { } else {
if (getsockname(fd, (struct sockaddr *)&sa, &salen) == -1) goto error; if (getsockname(fd, (struct sockaddr *)&sa, &salen) == -1) goto error;
...@@ -627,23 +627,6 @@ error: ...@@ -627,23 +627,6 @@ error:
return -1; return -1;
} }
/* Format an IP,port pair into something easy to parse. If IP is IPv6
* (matches for ":"), the ip is surrounded by []. IP and port are just
* separated by colons. This the standard to display addresses within Redis. */
int anetFormatAddr(char *buf, size_t buf_len, char *ip, int port) {
return snprintf(buf,buf_len, strchr(ip,':') ?
"[%s]:%d" : "%s:%d", ip, port);
}
/* Like anetFormatAddr() but extract ip and port from the socket's peer/sockname. */
int anetFormatFdAddr(int fd, char *buf, size_t buf_len, int fd_to_str_type) {
char ip[INET6_ADDRSTRLEN];
int port;
anetFdToString(fd,ip,sizeof(ip),&port,fd_to_str_type);
return anetFormatAddr(buf, buf_len, ip, port);
}
/* Create a pipe buffer with given flags for read end and write end. /* Create a pipe buffer with given flags for read end and write end.
* Note that it supports the file flags defined by pipe2() and fcntl(F_SETFL), * Note that it supports the file flags defined by pipe2() and fcntl(F_SETFL),
* and one of the use cases is O_CLOEXEC|O_NONBLOCK. */ * and one of the use cases is O_CLOEXEC|O_NONBLOCK. */
......
...@@ -49,10 +49,6 @@ ...@@ -49,10 +49,6 @@
#undef ip_len #undef ip_len
#endif #endif
/* FD to address string conversion types */
#define FD_TO_PEER_NAME 0
#define FD_TO_SOCK_NAME 1
int anetTcpNonBlockConnect(char *err, const char *addr, int port); int anetTcpNonBlockConnect(char *err, const char *addr, int port);
int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, const char *source_addr); int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, const char *source_addr);
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, int flags); int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, int flags);
...@@ -68,10 +64,9 @@ int anetEnableTcpNoDelay(char *err, int fd); ...@@ -68,10 +64,9 @@ int anetEnableTcpNoDelay(char *err, int fd);
int anetDisableTcpNoDelay(char *err, int fd); int anetDisableTcpNoDelay(char *err, int fd);
int anetSendTimeout(char *err, int fd, long long ms); int anetSendTimeout(char *err, int fd, long long ms);
int anetRecvTimeout(char *err, int fd, long long ms); int anetRecvTimeout(char *err, int fd, long long ms);
int anetFdToString(int fd, char *ip, size_t ip_len, int *port, int fd_to_str_type); int anetFdToString(int fd, char *ip, size_t ip_len, int *port, int remote);
int anetKeepAlive(char *err, int fd, int interval); int anetKeepAlive(char *err, int fd, int interval);
int anetFormatAddr(char *fmt, size_t fmt_len, char *ip, int port); int anetFormatAddr(char *fmt, size_t fmt_len, char *ip, int port);
int anetFormatFdAddr(int fd, char *buf, size_t buf_len, int fd_to_str_type);
int anetPipe(int fds[2], int read_flags, int write_flags); int anetPipe(int fds[2], int read_flags, int write_flags);
int anetSetSockMarkId(char *err, int fd, uint32_t id); int anetSetSockMarkId(char *err, int fd, uint32_t id);
int anetGetError(int fd); int anetGetError(int fd);
......
...@@ -1769,7 +1769,7 @@ int nodeIp2String(char *buf, clusterLink *link, char *announced_ip) { ...@@ -1769,7 +1769,7 @@ int nodeIp2String(char *buf, clusterLink *link, char *announced_ip) {
buf[NET_IP_STR_LEN-1] = '\0'; /* We are not sure the input is sane. */ buf[NET_IP_STR_LEN-1] = '\0'; /* We are not sure the input is sane. */
return C_OK; return C_OK;
} else { } else {
if (connPeerToString(link->conn, buf, NET_IP_STR_LEN, NULL) == C_ERR) { if (connAddrPeerName(link->conn, buf, NET_IP_STR_LEN, NULL) == C_ERR) {
serverLog(LL_NOTICE, "Error converting peer IP to string: %s", serverLog(LL_NOTICE, "Error converting peer IP to string: %s",
link->conn ? connGetLastError(link->conn) : "no link"); link->conn ? connGetLastError(link->conn) : "no link");
return C_ERR; return C_ERR;
...@@ -2273,7 +2273,7 @@ int clusterProcessPacket(clusterLink *link) { ...@@ -2273,7 +2273,7 @@ int clusterProcessPacket(clusterLink *link) {
{ {
char ip[NET_IP_STR_LEN]; char ip[NET_IP_STR_LEN];
if (connSockName(link->conn,ip,sizeof(ip),NULL) != -1 && if (connAddrSockName(link->conn,ip,sizeof(ip),NULL) != -1 &&
strcmp(ip,myself->ip)) strcmp(ip,myself->ip))
{ {
memcpy(myself->ip,ip,NET_IP_STR_LEN); memcpy(myself->ip,ip,NET_IP_STR_LEN);
......
...@@ -32,9 +32,12 @@ ...@@ -32,9 +32,12 @@
#define __REDIS_CONNECTION_H #define __REDIS_CONNECTION_H
#include <errno.h> #include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/uio.h> #include <sys/uio.h>
#define CONN_INFO_LEN 32 #define CONN_INFO_LEN 32
#define CONN_ADDR_STR_LEN 128 /* Similar to INET6_ADDRSTRLEN, hoping to handle other protocols. */
struct aeEventLoop; struct aeEventLoop;
typedef struct connection connection; typedef struct connection connection;
...@@ -62,6 +65,7 @@ typedef struct ConnectionType { ...@@ -62,6 +65,7 @@ typedef struct ConnectionType {
/* ae & accept & listen & error & address handler */ /* ae & accept & listen & error & address handler */
void (*ae_handler)(struct aeEventLoop *el, int fd, void *clientData, int mask); void (*ae_handler)(struct aeEventLoop *el, int fd, void *clientData, int mask);
int (*addr)(connection *conn, char *ip, size_t ip_len, int *port, int remote);
/* create/close connection */ /* create/close connection */
void (*close)(struct connection *conn); void (*close)(struct connection *conn);
...@@ -233,6 +237,44 @@ static inline int connLastErrorRetryable(connection *conn) { ...@@ -233,6 +237,44 @@ static inline int connLastErrorRetryable(connection *conn) {
return conn->last_errno == EINTR; return conn->last_errno == EINTR;
} }
/* Get address information of a connection.
* @remote works as boolean type to get local/remote address */
static inline int connAddr(connection *conn, char *ip, size_t ip_len, int *port, int remote) {
if (conn && conn->type->addr) {
return conn->type->addr(conn, ip, ip_len, port, remote);
}
return -1;
}
/* Format an IP,port pair into something easy to parse. If IP is IPv6
* (matches for ":"), the ip is surrounded by []. IP and port are just
* separated by colons. This the standard to display addresses within Redis. */
static inline int formatAddr(char *buf, size_t buf_len, char *ip, int port) {
return snprintf(buf,buf_len, strchr(ip,':') ?
"[%s]:%d" : "%s:%d", ip, port);
}
static inline int connFormatAddr(connection *conn, char *buf, size_t buf_len, int remote)
{
char ip[CONN_ADDR_STR_LEN];
int port;
if (connAddr(conn, ip, sizeof(ip), &port, remote) < 0) {
return -1;
}
return formatAddr(buf, buf_len, ip, port);
}
static inline int connAddrPeerName(connection *conn, char *ip, size_t ip_len, int *port) {
return connAddr(conn, ip, ip_len, port, 1);
}
static inline int connAddrSockName(connection *conn, char *ip, size_t ip_len, int *port) {
return connAddr(conn, ip, ip_len, port, 0);
}
connection *connCreateSocket(); connection *connCreateSocket();
connection *connCreateAcceptedSocket(int fd); connection *connCreateAcceptedSocket(int fd);
...@@ -281,9 +323,6 @@ int connDisableTcpNoDelay(connection *conn); ...@@ -281,9 +323,6 @@ int connDisableTcpNoDelay(connection *conn);
int connKeepAlive(connection *conn, int interval); int connKeepAlive(connection *conn, int interval);
int connSendTimeout(connection *conn, long long ms); int connSendTimeout(connection *conn, long long ms);
int connRecvTimeout(connection *conn, long long ms); int connRecvTimeout(connection *conn, long long ms);
int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port);
int connFormatFdAddr(connection *conn, char *buf, size_t buf_len, int fd_to_str_type);
int connSockName(connection *conn, char *ip, size_t ip_len, int *port);
/* Helpers for tls special considerations */ /* Helpers for tls special considerations */
sds connTLSGetPeerCert(connection *conn); sds connTLSGetPeerCert(connection *conn);
......
...@@ -3331,7 +3331,7 @@ int modulePopulateClientInfoStructure(void *ci, client *client, int structver) { ...@@ -3331,7 +3331,7 @@ int modulePopulateClientInfoStructure(void *ci, client *client, int structver) {
ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_SSL; ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_SSL;
   
int port; int port;
connPeerToString(client->conn,ci1->addr,sizeof(ci1->addr),&port); connAddrPeerName(client->conn,ci1->addr,sizeof(ci1->addr),&port);
ci1->port = port; ci1->port = port;
ci1->db = client->db->id; ci1->db = client->db->id;
ci1->id = client->id; ci1->id = client->id;
......
...@@ -1196,7 +1196,7 @@ int islocalClient(client *c) { ...@@ -1196,7 +1196,7 @@ int islocalClient(client *c) {
/* tcp */ /* tcp */
char cip[NET_IP_STR_LEN+1] = { 0 }; char cip[NET_IP_STR_LEN+1] = { 0 };
connPeerToString(c->conn, cip, sizeof(cip)-1, NULL); connAddrPeerName(c->conn, cip, sizeof(cip)-1, NULL);
return !strcmp(cip,"127.0.0.1") || !strcmp(cip,"::1"); return !strcmp(cip,"127.0.0.1") || !strcmp(cip,"::1");
} }
...@@ -2700,13 +2700,13 @@ done: ...@@ -2700,13 +2700,13 @@ done:
* you want to relax error checking or need to display something anyway (see * you want to relax error checking or need to display something anyway (see
* anetFdToString implementation for more info). */ * anetFdToString implementation for more info). */
void genClientAddrString(client *client, char *addr, void genClientAddrString(client *client, char *addr,
size_t addr_len, int fd_to_str_type) { size_t addr_len, int remote) {
if (client->flags & CLIENT_UNIX_SOCKET) { if (client->flags & CLIENT_UNIX_SOCKET) {
/* Unix socket client. */ /* Unix socket client. */
snprintf(addr,addr_len,"%s:0",server.unixsocket); snprintf(addr,addr_len,"%s:0",server.unixsocket);
} else { } else {
/* TCP client. */ /* TCP client. */
connFormatFdAddr(client->conn,addr,addr_len,fd_to_str_type); connFormatAddr(client->conn,addr,addr_len,remote);
} }
} }
...@@ -2715,10 +2715,10 @@ void genClientAddrString(client *client, char *addr, ...@@ -2715,10 +2715,10 @@ void genClientAddrString(client *client, char *addr,
* The Peer ID never changes during the life of the client, however it * The Peer ID never changes during the life of the client, however it
* is expensive to compute. */ * is expensive to compute. */
char *getClientPeerId(client *c) { char *getClientPeerId(client *c) {
char peerid[NET_ADDR_STR_LEN]; char peerid[NET_ADDR_STR_LEN] = {0};
if (c->peerid == NULL) { if (c->peerid == NULL) {
genClientAddrString(c,peerid,sizeof(peerid),FD_TO_PEER_NAME); genClientAddrString(c,peerid,sizeof(peerid),1);
c->peerid = sdsnew(peerid); c->peerid = sdsnew(peerid);
} }
return c->peerid; return c->peerid;
...@@ -2729,10 +2729,10 @@ char *getClientPeerId(client *c) { ...@@ -2729,10 +2729,10 @@ char *getClientPeerId(client *c) {
* The Socket Name never changes during the life of the client, however it * The Socket Name never changes during the life of the client, however it
* is expensive to compute. */ * is expensive to compute. */
char *getClientSockname(client *c) { char *getClientSockname(client *c) {
char sockname[NET_ADDR_STR_LEN]; char sockname[NET_ADDR_STR_LEN] = {0};
if (c->sockname == NULL) { if (c->sockname == NULL) {
genClientAddrString(c,sockname,sizeof(sockname),FD_TO_SOCK_NAME); genClientAddrString(c,sockname,sizeof(sockname),0);
c->sockname = sdsnew(sockname); c->sockname = sdsnew(sockname);
} }
return c->sockname; return c->sockname;
......
...@@ -61,6 +61,7 @@ ...@@ -61,6 +61,7 @@
#include "help.h" /* Used for backwards-compatibility with pre-7.0 servers that don't support COMMAND DOCS. */ #include "help.h" /* Used for backwards-compatibility with pre-7.0 servers that don't support COMMAND DOCS. */
#include "anet.h" #include "anet.h"
#include "ae.h" #include "ae.h"
#include "connection.h"
#include "cli_common.h" #include "cli_common.h"
#include "mt19937-64.h" #include "mt19937-64.h"
...@@ -322,7 +323,7 @@ static void cliRefreshPrompt(void) { ...@@ -322,7 +323,7 @@ static void cliRefreshPrompt(void) {
prompt = sdscatfmt(prompt,"redis %s",config.hostsocket); prompt = sdscatfmt(prompt,"redis %s",config.hostsocket);
} else { } else {
char addr[256]; char addr[256];
anetFormatAddr(addr, sizeof(addr), config.conn_info.hostip, config.conn_info.hostport); formatAddr(addr, sizeof(addr), config.conn_info.hostip, config.conn_info.hostport);
prompt = sdscatlen(prompt,addr,strlen(addr)); prompt = sdscatlen(prompt,addr,strlen(addr));
} }
......
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
#include "cluster.h" #include "cluster.h"
#include "bio.h" #include "bio.h"
#include "functions.h" #include "functions.h"
#include "connection.h"
#include <memory.h> #include <memory.h>
#include <sys/time.h> #include <sys/time.h>
...@@ -66,11 +67,11 @@ char *replicationGetSlaveName(client *c) { ...@@ -66,11 +67,11 @@ char *replicationGetSlaveName(client *c) {
ip[0] = '\0'; ip[0] = '\0';
buf[0] = '\0'; buf[0] = '\0';
if (c->slave_addr || if (c->slave_addr ||
connPeerToString(c->conn,ip,sizeof(ip),NULL) != -1) connAddrPeerName(c->conn,ip,sizeof(ip),NULL) != -1)
{ {
char *addr = c->slave_addr ? c->slave_addr : ip; char *addr = c->slave_addr ? c->slave_addr : ip;
if (c->slave_listening_port) if (c->slave_listening_port)
anetFormatAddr(buf,sizeof(buf),addr,c->slave_listening_port); formatAddr(buf,sizeof(buf),addr,c->slave_listening_port);
else else
snprintf(buf,sizeof(buf),"%s:<unknown-replica-port>",addr); snprintf(buf,sizeof(buf),"%s:<unknown-replica-port>",addr);
} else { } else {
...@@ -3156,7 +3157,7 @@ void roleCommand(client *c) { ...@@ -3156,7 +3157,7 @@ void roleCommand(client *c) {
char ip[NET_IP_STR_LEN], *slaveaddr = slave->slave_addr; char ip[NET_IP_STR_LEN], *slaveaddr = slave->slave_addr;
if (!slaveaddr) { if (!slaveaddr) {
if (connPeerToString(slave->conn,ip,sizeof(ip),NULL) == -1) if (connAddrPeerName(slave->conn,ip,sizeof(ip),NULL) == -1)
continue; continue;
slaveaddr = ip; slaveaddr = ip;
} }
...@@ -3817,7 +3818,7 @@ static client *findReplica(char *host, int port) { ...@@ -3817,7 +3818,7 @@ static client *findReplica(char *host, int port) {
char ip[NET_IP_STR_LEN], *replicaip = replica->slave_addr; char ip[NET_IP_STR_LEN], *replicaip = replica->slave_addr;
if (!replicaip) { if (!replicaip) {
if (connPeerToString(replica->conn, ip, sizeof(ip), NULL) == -1) if (connAddrPeerName(replica->conn, ip, sizeof(ip), NULL) == -1)
continue; continue;
replicaip = ip; replicaip = ip;
} }
...@@ -4048,7 +4049,7 @@ void updateFailoverStatus(void) { ...@@ -4048,7 +4049,7 @@ void updateFailoverStatus(void) {
char ip[NET_IP_STR_LEN], *replicaaddr = replica->slave_addr; char ip[NET_IP_STR_LEN], *replicaaddr = replica->slave_addr;
if (!replicaaddr) { if (!replicaaddr) {
if (connPeerToString(replica->conn,ip,sizeof(ip),NULL) == -1) if (connAddrPeerName(replica->conn,ip,sizeof(ip),NULL) == -1)
continue; continue;
replicaaddr = ip; replicaaddr = ip;
} }
......
...@@ -3027,7 +3027,7 @@ int sentinelSendHello(sentinelRedisInstance *ri) { ...@@ -3027,7 +3027,7 @@ int sentinelSendHello(sentinelRedisInstance *ri) {
if (sentinel.announce_ip) { if (sentinel.announce_ip) {
announce_ip = sentinel.announce_ip; announce_ip = sentinel.announce_ip;
} else { } else {
if (anetFdToString(ri->link->cc->c.fd,ip,sizeof(ip),NULL,FD_TO_SOCK_NAME) == -1) if (anetFdToString(ri->link->cc->c.fd,ip,sizeof(ip),NULL,0) == -1)
return C_ERR; return C_ERR;
announce_ip = ip; announce_ip = ip;
} }
......
...@@ -5934,7 +5934,7 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) { ...@@ -5934,7 +5934,7 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
long lag = 0; long lag = 0;
if (!slaveip) { if (!slaveip) {
if (connPeerToString(slave->conn,ip,sizeof(ip),&port) == -1) if (connAddrPeerName(slave->conn,ip,sizeof(ip),&port) == -1)
continue; continue;
slaveip = ip; slaveip = ip;
} }
......
...@@ -300,6 +300,14 @@ static void connSocketEventHandler(struct aeEventLoop *el, int fd, void *clientD ...@@ -300,6 +300,14 @@ static void connSocketEventHandler(struct aeEventLoop *el, int fd, void *clientD
} }
} }
static int connSocketAddr(connection *conn, char *ip, size_t ip_len, int *port, int remote) {
if (anetFdToString(conn->fd, ip, ip_len, port, remote) == 0)
return C_OK;
conn->last_errno = errno;
return C_ERR;
}
static int connSocketBlockingConnect(connection *conn, const char *addr, int port, long long timeout) { static int connSocketBlockingConnect(connection *conn, const char *addr, int port, long long timeout) {
int fd = anetTcpNonBlockConnect(NULL,addr,port); int fd = anetTcpNonBlockConnect(NULL,addr,port);
if (fd == -1) { if (fd == -1) {
...@@ -346,6 +354,7 @@ ConnectionType CT_Socket = { ...@@ -346,6 +354,7 @@ ConnectionType CT_Socket = {
/* ae & accept & listen & error & address handler */ /* ae & accept & listen & error & address handler */
.ae_handler = connSocketEventHandler, .ae_handler = connSocketEventHandler,
.addr = connSocketAddr,
/* create/close connection */ /* create/close connection */
.close = connSocketClose, .close = connSocketClose,
...@@ -367,22 +376,6 @@ ConnectionType CT_Socket = { ...@@ -367,22 +376,6 @@ ConnectionType CT_Socket = {
.sync_readline = connSocketSyncReadLine, .sync_readline = connSocketSyncReadLine,
}; };
int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port) {
if (anetFdToString(conn ? conn->fd : -1, ip, ip_len, port, FD_TO_PEER_NAME) == -1) {
if (conn) conn->last_errno = errno;
return C_ERR;
}
return C_OK;
}
int connSockName(connection *conn, char *ip, size_t ip_len, int *port) {
return anetFdToString(conn->fd, ip, ip_len, port, FD_TO_SOCK_NAME);
}
int connFormatFdAddr(connection *conn, char *buf, size_t buf_len, int fd_to_str_type) {
return anetFormatFdAddr(conn ? conn->fd : -1, buf, buf_len, fd_to_str_type);
}
int connBlock(connection *conn) { int connBlock(connection *conn) {
if (conn->fd == -1) return C_ERR; if (conn->fd == -1) return C_ERR;
return anetBlock(NULL, conn->fd); return anetBlock(NULL, conn->fd);
......
...@@ -718,6 +718,10 @@ static void tlsEventHandler(struct aeEventLoop *el, int fd, void *clientData, in ...@@ -718,6 +718,10 @@ static void tlsEventHandler(struct aeEventLoop *el, int fd, void *clientData, in
tlsHandleEvent(conn, mask); tlsHandleEvent(conn, mask);
} }
static int connTLSAddr(connection *conn, char *ip, size_t ip_len, int *port, int remote) {
return anetFdToString(conn->fd, ip, ip_len, port, remote);
}
static void connTLSClose(connection *conn_) { static void connTLSClose(connection *conn_) {
tls_connection *conn = (tls_connection *) conn_; tls_connection *conn = (tls_connection *) conn_;
...@@ -1064,6 +1068,7 @@ ConnectionType CT_TLS = { ...@@ -1064,6 +1068,7 @@ ConnectionType CT_TLS = {
/* ae & accept & listen & error & address handler */ /* ae & accept & listen & error & address handler */
.ae_handler = tlsEventHandler, .ae_handler = tlsEventHandler,
.addr = connTLSAddr,
/* create/close connection */ /* create/close connection */
.close = connTLSClose, .close = connTLSClose,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment