Hello Arthur,
sh> psql "host=127.0.0.2 hostaddr=127.0.0.1"
I'm not sure that is is the issue. User defined the host name and psql
show it.
The issue is that "host" is an ip, "\conninfo" will inform wrongly that you
are connected to "127.0.0.2", but the actual connection is really to
"127.0.0.1", this is plain misleading, and I consider this level of
unhelpfullness more a bug than a feature.
I didn't think that this is an issue, because I determined "host" as just a
host's display name when "hostaddr" is defined.
When I type "\conninfo", I do not expect to have false clues that must be
interpreted depending on a fine knowledge of the documentation and the
connection parameters possibly typed hours earlier, I would just expect to
have a direct answer describing in a self contained way what the
connection actually is.
So user may determine 127.0.0.1 (hostaddr) as "happy_host", for example.
It shouldn't be a real host.
They may determine it if they can access the initial connection
information, which means an careful inquest because \conninfo does not say
what it is... If they just read what is said, they just get wrong
informations.
I searched for another use cases of PQhost(). In PostgreSQL source code I
found that it is used in pg_dump and psql to connect to some instance.
There is the next issue with PQhost() and psql (pg_dump could have it too,
see CloneArchive() in pg_backup_archiver.c and _connectDB() in
pg_backup_db.c):
$ psql "host=host_1,host_2 hostaddr=127.0.0.1,127.0.0.3 dbname=postgres"
=# \conninfo
You are connected to database "postgres" as user "artur" on host "host_1" at
port "5432".
=# \connect test
could not translate host name "host_1" to address: Неизвестное имя или служба
Previous connection kept
So in the example above you cannot reuse connection string with \connect.
What do you think?
I think that this is another connection related "feature", aka bug, that
should be fixed as well:-(
I cannot agree with you. When I've learned libpq before I found
host/hostaddr rules description useful. And I disagree that it is good
to remove it (as the patch does).
Of course it is only my point of view and others may have another opinion.
I'm not sure I understand your concern.
Do you mean that you would prefer the document to keep describing that
host/hostaddr/port accepts one value, and then have in some other place or
at the end of the option documentation a line that say, "by the way, we
really accept lists, and they must be somehow consistent between
host/hostaddr/port"?
I wrote about the following part of the documentation:
- Using <literal>hostaddr</literal> instead of <literal>host</literal>
allows the
- application to avoid a host name look-up, which might be important
- in applications with time constraints. However, a host name is
- required for GSSAPI or SSPI authentication
- methods, as well as for <literal>verify-full</literal> SSL
- certificate verification. The following rules are used:
- <itemizedlist>
...
So I think description of these rules is useful here and shouldn't be
removed.
Ok, I have put back a summary description of which rules apply, which are
somehow simpler & saner, at least this is the aim of this patch.
Your patch removes it and maybe it shouldn't do that. But now I
realised that the patch breaks this behavior and backward compatibility
is broken.
Indeed. The incompatible changes are that "host" must always be provided,
instead of letting the user providing an IP either in host or hostaddr
(currently both work although undocumented), and that "hostaddr" can only
be provided for a host name, not for an IP or socket.
Patch gives me an error if I specified only hostaddr:
psql -d "hostaddr=127.0.0.1"
psql: host "/tmp" cannot have an hostaddr "127.0.0.1"
This is the expected modified behavior: hostaddr can only be specified on a
host when it is a name, which is not the case here.
See the comment above about backward compatibility. psql without the patch
can connect to an instance if I specify only hostaddr.
Yes, that is intentional and is the purpose of this patch: to provide a
simple connection model for the user: use "host" to connect to a target
server, and "hostaddr" as a lookup shortcut only.
For a reminder, my main issues with the current status are:
(1) the documentation is inconsistent with the implementation:
"host" can be given an IP, but this is not documented.
"hostaddr" can be provided for anything, and overshadows the initial
specification, but:
(2) "\conninfo" does not give a clue about what the connection
really is in such cases.
Moreover, you found another issue with psql's "\connect" which does not
work properly when both "host" & "hostaddr" are given.
In the attached patch, I tried to clarify the documentation further and
fix some rebase issues I had. ISTM that all relevant informations provided
in the previous version are still there.
The backward incompatibility is clearly documented.
The patch does not address the \conninfo issue, which requires extending
libpq. I think that the \connect issue you raised is linked to the same
set of problems within libpq, which does not provide any reliable way to
know about the current connection in some cases, either for describing it
or reusing it.
--
Fabien.
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 82a440531b..c10957f1ee 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -964,49 +964,41 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>host</literal></term>
<listitem>
<para>
- Name of host to connect to.<indexterm><primary>host name</primary></indexterm>
- If a host name begins with a slash, it specifies Unix-domain
- communication rather than TCP/IP communication; the value is the
- name of the directory in which the socket file is stored.
+ Comma-separated list of hosts to connect to.<indexterm><primary>host name</primary></indexterm>
+ Each specified host will be tried in turn in the order given.
+ See <xref linkend="libpq-multiple-hosts"/> for details.
+ Each item may be a host name that will be resolved with a look-up,
+ a numeric IP address (IPv4 in the standard format, e.g.,
+ <literal>172.28.40.9</literal>, or IPv6 if supported by your machine)
+ that will be used directly, or
+ the name of a directory which contains the socket file for Unix-domain
+ communication rather than TCP/IP communication
+ (the specification must then begin with a slash);
+ </para>
+
+ <para>
The default behavior when <literal>host</literal> is
- not specified, or is empty, is to connect to a Unix-domain
+ not specified, or an item is empty, is to connect to a Unix-domain
socket<indexterm><primary>Unix domain socket</primary></indexterm> in
<filename>/tmp</filename> (or whatever socket directory was specified
- when <productname>PostgreSQL</productname> was built). On machines without
- Unix-domain sockets, the default is to connect to <literal>localhost</literal>.
+ when <productname>PostgreSQL</productname> was built).
+ On machines without Unix-domain sockets, the default is to connect to
+ <literal>localhost</literal>.
</para>
- <para>
- A comma-separated list of host names is also accepted, in which case
- each host name in the list is tried in order; an empty item in the
- list selects the default behavior as explained above. See
- <xref linkend="libpq-multiple-hosts"/> for details.
- </para>
- </listitem>
- </varlistentry>
- <varlistentry id="libpq-connect-hostaddr" xreflabel="hostaddr">
- <term><literal>hostaddr</literal></term>
- <listitem>
<para>
- Numeric IP address of host to connect to. This should be in the
- standard IPv4 address format, e.g., <literal>172.28.40.9</literal>. If
- your machine supports IPv6, you can also use those addresses.
- TCP/IP communication is
- always used when a nonempty string is specified for this parameter.
+ To shortcut host name lookups, see parameter
+ <xref linkend="libpq-connect-hostaddr"/>.
</para>
<para>
- Using <literal>hostaddr</literal> instead of <literal>host</literal> allows the
- application to avoid a host name look-up, which might be important
- in applications with time constraints. However, a host name is
- required for GSSAPI or SSPI authentication
- methods, as well as for <literal>verify-full</literal> SSL
- certificate verification. The following rules are used:
+ When connecting, the following rules apply:
<itemizedlist>
<listitem>
<para>
- If <literal>host</literal> is specified
- without <literal>hostaddr</literal>, a host name lookup occurs.
+ If a host name item in <literal>host</literal> has a
+ corresponding empty item in <literal>hostaddr</literal>, a host name
+ lookup occurs, and the resulting set of IPs will be tried in turn.
(When using <function>PQconnectPoll</function>, the lookup occurs
when <function>PQconnectPoll</function> first considers this host
name, and it may cause <function>PQconnectPoll</function> to block
@@ -1015,45 +1007,69 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
<listitem>
<para>
- If <literal>hostaddr</literal> is specified without <literal>host</literal>,
- the value for <literal>hostaddr</literal> gives the server network address.
- The connection attempt will fail if the authentication
- method requires a host name.
+ If a host name item in <literal>host</literal> has a
+ corresponding non-empty item in <literal>hostaddr</literal>, the
+ later network address is used and the host name may be used for
+ authentication purposes.
</para>
</listitem>
<listitem>
<para>
- If both <literal>host</literal> and <literal>hostaddr</literal> are specified,
- the value for <literal>hostaddr</literal> gives the server network address.
- The value for <literal>host</literal> is ignored unless the
- authentication method requires it, in which case it will be
- used as the host name.
+ If an item in <literal>host</literal> is either an IP or a
+ Unix-domain socket, the corresponding <literal>hostaddr</literal>
+ must be empty, and the connection uses the provided data.
</para>
</listitem>
</itemizedlist>
- Note that authentication is likely to fail if <literal>host</literal>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-connect-hostaddr" xreflabel="hostaddr">
+ <term><literal>hostaddr</literal></term>
+ <listitem>
+ <para>
+ Comma-separated numeric IP addresses corresponding one-to-one to
+ <literal>host</literal>, to avoid a host name look-up when non empty.
+ This may be desirable for testing, to work around NAT settings, or for
+ better performance.
+ </para>
+
+ <para>
+ Parameter <literal>hostaddr</literal> must be either empty or a
+ list of the same length as <literal>host</literal>.
+ A non empty item in <literal>hostaddr</literal> can only be provided
+ for corresponding host name items in <literal>host</literal>, but not
+ for IP addresses or Unix-domain sockets.
+ </para>
+
+ <para>
+ If a host name is required for GSSAPI or SSPI authentication
+ methods, for <literal>verify-full</literal> SSL certificate
+ verification, or to identify the connection in
+ a password file (see <xref linkend="libpq-pgpass"/>),
+ the corresponding host name provided by <literal>host</literal> is used.
+ </para>
+
+ <para>
+ When both <literal>host</literal> and <literal>hostaddr</literal> parameters
+ are set, note that authentication is likely to fail if <literal>host</literal>
is not the name of the server at network address <literal>hostaddr</literal>.
- Also, when both <literal>host</literal> and <literal>hostaddr</literal>
- are specified, <literal>host</literal>
- is used to identify the connection in a password file (see
- <xref linkend="libpq-pgpass"/>).
</para>
- <para>
- A comma-separated list of <literal>hostaddr</literal> values is also
- accepted, in which case each host in the list is tried in order.
- An empty item in the list causes the corresponding host name to be
- used, or the default host name if that is empty as well. See
- <xref linkend="libpq-multiple-hosts"/> for details.
- </para>
- <para>
- Without either a host name or host address,
- <application>libpq</application> will connect using a
- local Unix-domain socket; or on machines without Unix-domain
- sockets, it will attempt to connect to <literal>localhost</literal>.
- </para>
- </listitem>
- </varlistentry>
+ <note>
+ <para>
+ From <productname>PostgreSQL</productname> 12,
+ <literal>hostaddr</literal> does not replace <literal>host</literal>,
+ but only works as a look-up shortcut.
+ It can be set only over a host name, but not over an IP address or
+ a Unix-domain socket.
+ To connect directly to an IP address, simply use
+ <literal>host=IP</literal>.
+ </para>
+ </note>
+ </listitem>
+ </varlistentry>
<varlistentry id="libpq-connect-port" xreflabel="port">
<term><literal>port</literal></term>
@@ -1062,10 +1078,10 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
Port number to connect to at the server host, or socket file
name extension for Unix-domain
connections.<indexterm><primary>port</primary></indexterm>
- If multiple hosts were given in the <literal>host</literal> or
- <literal>hostaddr</literal> parameters, this parameter may specify a
- comma-separated list of ports of the same length as the host list, or
- it may specify a single port number to be used for all hosts.
+ If multiple hosts were given in the <literal>host</literal> parameter,
+ this parameter may specify a comma-separated list of ports of the same
+ length as the host list, or it may specify a single port number to be
+ used for all hosts.
An empty string, or an empty item in a comma-separated list,
specifies the default port number established
when <productname>PostgreSQL</productname> was built.
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index d001bc513d..470a47a57b 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -909,14 +909,15 @@ count_comma_separated_elems(const char *input)
/*
* Parse a simple comma-separated list.
*
- * On each call, returns a malloc'd copy of the next element, and sets *more
- * to indicate whether there are any more elements in the list after this,
+ * Note: this parsing must be consistent with count_comma_separated_elems.
+ *
+ * On each call, returns a malloc'd copy of the next element,
* and updates *startptr to point to the next element, if any.
*
* On out of memory, returns NULL.
*/
static char *
-parse_comma_separated_list(char **startptr, bool *more)
+parse_comma_separated_list(char **startptr)
{
char *p;
char *s = *startptr;
@@ -930,7 +931,6 @@ parse_comma_separated_list(char **startptr, bool *more)
e = s;
while (*e != '\0' && *e != ',')
++e;
- *more = (*e == ',');
len = e - s;
p = (char *) malloc(sizeof(char) * (len + 1));
@@ -944,6 +944,22 @@ parse_comma_separated_list(char **startptr, bool *more)
return p;
}
+/* tell whether it is an ipv4 or possibly ipv6 address */
+static bool
+look_like_an_ip(const char *str)
+{
+ int b0, b1, b2, b3;
+ char garbage;
+
+ if (sscanf(str, "%d.%d.%d.%d%c", &b0, &b1, &b2, &b3, &garbage) == 4 &&
+ 0 <= b0 && b0 < 256 && 0 <= b1 && b1 < 256 &&
+ 0 <= b2 && b2 < 256 && 0 <= b3 && b3 < 256)
+ return true;
+ else
+ /* ":" cannot appear in a host name */
+ return strchr(str, ':') != NULL;
+}
+
/*
* connectOptions2
*
@@ -959,16 +975,27 @@ connectOptions2(PGconn *conn)
/*
* Allocate memory for details about each host to which we might possibly
- * try to connect. For that, count the number of elements in the hostaddr
- * or host options. If neither is given, assume one host.
+ * try to connect. For that, count the number of elements in the host
+ * options or assume one host if not set.
*/
conn->whichhost = 0;
- if (conn->pghostaddr && conn->pghostaddr[0] != '\0')
- conn->nconnhost = count_comma_separated_elems(conn->pghostaddr);
- else if (conn->pghost && conn->pghost[0] != '\0')
+
+ if (conn->pghost && conn->pghost[0] != '\0')
conn->nconnhost = count_comma_separated_elems(conn->pghost);
else
conn->nconnhost = 1;
+
+ /* check that hostaddr length is compatible */
+ if (conn->pghostaddr && conn->pghostaddr[0] != '\0' &&
+ count_comma_separated_elems(conn->pghostaddr) != conn->nconnhost)
+ {
+ conn->status = CONNECTION_BAD;
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("host and hostaddr list must be of equal length, got %d and %d\n"),
+ conn->nconnhost, count_comma_separated_elems(conn->pghostaddr));
+ return false;
+ }
+
conn->connhost = (pg_conn_host *)
calloc(conn->nconnhost, sizeof(pg_conn_host));
if (conn->connhost == NULL)
@@ -978,82 +1005,86 @@ connectOptions2(PGconn *conn)
* We now have one pg_conn_host structure per possible host. Fill in the
* host and hostaddr fields for each, by splitting the parameter strings.
*/
- if (conn->pghostaddr != NULL && conn->pghostaddr[0] != '\0')
- {
- char *s = conn->pghostaddr;
- bool more = true;
-
- for (i = 0; i < conn->nconnhost && more; i++)
- {
- conn->connhost[i].hostaddr = parse_comma_separated_list(&s, &more);
- if (conn->connhost[i].hostaddr == NULL)
- goto oom_error;
- }
-
- /*
- * If hostaddr was given, the array was allocated according to the
- * number of elements in the hostaddr list, so it really should be the
- * right size.
- */
- Assert(!more);
- Assert(i == conn->nconnhost);
- }
-
if (conn->pghost != NULL && conn->pghost[0] != '\0')
{
char *s = conn->pghost;
- bool more = true;
- for (i = 0; i < conn->nconnhost && more; i++)
+ for (i = 0; i < conn->nconnhost; i++)
{
- conn->connhost[i].host = parse_comma_separated_list(&s, &more);
+ conn->connhost[i].host = parse_comma_separated_list(&s);
if (conn->connhost[i].host == NULL)
goto oom_error;
}
+ }
- /* Check for wrong number of host items. */
- if (more || i != conn->nconnhost)
+ /* we know that the number or host/hostaddr matches. */
+ if (conn->pghostaddr != NULL && conn->pghostaddr[0] != '\0')
+ {
+ char *s = conn->pghostaddr;
+
+ for (i = 0; i < conn->nconnhost; i++)
{
- conn->status = CONNECTION_BAD;
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("could not match %d host names to %d hostaddr values\n"),
- count_comma_separated_elems(conn->pghost), conn->nconnhost);
- return false;
+ conn->connhost[i].hostaddr = parse_comma_separated_list(&s);
+ if (conn->connhost[i].hostaddr == NULL)
+ goto oom_error;
}
}
/*
- * Now, for each host slot, identify the type of address spec, and fill in
- * the default address if nothing was given.
+ * Now, for each host slot, fill in the default address if necessary,
+ * identify the type of address spec, and make some sanity checks.
*/
for (i = 0; i < conn->nconnhost; i++)
{
pg_conn_host *ch = &conn->connhost[i];
- if (ch->hostaddr != NULL && ch->hostaddr[0] != '\0')
- ch->type = CHT_HOST_ADDRESS;
- else if (ch->host != NULL && ch->host[0] != '\0')
- {
- ch->type = CHT_HOST_NAME;
-#ifdef HAVE_UNIX_SOCKETS
- if (is_absolute_path(ch->host))
- ch->type = CHT_UNIX_SOCKET;
-#endif
- }
- else
+ if (ch->host == NULL || ch->host[0] == '\0')
{
if (ch->host)
free(ch->host);
#ifdef HAVE_UNIX_SOCKETS
ch->host = strdup(DEFAULT_PGSOCKET_DIR);
- ch->type = CHT_UNIX_SOCKET;
#else
ch->host = strdup(DefaultHost);
- ch->type = CHT_HOST_NAME;
#endif
if (ch->host == NULL)
goto oom_error;
}
+
+#ifdef HAVE_UNIX_SOCKETS
+ if (is_absolute_path(ch->host))
+ ch->type = CHT_UNIX_SOCKET;
+ else
+#endif
+ if (look_like_an_ip(ch->host))
+ ch->type = CHT_HOST_ADDRESS;
+ else
+ ch->type = CHT_HOST_NAME;
+
+ if (ch->hostaddr != NULL && ch->hostaddr[0] != '\0')
+ {
+ /* hostaddr only allowed on host names */
+ if (ch->type != CHT_HOST_NAME)
+ {
+ conn->status = CONNECTION_BAD;
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("host \"%s\" cannot have an hostaddr \"%s\"\n"),
+ ch->host, ch->hostaddr);
+ return false;
+ }
+
+ if (!look_like_an_ip(ch->hostaddr))
+ {
+ conn->status = CONNECTION_BAD;
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("hostaddr \"%s\" is not a numeric IP address\n"),
+ ch->hostaddr);
+ return false;
+ }
+
+ /* hostaddr superseedes name for this connection */
+ ch->type = CHT_HOST_ADDRESS;
+ }
}
/*
@@ -1065,36 +1096,40 @@ connectOptions2(PGconn *conn)
*/
if (conn->pgport != NULL && conn->pgport[0] != '\0')
{
- char *s = conn->pgport;
- bool more = true;
+ int count = count_comma_separated_elems(conn->pgport);
- for (i = 0; i < conn->nconnhost && more; i++)
+ if (count > 1)
{
- conn->connhost[i].port = parse_comma_separated_list(&s, &more);
- if (conn->connhost[i].port == NULL)
- goto oom_error;
- }
+ char *s = conn->pgport;
- /*
- * If exactly one port was given, use it for every host. Otherwise,
- * there must be exactly as many ports as there were hosts.
- */
- if (i == 1 && !more)
- {
- for (i = 1; i < conn->nconnhost; i++)
+ /* there must be exactly as many ports as there were hosts. */
+ if (count != conn->nconnhost)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not match %d port numbers to %d hosts\n"),
+ count, conn->nconnhost);
+ return false;
+ }
+
+ for (i = 0; i < conn->nconnhost; i++)
{
- conn->connhost[i].port = strdup(conn->connhost[0].port);
+ conn->connhost[i].port = parse_comma_separated_list(&s);
if (conn->connhost[i].port == NULL)
goto oom_error;
}
}
- else if (more || i != conn->nconnhost)
+ else
{
- conn->status = CONNECTION_BAD;
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("could not match %d port numbers to %d hosts\n"),
- count_comma_separated_elems(conn->pgport), conn->nconnhost);
- return false;
+ Assert(count == 1);
+
+ /* If exactly one port was given, use it for every host. */
+ for (i = 0; i < conn->nconnhost; i++)
+ {
+ conn->connhost[i].port = strdup(conn->pgport);
+ if (conn->connhost[i].port == NULL)
+ goto oom_error;
+ }
}
}
@@ -2120,6 +2155,7 @@ keep_going: /* We will come back to here until there is
int thisport;
int ret;
char portstr[MAXPGPATH];
+ char *addr;
if (conn->whichhost + 1 >= conn->nconnhost)
{
@@ -2181,13 +2217,13 @@ keep_going: /* We will come back to here until there is
case CHT_HOST_ADDRESS:
hint.ai_flags = AI_NUMERICHOST;
- ret = pg_getaddrinfo_all(ch->hostaddr, portstr, &hint,
- &conn->addrlist);
+ addr = (ch->hostaddr != NULL && ch->hostaddr[0] != '\0') ? ch->hostaddr : ch->host;
+ ret = pg_getaddrinfo_all(addr, portstr, &hint, &conn->addrlist);
if (ret || !conn->addrlist)
{
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not parse network address \"%s\": %s\n"),
- ch->hostaddr, gai_strerror(ret));
+ addr, gai_strerror(ret));
goto keep_going;
}
break;
@@ -6161,12 +6197,10 @@ PQhost(const PGconn *conn)
if (conn->connhost != NULL)
{
+ /* always true, defaults are filled in by Options2 */
if (conn->connhost[conn->whichhost].host != NULL &&
conn->connhost[conn->whichhost].host[0] != '\0')
return conn->connhost[conn->whichhost].host;
- else if (conn->connhost[conn->whichhost].hostaddr != NULL &&
- conn->connhost[conn->whichhost].hostaddr[0] != '\0')
- return conn->connhost[conn->whichhost].hostaddr;
}
return "";