static void auto_event_error(struct bufferevent *buffev, short what, void *_arg) { redsocks_client *client = _arg; int saved_errno = errno; assert(buffev == client->relay || buffev == client->client); redsocks_touch_client(client); redsocks_log_errno(client, LOG_DEBUG, "Errno: %d, State: %d, what: %x", saved_errno, client->state, what); if (buffev == client->relay) { if ( client->state == socks5_pre_detect && what == (EVBUFFER_WRITE|EVBUFFER_TIMEOUT)) { /* In case timeout occurs for connecting relay, we try to connect to target with SOCKS5 proxy. It is possible that the connection to target can be set up a bit longer than the timeout value we set. However, it is still better to make connection via proxy. */ auto_retry(client, 1); return; } if (client->state == socks5_pre_detect && saved_errno == ECONNRESET) if (!auto_retry_or_drop(client)) return; if (client->state == socks5_direct && what == (EVBUFFER_READ|EVBUFFER_ERROR) && saved_errno == ECONNRESET ) { if (!auto_retry_or_drop(client)) return; } } if (what == (EVBUFFER_READ|EVBUFFER_EOF)) { struct bufferevent *antiev; if (buffev == client->relay) antiev = client->client; else antiev = client->relay; redsocks_shutdown(client, buffev, SHUT_RD); if (antiev != NULL && EVBUFFER_LENGTH(antiev->output) == 0) redsocks_shutdown(client, antiev, SHUT_WR); } else { /* myerrno = red_socket_geterrno(buffev); redsocks_log_errno(client, LOG_NOTICE, "%s error, code " event_fmt_str, buffev == client->relay ? "relay" : "client", event_fmt(what)); */ redsocks_drop_client(client); } }
static void auto_event_error(struct bufferevent *buffev, short what, void *_arg) { redsocks_client *client = _arg; autoproxy_client * aclient = (void*)(client + 1) + client->instance->relay_ss->payload_len; int saved_errno = errno; assert(buffev == client->relay || buffev == client->client); redsocks_touch_client(client); redsocks_log_errno(client, LOG_DEBUG, "%s errno(%d), State: %d, what: " event_fmt_str, buffev == client->client?"client":"relay", saved_errno, aclient->state, event_fmt(what)); if (buffev == client->relay) { if ( aclient->state == AUTOPROXY_NEW && what == (EVBUFFER_WRITE|EVBUFFER_TIMEOUT)) { on_connection_blocked(client); /* In case timeout occurs while connecting relay, we try to connect to target via SOCKS5 proxy. It is possible that the connection to target can be set up a bit longer than the timeout value we set. However, it is still better to make connection via proxy. */ auto_retry(client, 1); return; } if (aclient->state == AUTOPROXY_NEW && saved_errno == ECONNRESET) if (!auto_retry_or_drop(client)) return; if (aclient->state == AUTOPROXY_CONNECTED && what == (EVBUFFER_READ|EVBUFFER_ERROR) && saved_errno == ECONNRESET ) { if (!auto_retry_or_drop(client)) return; } } if (what == (EVBUFFER_READ|EVBUFFER_EOF)) { struct bufferevent *antiev; if (buffev == client->relay) antiev = client->client; else antiev = client->relay; redsocks_shutdown(client, buffev, SHUT_RD); if (antiev != NULL && EVBUFFER_LENGTH(antiev->output) == 0) redsocks_shutdown(client, antiev, SHUT_WR); } else { redsocks_drop_client(client); } }
static void direct_relay_clientwritecb(struct bufferevent *to, void *_client) { redsocks_client *client = _client; autoproxy_client * aclient = (void*)(client + 1) + client->instance->relay_ss->payload_len; struct bufferevent * from = client->relay; redsocks_touch_client(client); if (EVBUFFER_LENGTH(from->input) == 0 && (client->relay_evshut & EV_READ)) { redsocks_shutdown(client, to, SHUT_WR); return; } if (aclient->state == AUTOPROXY_CONNECTED) { if (!aclient->data_recv) aclient->data_recv = EVBUFFER_LENGTH(from->input); } if (EVBUFFER_LENGTH(to->output) < to->wm_write.high) { if (bufferevent_write_buffer(to, from->input) == -1) redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); if (bufferevent_enable(from, EV_READ) == -1) redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); } }
static void direct_relay_clientwritecb(struct bufferevent *to, void *_client) { redsocks_client *client = _client; socks5_client *socks5 = (void*)(client + 1); struct bufferevent * from = client->relay; redsocks_touch_client(client); if (EVBUFFER_LENGTH(from->input) == 0 && (client->relay_evshut & EV_READ)) { redsocks_shutdown(client, to, SHUT_WR); return; } if (client->state == socks5_direct) { if (!socks5->got_data) socks5->got_data = EVBUFFER_LENGTH(from->input); } if (EVBUFFER_LENGTH(to->output) < to->wm_write.high) { if (bufferevent_write_buffer(to, from->input) == -1) redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); if (bufferevent_enable(from, EV_READ) == -1) redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); } }
static void direct_relay_relaywritecb(struct bufferevent *to, void *_client) { redsocks_client *client = _client; autoproxy_client * aclient = (void*)(client + 1) + client->instance->relay_ss->payload_len; struct bufferevent * from = client->client; redsocks_touch_client(client); if (EVBUFFER_LENGTH(from->input) == 0 && (client->client_evshut & EV_READ)) { redsocks_shutdown(client, to, SHUT_WR); return; } else if (aclient->state == AUTOPROXY_CONNECTED ) { /* Not send or receive data. */ if (!aclient->data_sent && !aclient->data_recv) { /* Ensure we have data to send */ if (EVBUFFER_LENGTH(from->input)) { /* copy data from input to output of relay */ aclient->data_sent = copy_evbuffer (to, from, 0); redsocks_log_error(client, LOG_DEBUG, "not sent, not got %d", EVBUFFER_LENGTH(from->input)); } } /* * Relay reaceived data before writing to relay. */ else if (!aclient->data_sent && aclient->data_recv) { redsocks_log_error(client, LOG_DEBUG, "not sent, got"); aclient->data_sent = copy_evbuffer(to, from, 0); } /* aclient->state = AUTOPROXY_CONFIRMED; */ /* We have writen data to relay, but got nothing until we are requested to * write to it again. */ else if (aclient->data_sent && !aclient->data_recv) { /* No response from relay and no CONNECTION RESET, Send more data. */ redsocks_log_error(client, LOG_DEBUG, "sent, not got in:%d out:%d high:%d sent:%d", evbuffer_get_length(from->input), evbuffer_get_length(to->output), to->wm_write.high, aclient->data_sent ); /* Write more data util input buffer is full */ if (EVBUFFER_LENGTH(from->input)- aclient->data_sent > 0) /* we have more data waiting to be sent */ { aclient->data_sent += copy_evbuffer(to, from, aclient->data_sent); } else if (EVBUFFER_LENGTH(to->output) < aclient->data_sent /* data is sent out, more or less */ && EVBUFFER_LENGTH(from->input) == from->wm_read.high /* do not confirm unless read buffer is full */ && EVBUFFER_LENGTH(from->input) == aclient->data_sent /* all data in read buffer is sent */ ) { evbuffer_drain(from->input, aclient->data_sent); aclient->data_sent = 0; aclient->state = AUTOPROXY_CONFIRMED; on_connection_confirmed(client); } } /* We sent data to and got data from relay. */ else if (aclient->data_sent && aclient->data_recv) { /* No CONNECTION RESET error occur after sending data, good. */ aclient->state = AUTOPROXY_CONFIRMED; on_connection_confirmed(client); redsocks_log_error(client, LOG_DEBUG, "sent, got %d ", aclient->data_recv); if (evbuffer_get_length(from->input)) { evbuffer_drain(from->input, aclient->data_sent); aclient->data_sent = 0; } } } if (aclient->state == AUTOPROXY_CONFIRMED) { if (EVBUFFER_LENGTH(to->output) < to->wm_write.high) { if (bufferevent_write_buffer(to, from->input) == -1) redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); if (bufferevent_enable(from, EV_READ) == -1) redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); } } }
static void httpc_read_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; assert(client->relay == buffev); assert(client->state == httpc_request_sent || client->state == httpc_reply_came); redsocks_touch_client(client); // evbuffer_add() triggers callbacks, so we can't write to client->client // till we know that we're going to ONFAIL_FORWARD_HTTP_ERR. // And the decision is made when all the headers are processed. struct evbuffer* tee = NULL; const bool do_errtee = client->instance->config.on_proxy_fail == ONFAIL_FORWARD_HTTP_ERR; if (client->state == httpc_request_sent) { size_t len = evbuffer_get_length(buffev->input); char *line = redsocks_evbuffer_readline(buffev->input); if (line) { unsigned int code; if (sscanf(line, "HTTP/%*u.%*u %u", &code) == 1) { // 1 == one _assigned_ match if (code == 407) { // auth failed http_auth *auth = red_http_auth(client->instance); if (auth->last_auth_query != NULL && auth->last_auth_count == 1) { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy auth failed: %s", line); client->state = httpc_no_way; } else if (client->instance->config.login == NULL || client->instance->config.password == NULL) { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy auth required, but no login/password configured: %s", line); client->state = httpc_no_way; } else { if (do_errtee) tee = evbuffer_new(); char *auth_request = http_auth_request_header(buffev->input, tee); if (!auth_request) { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy auth required, but no <%s> header found: %s", auth_request_header, line); client->state = httpc_no_way; } else { free(line); if (tee) evbuffer_free(tee); free(auth->last_auth_query); char *ptr = auth_request; ptr += strlen(auth_request_header); while (isspace(*ptr)) ptr++; size_t last_auth_query_len = strlen(ptr) + 1; auth->last_auth_query = calloc(last_auth_query_len, 1); memcpy(auth->last_auth_query, ptr, last_auth_query_len); auth->last_auth_count = 0; free(auth_request); if (bufferevent_disable(client->relay, EV_WRITE)) { redsocks_log_errno(client, LOG_ERR, "bufferevent_disable"); return; } /* close relay tunnel */ redsocks_bufferevent_free(client->relay); /* set to initial state*/ client->state = httpc_new; /* and reconnect */ redsocks_connect_relay(client); return; } } } else if (200 <= code && code <= 299) { client->state = httpc_reply_came; } else { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy error: %s", line); client->state = httpc_no_way; } } else { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy bad firstline: %s", line); client->state = httpc_no_way; } if (do_errtee && client->state == httpc_no_way) { if (bufferevent_write(client->client, line, strlen(line)) != 0 || bufferevent_write(client->client, "\r\n", 2) != 0) { redsocks_log_errno(client, LOG_NOTICE, "bufferevent_write"); goto fail; } } free(line); } else if (len >= HTTP_HEAD_WM_HIGH) { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy reply is too long, %zu bytes", len); client->state = httpc_no_way; } } if (do_errtee && client->state == httpc_no_way) { if (tee) { if (bufferevent_write_buffer(client->client, tee) != 0) { redsocks_log_errno(client, LOG_NOTICE, "bufferevent_write_buffer"); goto fail; } } redsocks_shutdown(client, client->client, SHUT_RD); const size_t avail = evbuffer_get_length(client->client->input); if (avail) { if (evbuffer_drain(client->client->input, avail) != 0) { redsocks_log_errno(client, LOG_NOTICE, "evbuffer_drain"); goto fail; } } redsocks_shutdown(client, client->relay, SHUT_WR); client->state = httpc_headers_skipped; } fail: if (tee) { evbuffer_free(tee); } if (client->state == httpc_no_way) { redsocks_drop_client(client); return; } while (client->state == httpc_reply_came) { char *line = redsocks_evbuffer_readline(buffev->input); if (line) { if (strlen(line) == 0) { client->state = httpc_headers_skipped; } free(line); } else { break; } } if (client->state == httpc_headers_skipped) { redsocks_start_relay(client); } }
static void direct_relay_relaywritecb(struct bufferevent *to, void *_client) { redsocks_client *client = _client; socks5_client *socks5 = (void*)(client + 1); struct bufferevent * from = client->client; redsocks_touch_client(client); if (EVBUFFER_LENGTH(from->input) == 0 && (client->client_evshut & EV_READ)) { redsocks_shutdown(client, to, SHUT_WR); return; } else if (client->state == socks5_direct ) { /* Not send or receive data. */ if (!socks5->data_sent && !socks5->got_data) { /* Ensure we have data to send */ if (EVBUFFER_LENGTH(from->input)) { /* copy data from input to output of relay */ socks5->data_sent = copy_evbuffer (to, from); redsocks_log_error(client, LOG_DEBUG, "not sent, not got %d", EVBUFFER_LENGTH(from->input)); } } /* * Relay reaceived data before writing to relay. */ else if (!socks5->data_sent && socks5->got_data) { redsocks_log_error(client, LOG_DEBUG, "not sent, got"); socks5->data_sent = copy_evbuffer(to, from); } /* client->state = socks5_direct_confirmed; */ /* We have writen data to relay, but got nothing until we are requested to * write to it again. */ else if (socks5->data_sent && !socks5->got_data) { /* No response from relay and no CONNECTION RESET, Send more data. */ redsocks_log_error(client, LOG_DEBUG, "sent, not got in:%d out:%d high:%d sent:%d", evbuffer_get_length(from->input), evbuffer_get_length(to->output), to->wm_write.high, socks5->data_sent ); /* TODO: Write more data? */ if (EVBUFFER_LENGTH(to->output) < socks5->data_sent /* data is sent out, more or less */ && EVBUFFER_LENGTH(from->input) == from->wm_read.high /* read buffer is full */ && EVBUFFER_LENGTH(from->input) == socks5->data_sent /* all data in read buffer is sent */ ) { evbuffer_drain(from->input, socks5->data_sent); socks5->data_sent = 0; client->state = socks5_direct_confirmed; } } /* We sent data to and got data from relay. */ else if (socks5->data_sent && socks5->got_data) { /* No CONNECTION RESET error occur after sending data, good. */ client->state = socks5_direct_confirmed; redsocks_log_error(client, LOG_DEBUG, "sent, got %d ", socks5->got_data); if (evbuffer_get_length(from->input)) { evbuffer_drain(from->input, socks5->data_sent); socks5->data_sent = 0; } } } if (client->state == socks5_direct_confirmed) { if (EVBUFFER_LENGTH(to->output) < to->wm_write.high) { if (bufferevent_write_buffer(to, from->input) == -1) redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); if (bufferevent_enable(from, EV_READ) == -1) redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); } } }