MINOR: new update_server_addr_port() function to change both server's ADDR and service PORT
This function can replace update_server_addr() where the need to change the
server's port as well as the IP address is required.
It performs some validation before performing each type of change.
diff --git a/include/proto/server.h b/include/proto/server.h
index ee45f63..47630fe 100644
--- a/include/proto/server.h
+++ b/include/proto/server.h
@@ -40,6 +40,7 @@
int srv_getinter(const struct check *check);
int parse_server(const char *file, int linenum, char **args, struct proxy *curproxy, struct proxy *defproxy);
int update_server_addr(struct server *s, void *ip, int ip_sin_family, const char *updater);
+const char *update_server_addr_port(struct server *s, const char *addr, const char *port, char *updater);
struct server *server_find_by_id(struct proxy *bk, int id);
struct server *server_find_by_name(struct proxy *bk, const char *name);
struct server *server_find_best_match(struct proxy *bk, char *name, int id, int *diff);
diff --git a/src/server.c b/src/server.c
index a90ae9b..b105e28 100644
--- a/src/server.c
+++ b/src/server.c
@@ -2611,6 +2611,180 @@
}
/*
+ * This function update a server's addr and port only for AF_INET and AF_INET6 families.
+ *
+ * Caller can pass its name through <updater> to get it integrated in the response message
+ * returned by the function.
+ *
+ * The function first does the following, in that order:
+ * - validates the new addr and/or port
+ * - checks if an update is required (new IP or port is different than current ones)
+ * - checks the update is allowed:
+ * - don't switch from/to a family other than AF_INET4 and AF_INET6
+ * - allow all changes if no CHECKS are configured
+ * - if CHECK is configured:
+ * - if switch to port map (SRV_F_MAPPORTS), ensure health check have their own ports
+ * - applies required changes to both ADDR and PORT if both 'required' and 'allowed'
+ * conditions are met
+ */
+const char *update_server_addr_port(struct server *s, const char *addr, const char *port, char *updater)
+{
+ struct sockaddr_storage sa;
+ int ret, port_change_required;
+ char current_addr[INET6_ADDRSTRLEN];
+ u_int16_t current_port, new_port;
+ struct chunk *msg;
+
+ msg = get_trash_chunk();
+ chunk_reset(msg);
+
+ if (addr) {
+ memset(&sa, 0, sizeof(struct sockaddr_storage));
+ if (str2ip2(addr, &sa, 0) == NULL) {
+ chunk_printf(msg, "Invalid addr '%s'", addr);
+ goto out;
+ }
+
+ /* changes are allowed on AF_INET* families only */
+ if ((sa.ss_family != AF_INET) && (sa.ss_family != AF_INET6)) {
+ chunk_printf(msg, "Update to families other than AF_INET and AF_INET6 supported only through configuration file");
+ goto out;
+ }
+
+ /* collecting data currently setup */
+ memset(current_addr, '\0', sizeof(current_addr));
+ ret = addr_to_str(&s->addr, current_addr, sizeof(current_addr));
+ /* changes are allowed on AF_INET* families only */
+ if ((ret != AF_INET) && (ret != AF_INET6)) {
+ chunk_printf(msg, "Update for the current server address family is only supported through configuration file");
+ goto out;
+ }
+
+ /* applying ADDR changes if required and allowed
+ * ipcmp returns 0 when both ADDR are the same
+ */
+ if (ipcmp(&s->addr, &sa) == 0) {
+ chunk_appendf(msg, "no need to change the addr");
+ goto port;
+ }
+ current_port = get_host_port(&s->addr);
+ memset(&s->addr, '\0', sizeof(s->addr));
+ ipcpy(&sa, &s->addr);
+ set_host_port(&s->addr, current_port);
+
+ /* we also need to update check's ADDR only if it uses the server's one */
+ if ((s->check.state & CHK_ST_CONFIGURED) && (s->flags & SRV_F_CHECKADDR)) {
+ current_port = get_host_port(&s->check.addr);
+ memset(&s->check.addr, '\0', sizeof(s->check.addr));
+ ipcpy(&sa, &s->check.addr);
+ set_host_port(&s->check.addr, current_port);
+ }
+
+ /* we also need to update agent ADDR only if it use the server's one */
+ if ((s->agent.state & CHK_ST_CONFIGURED) && (s->flags & SRV_F_AGENTADDR)) {
+ current_port = get_host_port(&s->agent.addr);
+ memset(&s->agent.addr, '\0', sizeof(s->agent.addr));
+ ipcpy(&sa, &s->agent.addr);
+ set_host_port(&s->agent.addr, current_port);
+ }
+
+ /* update report for caller */
+ chunk_printf(msg, "IP changed from '%s' to '%s'", current_addr, addr);
+ }
+
+ port:
+ if (port) {
+ char sign = '\0';
+ char *endptr;
+
+ if (addr)
+ chunk_appendf(msg, ", ");
+
+ /* collecting data currently setup */
+ current_port = get_host_port(&s->addr);
+
+ /* check if PORT change is required */
+ port_change_required = 0;
+
+ sign = *port;
+ new_port = strtol(port, &endptr, 10);
+ if ((errno != 0) || (port == endptr)) {
+ chunk_appendf(msg, "problem converting port '%s' to an int", port);
+ goto out;
+ }
+
+ /* check if caller triggers a port mapped or offset */
+ if (sign == '-' || (sign == '+')) {
+ /* check if server currently uses port map */
+ if (!(s->flags & SRV_F_MAPPORTS)) {
+ /* switch from fixed port to port map mandatorily triggers
+ * a port change */
+ port_change_required = 1;
+ /* check is configured
+ * we're switching from a fixed port to a SRV_F_MAPPORTS (mapped) port
+ * prevent PORT change if check doesn't have it's dedicated port while switching
+ * to port mapping */
+ if ((s->check.state & CHK_ST_CONFIGURED) && !(s->flags & SRV_F_CHECKPORT)) {
+ chunk_appendf(msg, "can't change <port> to port map because it is incompatible with current health check port configuration (use 'port' statement from the 'server' directive.");
+ goto out;
+ }
+ }
+ /* we're already using port maps */
+ else {
+ port_change_required = current_port != new_port;
+ }
+ }
+ /* fixed port */
+ else {
+ port_change_required = current_port != new_port;
+ }
+
+ /* applying PORT changes if required and update response message */
+ if (port_change_required) {
+ /* apply new port */
+ set_host_port(&s->addr, new_port);
+
+ /* prepare message */
+ chunk_appendf(msg, "port changed from '");
+ if (s->flags & SRV_F_MAPPORTS)
+ chunk_appendf(msg, "+");
+ chunk_appendf(msg, "%d' to '", current_port);
+
+ if (sign == '-') {
+ s->flags |= SRV_F_MAPPORTS;
+ chunk_appendf(msg, "%c", sign);
+ /* just use for result output */
+ new_port = -new_port;
+ }
+ else if (sign == '+') {
+ s->flags |= SRV_F_MAPPORTS;
+ chunk_appendf(msg, "%c", sign);
+ }
+ else {
+ s->flags &= ~SRV_F_MAPPORTS;
+ }
+
+ chunk_appendf(msg, "%d'", new_port);
+
+ /* we also need to update health checks port only if it uses server's realport */
+ if ((s->check.state & CHK_ST_CONFIGURED) && !(s->flags & SRV_F_CHECKPORT)) {
+ s->check.port = new_port;
+ }
+ }
+ else {
+ chunk_appendf(msg, "no need to change the port");
+ }
+ }
+
+out:
+ if (updater)
+ chunk_appendf(msg, " by '%s'", updater);
+ chunk_appendf(msg, "\n");
+ return msg->str;
+}
+
+
+/*
* update server status based on result of name resolution
* returns:
* 0 if server status is updated