diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2023-05-13 17:11:56 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-13 17:11:56 +0100 |
commit | 9d41a3fd694d983ade53fb602a58f6df25ce0656 (patch) | |
tree | 73fb6759a3138023f85654e38dcac1fb9cc4c7ad /src/libgit2/streams | |
parent | 905e4d19c2c401cef3964c2e28bf64b0dad77331 (diff) | |
parent | 8f695c806bac2b044f102c55b802e44f2d30ca01 (diff) | |
download | libgit2-9d41a3fd694d983ade53fb602a58f6df25ce0656.tar.gz |
Merge pull request #6535 from libgit2/ethomson/timeouts
Diffstat (limited to 'src/libgit2/streams')
-rw-r--r-- | src/libgit2/streams/mbedtls.c | 1 | ||||
-rw-r--r-- | src/libgit2/streams/openssl.c | 24 | ||||
-rw-r--r-- | src/libgit2/streams/socket.c | 212 | ||||
-rw-r--r-- | src/libgit2/streams/socket.h | 2 | ||||
-rw-r--r-- | src/libgit2/streams/stransport.c | 52 |
5 files changed, 238 insertions, 53 deletions
diff --git a/src/libgit2/streams/mbedtls.c b/src/libgit2/streams/mbedtls.c index 0cf5c8af1..49aa76c3e 100644 --- a/src/libgit2/streams/mbedtls.c +++ b/src/libgit2/streams/mbedtls.c @@ -14,7 +14,6 @@ #include "runtime.h" #include "stream.h" #include "streams/socket.h" -#include "netops.h" #include "git2/transport.h" #include "util.h" diff --git a/src/libgit2/streams/openssl.c b/src/libgit2/streams/openssl.c index 5e0e2c939..9db911e39 100644 --- a/src/libgit2/streams/openssl.c +++ b/src/libgit2/streams/openssl.c @@ -18,8 +18,8 @@ #include "settings.h" #include "posix.h" #include "stream.h" +#include "net.h" #include "streams/socket.h" -#include "netops.h" #include "git2/transport.h" #include "git2/sys/openssl.h" @@ -70,14 +70,14 @@ static void *git_openssl_malloc(size_t bytes, const char *file, int line) GIT_UNUSED(line); return git__calloc(1, bytes); } - + static void *git_openssl_realloc(void *mem, size_t size, const char *file, int line) { GIT_UNUSED(file); GIT_UNUSED(line); return git__realloc(mem, size); } - + static void git_openssl_free(void *mem, const char *file, int line) { GIT_UNUSED(file); @@ -357,15 +357,10 @@ static int ssl_teardown(SSL *ssl) return ret; } -static int check_host_name(const char *name, const char *host) +static bool check_host_name(const char *host, const char *name) { - if (!strcasecmp(name, host)) - return 0; - - if (gitno__match_host(name, host) < 0) - return -1; - - return 0; + return !strcasecmp(host, name) || + git_net_hostname_matches_cert(host, name); } static int verify_server_cert(SSL *ssl, const char *host) @@ -425,10 +420,7 @@ static int verify_server_cert(SSL *ssl, const char *host) if (memchr(name, '\0', namelen)) continue; - if (check_host_name(name, host) < 0) - matched = 0; - else - matched = 1; + matched = !!check_host_name(host, name); } else if (type == GEN_IPADD) { /* Here name isn't so much a name but a binary representation of the IP */ matched = addr && !!memcmp(name, addr, namelen); @@ -481,7 +473,7 @@ static int verify_server_cert(SSL *ssl, const char *host) goto cert_fail_name; } - if (check_host_name((char *)peer_cn, host) < 0) + if (!check_host_name(host, (char *)peer_cn)) goto cert_fail_name; goto cleanup; diff --git a/src/libgit2/streams/socket.c b/src/libgit2/streams/socket.c index 8f23e746e..a463312fd 100644 --- a/src/libgit2/streams/socket.c +++ b/src/libgit2/streams/socket.c @@ -8,7 +8,6 @@ #include "streams/socket.h" #include "posix.h" -#include "netops.h" #include "registry.h" #include "runtime.h" #include "stream.h" @@ -29,6 +28,9 @@ # endif #endif +int git_socket_stream__connect_timeout = 0; +int git_socket_stream__timeout = 0; + #ifdef GIT_WIN32 static void net_set_error(const char *str) { @@ -67,21 +69,119 @@ static int close_socket(GIT_SOCKET s) } +static int set_nonblocking(GIT_SOCKET s) +{ +#ifdef GIT_WIN32 + unsigned long nonblocking = 1; + + if (ioctlsocket(s, FIONBIO, &nonblocking) != 0) { + net_set_error("could not set socket non-blocking"); + return -1; + } +#else + int flags; + + if ((flags = fcntl(s, F_GETFL, 0)) == -1) { + net_set_error("could not query socket flags"); + return -1; + } + + flags |= O_NONBLOCK; + + if (fcntl(s, F_SETFL, flags) != 0) { + net_set_error("could not set socket non-blocking"); + return -1; + } +#endif + + return 0; +} + +/* Promote a sockerr to an errno for our error handling routines */ +static int handle_sockerr(GIT_SOCKET socket) +{ + int sockerr; + socklen_t errlen = sizeof(sockerr); + + if (getsockopt(socket, SOL_SOCKET, SO_ERROR, + (void *)&sockerr, &errlen) < 0) + return -1; + + if (sockerr == ETIMEDOUT) + return GIT_TIMEOUT; + + errno = sockerr; + return -1; +} + +GIT_INLINE(bool) connect_would_block(int error) +{ +#ifdef GIT_WIN32 + if (error == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK) + return true; +#endif + + if (error == -1 && errno == EINPROGRESS) + return true; + + return false; +} + +static int connect_with_timeout( + GIT_SOCKET socket, + const struct sockaddr *address, + socklen_t address_len, + int timeout) +{ + struct pollfd fd; + int error; + + if (timeout && (error = set_nonblocking(socket)) < 0) + return error; + + error = connect(socket, address, address_len); + + if (error == 0 || !connect_would_block(error)) + return error; + + fd.fd = socket; + fd.events = POLLOUT; + fd.revents = 0; + + error = p_poll(&fd, 1, timeout); + + if (error == 0) { + return GIT_TIMEOUT; + } else if (error != 1) { + return -1; + } else if ((fd.revents & (POLLPRI | POLLHUP | POLLERR))) { + return handle_sockerr(socket); + } else if ((fd.revents & POLLOUT) != POLLOUT) { + git_error_set(GIT_ERROR_NET, + "unknown error while polling for connect: %d", + fd.revents); + return -1; + } + + return 0; +} + static int socket_connect(git_stream *stream) { - struct addrinfo *info = NULL, *p; - struct addrinfo hints; git_socket_stream *st = (git_socket_stream *) stream; GIT_SOCKET s = INVALID_SOCKET; - int ret; + struct addrinfo *info = NULL, *p; + struct addrinfo hints; + int error; memset(&hints, 0x0, sizeof(struct addrinfo)); hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC; - if ((ret = p_getaddrinfo(st->host, st->port, &hints, &info)) != 0) { + if ((error = p_getaddrinfo(st->host, st->port, &hints, &info)) != 0) { git_error_set(GIT_ERROR_NET, - "failed to resolve address for %s: %s", st->host, p_gai_strerror(ret)); + "failed to resolve address for %s: %s", + st->host, p_gai_strerror(error)); return -1; } @@ -91,51 +191,115 @@ static int socket_connect(git_stream *stream) if (s == INVALID_SOCKET) continue; - if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0) + error = connect_with_timeout(s, p->ai_addr, + (socklen_t)p->ai_addrlen, + st->parent.connect_timeout); + + if (error == 0) break; /* If we can't connect, try the next one */ close_socket(s); s = INVALID_SOCKET; + + if (error == GIT_TIMEOUT) + break; } /* Oops, we couldn't connect to any address */ - if (s == INVALID_SOCKET && p == NULL) { - git_error_set(GIT_ERROR_OS, "failed to connect to %s", st->host); - p_freeaddrinfo(info); - return -1; + if (s == INVALID_SOCKET) { + if (error == GIT_TIMEOUT) + git_error_set(GIT_ERROR_NET, "failed to connect to %s: Operation timed out", st->host); + else + git_error_set(GIT_ERROR_OS, "failed to connect to %s", st->host); + error = -1; + goto done; } + if (st->parent.timeout && !st->parent.connect_timeout && + (error = set_nonblocking(s)) < 0) + return error; + st->s = s; + error = 0; + +done: p_freeaddrinfo(info); - return 0; + return error; } -static ssize_t socket_write(git_stream *stream, const char *data, size_t len, int flags) +static ssize_t socket_write( + git_stream *stream, + const char *data, + size_t len, + int flags) { git_socket_stream *st = (git_socket_stream *) stream; - ssize_t written; + struct pollfd fd; + ssize_t ret; GIT_ASSERT(flags == 0); GIT_UNUSED(flags); - errno = 0; + ret = p_send(st->s, data, len, 0); - if ((written = p_send(st->s, data, len, 0)) < 0) { - net_set_error("error sending data"); + if (st->parent.timeout && ret < 0 && + (errno == EAGAIN || errno != EWOULDBLOCK)) { + fd.fd = st->s; + fd.events = POLLOUT; + fd.revents = 0; + + ret = p_poll(&fd, 1, st->parent.timeout); + + if (ret == 1) { + ret = p_send(st->s, data, len, 0); + } else if (ret == 0) { + git_error_set(GIT_ERROR_NET, + "could not write to socket: timed out"); + return GIT_TIMEOUT; + } + } + + if (ret < 0) { + net_set_error("error receiving data from socket"); return -1; } - return written; + return ret; } -static ssize_t socket_read(git_stream *stream, void *data, size_t len) +static ssize_t socket_read( + git_stream *stream, + void *data, + size_t len) { - ssize_t ret; git_socket_stream *st = (git_socket_stream *) stream; + struct pollfd fd; + ssize_t ret; + + ret = p_recv(st->s, data, len, 0); + + if (st->parent.timeout && ret < 0 && + (errno == EAGAIN || errno != EWOULDBLOCK)) { + fd.fd = st->s; + fd.events = POLLIN; + fd.revents = 0; + + ret = p_poll(&fd, 1, st->parent.timeout); - if ((ret = p_recv(st->s, data, len, 0)) < 0) - net_set_error("error receiving socket data"); + if (ret == 1) { + ret = p_recv(st->s, data, len, 0); + } else if (ret == 0) { + git_error_set(GIT_ERROR_NET, + "could not read from socket: timed out"); + return GIT_TIMEOUT; + } + } + + if (ret < 0) { + net_set_error("error receiving data from socket"); + return -1; + } return ret; } @@ -183,6 +347,8 @@ static int default_socket_stream_new( } st->parent.version = GIT_STREAM_VERSION; + st->parent.timeout = git_socket_stream__timeout; + st->parent.connect_timeout = git_socket_stream__connect_timeout; st->parent.connect = socket_connect; st->parent.write = socket_write; st->parent.read = socket_read; @@ -249,7 +415,7 @@ int git_socket_stream_global_init(void) return git_runtime_shutdown_register(socket_stream_global_shutdown); } - + #else #include "stream.h" diff --git a/src/libgit2/streams/socket.h b/src/libgit2/streams/socket.h index 300e70893..73e8de099 100644 --- a/src/libgit2/streams/socket.h +++ b/src/libgit2/streams/socket.h @@ -9,7 +9,7 @@ #include "common.h" -#include "netops.h" +#include "stream.h" typedef struct { git_stream parent; diff --git a/src/libgit2/streams/stransport.c b/src/libgit2/streams/stransport.c index 3f31d2541..d956df84d 100644 --- a/src/libgit2/streams/stransport.c +++ b/src/libgit2/streams/stransport.c @@ -44,6 +44,7 @@ typedef struct { git_stream parent; git_stream *io; int owned; + int error; SSLContextRef ctx; CFDataRef der_data; git_cert_x509 cert_info; @@ -61,7 +62,10 @@ static int stransport_connect(git_stream *stream) return error; ret = SSLHandshake(st->ctx); - if (ret != errSSLServerAuthCompleted) { + + if (ret != errSSLServerAuthCompleted && st->error != 0) + return -1; + else if (ret != errSSLServerAuthCompleted) { git_error_set(GIT_ERROR_SSL, "unexpected return value from ssl handshake %d", (int)ret); return -1; } @@ -147,10 +151,20 @@ static int stransport_set_proxy( */ static OSStatus write_cb(SSLConnectionRef conn, const void *data, size_t *len) { - git_stream *io = (git_stream *) conn; + stransport_stream *st = (stransport_stream *)conn; + git_stream *io = st->io; + OSStatus ret; - if (git_stream__write_full(io, data, *len, 0) < 0) - return -36; /* "ioErr" from MacErrors.h which is not available on iOS */ + st->error = 0; + + ret = git_stream__write_full(io, data, *len, 0); + + if (ret < 0) { + st->error = ret; + return (ret == GIT_TIMEOUT) ? + errSSLNetworkTimeout : + -36 /* ioErr */; + } return noErr; } @@ -164,8 +178,12 @@ static ssize_t stransport_write(git_stream *stream, const char *data, size_t len GIT_UNUSED(flags); data_len = min(len, SSIZE_MAX); - if ((ret = SSLWrite(st->ctx, data, data_len, &processed)) != noErr) + if ((ret = SSLWrite(st->ctx, data, data_len, &processed)) != noErr) { + if (st->error == GIT_TIMEOUT) + return GIT_TIMEOUT; + return stransport_error(ret); + } GIT_ASSERT(processed < SSIZE_MAX); return (ssize_t)processed; @@ -182,18 +200,24 @@ static ssize_t stransport_write(git_stream *stream, const char *data, size_t len */ static OSStatus read_cb(SSLConnectionRef conn, void *data, size_t *len) { - git_stream *io = (git_stream *) conn; + stransport_stream *st = (stransport_stream *)conn; + git_stream *io = st->io; OSStatus error = noErr; size_t off = 0; ssize_t ret; + st->error = 0; + do { ret = git_stream_read(io, data + off, *len - off); + if (ret < 0) { - error = -36; /* "ioErr" from MacErrors.h which is not available on iOS */ + st->error = ret; + error = (ret == GIT_TIMEOUT) ? + errSSLNetworkTimeout : + -36 /* ioErr */; break; - } - if (ret == 0) { + } else if (ret == 0) { error = errSSLClosedGraceful; break; } @@ -207,12 +231,16 @@ static OSStatus read_cb(SSLConnectionRef conn, void *data, size_t *len) static ssize_t stransport_read(git_stream *stream, void *data, size_t len) { - stransport_stream *st = (stransport_stream *) stream; + stransport_stream *st = (stransport_stream *)stream; size_t processed; OSStatus ret; - if ((ret = SSLRead(st->ctx, data, len, &processed)) != noErr) + if ((ret = SSLRead(st->ctx, data, len, &processed)) != noErr) { + if (st->error == GIT_TIMEOUT) + return GIT_TIMEOUT; + return stransport_error(ret); + } return processed; } @@ -269,7 +297,7 @@ static int stransport_wrap( } if ((ret = SSLSetIOFuncs(st->ctx, read_cb, write_cb)) != noErr || - (ret = SSLSetConnection(st->ctx, st->io)) != noErr || + (ret = SSLSetConnection(st->ctx, st)) != noErr || (ret = SSLSetSessionOption(st->ctx, kSSLSessionOptionBreakOnServerAuth, true)) != noErr || (ret = SSLSetProtocolVersionMin(st->ctx, kTLSProtocol1)) != noErr || (ret = SSLSetProtocolVersionMax(st->ctx, kTLSProtocol12)) != noErr || |