From: Huang Lei <lhua...@ebay.com> In some environments, an interface may have multiple IP addresses in same subnet, if TCP client socket doesn't call bind() explicitly, OS will chooses an local IP and port for it, usually the primary IP of the subnet will be chosen. With this patch, a secondary IP of the subnet can be specified as local address of TCP connection.
Signed-off-by: Huang Lei <lhua...@ebay.com> --- lib/socket-util.c | 44 ++++++++++--- lib/socket-util.h | 3 +- lib/stream.c | 8 +-- ofproto/ofproto-dpif-sflow.c | 2 +- ovn/controller/ovn-controller.8.xml | 15 ++++- tests/library.at | 5 ++ tests/test-util.c | 121 ++++++++++++++++++++++++++++++++++++ 7 files changed, 183 insertions(+), 15 deletions(-) diff --git a/lib/socket-util.c b/lib/socket-util.c index 5a36f3b..7ea25ec 100644 --- a/lib/socket-util.c +++ b/lib/socket-util.c @@ -392,7 +392,8 @@ exit: return false; } -/* Parses 'target', which should be a string in the format "<host>[:<port>]". +/* Parses 'target', which should be a string in the format + * "<host>[:<port>[:<local>[:<port>]]]". * <host>, which is required, may be an IPv4 address or an IPv6 address * enclosed in square brackets. If 'default_port' is nonzero then <port> is * optional and defaults to 'default_port'. @@ -401,7 +402,8 @@ exit: * On failure, logs an error, stores zeros into '*ss', and returns false. */ bool inet_parse_active(const char *target_, uint16_t default_port, - struct sockaddr_storage *ss) + struct sockaddr_storage *foreign_ss, + struct sockaddr_storage *local_ss) { char *target = xstrdup(target_); const char *port; @@ -415,15 +417,32 @@ inet_parse_active(const char *target_, uint16_t default_port, if (!host) { VLOG_ERR("%s: host must be specified", target_); ok = false; - } else if (!port && !default_port) { + } else if ((!port || !port[0]) && !default_port) { VLOG_ERR("%s: port must be specified", target_); ok = false; } else { - ok = parse_sockaddr_components(ss, host, port, default_port, target_); + ok = parse_sockaddr_components(foreign_ss, host, port, default_port, target_); } if (!ok) { - memset(ss, 0, sizeof *ss); + memset(foreign_ss, 0, sizeof *foreign_ss); + goto exit; + } + + if (local_ss) { + const char *local; + const char *local_port; + + local = parse_bracketed_token(&p); + local_port = parse_bracketed_token(&p); + + local = (!local || !local[0]) ? "0.0.0.0" : local; + ok = parse_sockaddr_components(local_ss, local, local_port, 0, target_); + if (!ok) { + memset(local_ss, 0, sizeof *local_ss); + } } + +exit: free(target); return ok; } @@ -431,7 +450,8 @@ inet_parse_active(const char *target_, uint16_t default_port, /* Opens a non-blocking IPv4 or IPv6 socket of the specified 'style' and * connects to 'target', which should be a string in the format - * "<host>[:<port>]". <host>, which is required, may be an IPv4 address or an + * "<host>[:<port>[:<local>[:<port>]]]". + * <host>, which is required, may be an IPv4 address or an * IPv6 address enclosed in square brackets. If 'default_port' is nonzero then * <port> is optional and defaults to 'default_port'. * @@ -452,11 +472,12 @@ inet_open_active(int style, const char *target, uint16_t default_port, struct sockaddr_storage *ssp, int *fdp, uint8_t dscp) { struct sockaddr_storage ss; + struct sockaddr_storage local_ss; int fd = -1; int error; /* Parse. */ - if (!inet_parse_active(target, default_port, &ss)) { + if (!inet_parse_active(target, default_port, &ss, &local_ss)) { error = EAFNOSUPPORT; goto exit; } @@ -482,6 +503,15 @@ inet_open_active(int style, const char *target, uint16_t default_port, goto exit; } + error = bind(fd, (struct sockaddr *) &local_ss, + ss_length(&local_ss)) == 0 + ? 0 + : sock_errno(); + if (error) { + VLOG_ERR("%s: bind failed", target); + goto exit; + } + /* Connect. */ error = connect(fd, (struct sockaddr *) &ss, ss_length(&ss)) == 0 ? 0 diff --git a/lib/socket-util.h b/lib/socket-util.h index c3c1224..1653483 100644 --- a/lib/socket-util.h +++ b/lib/socket-util.h @@ -44,7 +44,8 @@ void drain_fd(int fd, size_t n_packets); ovs_be32 guess_netmask(ovs_be32 ip); bool inet_parse_active(const char *target, uint16_t default_port, - struct sockaddr_storage *ssp); + struct sockaddr_storage *foreign_ssp, + struct sockaddr_storage *local_ssp); int inet_open_active(int style, const char *target, uint16_t default_port, struct sockaddr_storage *ssp, int *fdp, uint8_t dscp); diff --git a/lib/stream.c b/lib/stream.c index f6ea849..962de31 100644 --- a/lib/stream.c +++ b/lib/stream.c @@ -125,13 +125,13 @@ stream_usage(const char *name, bool active, bool passive, printf("\n"); if (active) { printf("Active %s connection methods:\n", name); - printf(" tcp:IP:PORT " + printf(" tcp:IP:PORT[:LOCAL-IP[:LOCAL-PORT]] " "PORT at remote IP\n"); #ifdef HAVE_OPENSSL - printf(" ssl:IP:PORT " + printf(" ssl:IP:PORT " "SSL PORT at remote IP\n"); #endif - printf(" unix:FILE " + printf(" unix:FILE " "Unix domain socket named FILE\n"); } @@ -732,7 +732,7 @@ stream_parse_target_with_default_port(const char *target, struct sockaddr_storage *ss) { return ((!strncmp(target, "tcp:", 4) || !strncmp(target, "ssl:", 4)) - && inet_parse_active(target + 4, default_port, ss)); + && inet_parse_active(target + 4, default_port, ss, NULL)); } /* Attempts to guess the content type of a stream whose first few bytes were diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c index fbc82b7..f9f8967 100644 --- a/ofproto/ofproto-dpif-sflow.c +++ b/ofproto/ofproto-dpif-sflow.c @@ -451,7 +451,7 @@ sflow_choose_agent_address(const char *agent_device, } sa; char name[IFNAMSIZ]; - if (inet_parse_active(target, SFL_DEFAULT_COLLECTOR_PORT, &sa.ss) + if (inet_parse_active(target, SFL_DEFAULT_COLLECTOR_PORT, &sa.ss, NULL) && sa.ss.ss_family == AF_INET) { struct in6_addr addr6, src, gw; diff --git a/ovn/controller/ovn-controller.8.xml b/ovn/controller/ovn-controller.8.xml index 1ee3a6e..4f0b966 100644 --- a/ovn/controller/ovn-controller.8.xml +++ b/ovn/controller/ovn-controller.8.xml @@ -46,13 +46,14 @@ </li> <li> <p> - <code>tcp:<var>ip</var>:<var>port</var></code> + <code>tcp:<var>ip</var>:<var>port</var>[:<var>local-ip</var>[:<var>local-port</var>]]</code> </p> <p> Connect to the given TCP <var>port</var> on <var>ip</var>, where <var>ip</var> can be IPv4 or IPv6 address. If <var>ip</var> is an IPv6 address, then wrap <var>ip</var> with square brackets, e.g.: - <code>tcp:[::1]:6640</code>. + <code>tcp:[::1]:6640</code>. <var>local-ip</var> and + <var>local-port</var> are optional. </p> </li> <li> @@ -97,6 +98,16 @@ </p> <p> + If use a active TCP connection, the value should be a string in the + format + "<var>remote-ip</var>[:<var>remote-port</var>[:<var>local-ip</var>[:<var>local-port</var>]]]". + <var>remote-ip</var> is required, may be an IPv4 address or an IPv6 + address enclosed in square brackets. <var>remote-port</var>, + <var>local-ip</var> and <var>local-port</var> are optional. + If <var>remote-port</var> is not set, it defaults to 6640. + </p> + + <p> Currently, <code>ovn-controller</code> does not support changing this setting mid-run. If the value needs to change, the daemon must be restarted. (This behavior should be improved.) diff --git a/tests/library.at b/tests/library.at index 8b1c443..90ab746 100644 --- a/tests/library.at +++ b/tests/library.at @@ -136,6 +136,11 @@ m4_foreach( AT_CHECK([ovstest test-util testname], [0], [], []) AT_CLEANUP]) +AT_SETUP([inet_parse_active[()] function]) +AT_KEYWORDS([inet_parse_active]) +AT_CHECK([ovstest test-util inet_parse_active], [0], [], [stderr]) +AT_CLEANUP + AT_SETUP([test unix socket, short pathname - C]) AT_SKIP_IF([test "$IS_WIN32" = "yes"]) AT_CHECK([ovstest test-unix-socket x]) diff --git a/tests/test-util.c b/tests/test-util.c index ef45903..2292bc8 100644 --- a/tests/test-util.c +++ b/tests/test-util.c @@ -23,11 +23,13 @@ #include <limits.h> #include <stdio.h> #include <stdlib.h> +#include <netinet/in.h> #include "byte-order.h" #include "command-line.h" #include "ovstest.h" #include "random.h" #include "openvswitch/vlog.h" +#include "socket-util.h" static void check_log_2_floor(uint32_t x, int n) @@ -1128,6 +1130,124 @@ test_snprintf(struct ovs_cmdl_context *ctx OVS_UNUSED) ovs_assert(snprintf(NULL, 0, "abcde") == 5); } +static void +test_inet_parse_active(struct ovs_cmdl_context *ctx OVS_UNUSED) +{ + union { + struct sockaddr_storage ss; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + } foreign_sa; + union { + struct sockaddr_storage ss; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + } local_sa; + bool ok = false; + + ok = inet_parse_active("127.0.0.1:100", 0, &foreign_sa.ss, &local_sa.ss); + ovs_assert(ok); + ovs_assert(foreign_sa.sin.sin_family == AF_INET); + ovs_assert(foreign_sa.sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK)); + ovs_assert(foreign_sa.sin.sin_port == htons(100)); + ovs_assert(local_sa.sin.sin_family == AF_INET); + ovs_assert(local_sa.sin.sin_addr.s_addr == htonl(INADDR_ANY)); + ovs_assert(local_sa.sin.sin_port == htons(0)); + + ok = inet_parse_active("127.0.0.1", 100, &foreign_sa.ss, &local_sa.ss); + ovs_assert(ok); + ovs_assert(foreign_sa.sin.sin_family == AF_INET); + ovs_assert(foreign_sa.sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK)); + ovs_assert(foreign_sa.sin.sin_port == htons(100)); + ovs_assert(local_sa.sin.sin_family == AF_INET); + ovs_assert(local_sa.sin.sin_addr.s_addr == htonl(INADDR_ANY)); + ovs_assert(local_sa.sin.sin_port == htons(0)); + + ok = inet_parse_active("127.0.0.1:", 100, &foreign_sa.ss, &local_sa.ss); + ovs_assert(ok); + ovs_assert(foreign_sa.sin.sin_family == AF_INET); + ovs_assert(foreign_sa.sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK)); + ovs_assert(foreign_sa.sin.sin_port == htons(100)); + ovs_assert(local_sa.sin.sin_family == AF_INET); + ovs_assert(local_sa.sin.sin_addr.s_addr == htonl(INADDR_ANY)); + ovs_assert(local_sa.sin.sin_port == htons(0)); + + ok = inet_parse_active("127.0.0.1", 0, &foreign_sa.ss, &local_sa.ss); + ovs_assert(!ok); + + ok = inet_parse_active("127.0.0.1:", 0, &foreign_sa.ss, &local_sa.ss); + ovs_assert(!ok); + + ok = inet_parse_active("127.0.0.1:100", 0, &foreign_sa.ss, NULL); + ovs_assert(ok); + ovs_assert(foreign_sa.sin.sin_family == AF_INET); + ovs_assert(foreign_sa.sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK)); + ovs_assert(foreign_sa.sin.sin_port == htons(100)); + + ok = inet_parse_active("127.0.0.1:100:127.0.0.1:200", 0, &foreign_sa.ss, &local_sa.ss); + ovs_assert(ok); + ovs_assert(foreign_sa.sin.sin_family == AF_INET); + ovs_assert(foreign_sa.sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK)); + ovs_assert(foreign_sa.sin.sin_port == htons(100)); + ovs_assert(local_sa.sin.sin_family == AF_INET); + ovs_assert(local_sa.sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK)); + ovs_assert(local_sa.sin.sin_port == htons(200)); + + ok = inet_parse_active("127.0.0.1:100:127.0.0.1:200", 0, &foreign_sa.ss, NULL); + ovs_assert(ok); + ovs_assert(foreign_sa.sin.sin_family == AF_INET); + ovs_assert(foreign_sa.sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK)); + ovs_assert(foreign_sa.sin.sin_port == htons(100)); + + ok = inet_parse_active("127.0.0.1:6640:127.0.0.1:", 0, &foreign_sa.ss, &local_sa.ss); + ovs_assert(ok); + ovs_assert(local_sa.sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK)); + ovs_assert(local_sa.sin.sin_port == htons(0)); + + ok = inet_parse_active("127.0.0.1:6640:127.0.0.1", 0, &foreign_sa.ss, &local_sa.ss); + ovs_assert(ok); + ovs_assert(local_sa.sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK)); + ovs_assert(local_sa.sin.sin_port == htons(0)); + + ok = inet_parse_active("127.0.0.1:6640::200", 0, &foreign_sa.ss, &local_sa.ss); + ovs_assert(ok); + ovs_assert(local_sa.sin.sin_family == AF_INET); + ovs_assert(local_sa.sin.sin_addr.s_addr == htonl(INADDR_ANY)); + ovs_assert(local_sa.sin.sin_port == htons(200)); + + ok = inet_parse_active("127.0.0.1:6640::", 0, &foreign_sa.ss, &local_sa.ss); + ovs_assert(ok); + ovs_assert(local_sa.sin.sin_family == AF_INET); + ovs_assert(local_sa.sin.sin_addr.s_addr == htonl(INADDR_ANY)); + ovs_assert(local_sa.sin.sin_port == htons(0)); + + ok = inet_parse_active("127.0.0.1:::", 100, &foreign_sa.ss, &local_sa.ss); + ovs_assert(ok); + ovs_assert(foreign_sa.sin.sin_family == AF_INET); + ovs_assert(foreign_sa.sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK)); + ovs_assert(foreign_sa.sin.sin_port == htons(100)); + ovs_assert(local_sa.sin.sin_family == AF_INET); + ovs_assert(local_sa.sin.sin_addr.s_addr == htonl(INADDR_ANY)); + ovs_assert(local_sa.sin.sin_port == htons(0)); + + ok = inet_parse_active("127.0.0.1::", 100, &foreign_sa.ss, &local_sa.ss); + ovs_assert(ok); + ovs_assert(foreign_sa.sin.sin_family == AF_INET); + ovs_assert(foreign_sa.sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK)); + ovs_assert(foreign_sa.sin.sin_port == htons(100)); + ovs_assert(local_sa.sin.sin_family == AF_INET); + ovs_assert(local_sa.sin.sin_addr.s_addr == htonl(INADDR_ANY)); + ovs_assert(local_sa.sin.sin_port == htons(0)); + + ok = inet_parse_active("127.0.0.1:999999:127.0.0.1:200", 0, &foreign_sa.ss, NULL); + ovs_assert(!ok); + + ok = inet_parse_active("127.0.0.0.0.1:100:127.0.0.1:200", 0, &foreign_sa.ss, NULL); + ovs_assert(!ok); +} + #ifndef _WIN32 static void test_file_name(struct ovs_cmdl_context *ctx) @@ -1164,6 +1282,7 @@ static const struct ovs_cmdl_command commands[] = { {"assert", NULL, 0, 0, test_assert}, {"ovs_scan", NULL, 0, 0, test_ovs_scan}, {"snprintf", NULL, 0, 0, test_snprintf}, + {"inet_parse_active", NULL, 0, 0, test_inet_parse_active}, #ifndef _WIN32 {"file_name", NULL, 1, INT_MAX, test_file_name}, #endif -- 1.9.1 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev