0
0
mirror of https://github.com/darkk/redsocks.git synced 2025-08-24 18:58:36 +00:00
redsocks/utils.c
2018-01-26 23:57:43 +03:00

307 lines
6.9 KiB
C

/* redsocks - transparent TCP-to-proxy redirector
* Copyright (C) 2007-2018 Leonid Evdokimov <leon@darkk.net.ru>
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.h"
#include "base.h"
#include "utils.h"
#include "redsocks.h" // for redsocks_close
#include "libc-compat.h"
int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inaddr, struct sockaddr_in *toaddr)
{
socklen_t addrlen = sizeof(*inaddr);
ssize_t pktlen;
struct msghdr msg;
struct iovec io;
char control[1024];
memset(&msg, 0, sizeof(msg));
msg.msg_name = inaddr;
msg.msg_namelen = sizeof(*inaddr);
msg.msg_iov = &io;
msg.msg_iovlen = 1;
msg.msg_control = control;
msg.msg_controllen = sizeof(control);
io.iov_base = buf;
io.iov_len = buflen;
pktlen = recvmsg(fd, &msg, 0);
if (pktlen == -1) {
log_errno(LOG_WARNING, "recvfrom");
return -1;
}
if (toaddr) {
memset(toaddr, 0, sizeof(*toaddr));
for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (
cmsg->cmsg_level == SOL_IP &&
cmsg->cmsg_type == IP_ORIGDSTADDR &&
cmsg->cmsg_len >= CMSG_LEN(sizeof(*toaddr))
) {
struct sockaddr_in* cmsgaddr = (struct sockaddr_in*)CMSG_DATA(cmsg);
char buf[RED_INET_ADDRSTRLEN];
log_error(LOG_DEBUG, "IP_ORIGDSTADDR: %s", red_inet_ntop(cmsgaddr, buf, sizeof(buf)));
memcpy(toaddr, cmsgaddr, sizeof(*toaddr));
}
else {
log_error(LOG_WARNING, "unexepcted cmsg (level,type) = (%d,%d)",
cmsg->cmsg_level, cmsg->cmsg_type);
}
}
if (toaddr->sin_family != AF_INET) {
log_error(LOG_WARNING, "(SOL_IP, IP_ORIGDSTADDR) not found");
return -1;
}
}
if (addrlen != sizeof(*inaddr)) {
log_error(LOG_WARNING, "unexpected address length %u instead of %zu", addrlen, sizeof(*inaddr));
return -1;
}
if (pktlen >= buflen) {
char buf[RED_INET_ADDRSTRLEN];
log_error(LOG_WARNING, "wow! Truncated udp packet of size %zd from %s! impossible! dropping it...",
pktlen, red_inet_ntop(inaddr, buf, sizeof(buf)));
return -1;
}
return pktlen;
}
uint32_t red_randui32()
{
uint32_t ret;
evutil_secure_rng_get_bytes(&ret, sizeof(ret));
return ret;
}
time_t redsocks_time(time_t *t)
{
time_t retval;
retval = time(t);
if (retval == ((time_t) -1))
log_errno(LOG_WARNING, "time");
return retval;
}
int redsocks_gettimeofday(struct timeval *tv)
{
int retval = gettimeofday(tv, NULL);
if (retval != 0)
log_errno(LOG_WARNING, "gettimeofday");
return retval;
}
char *redsocks_evbuffer_readline(struct evbuffer *buf)
{
#if _EVENT_NUMERIC_VERSION >= 0x02000000
return evbuffer_readln(buf, NULL, EVBUFFER_EOL_CRLF);
#else
return evbuffer_readline(buf);
#endif
}
int red_socket_client(int type)
{
int fd = -1;
int error;
fd = socket(AF_INET, type, 0);
if (fd == -1) {
log_errno(LOG_ERR, "socket");
goto fail;
}
error = fcntl_nonblock(fd);
if (error) {
log_errno(LOG_ERR, "fcntl");
goto fail;
}
if (type == SOCK_STREAM) {
if (apply_tcp_keepalive(fd))
goto fail;
}
return fd;
fail:
if (fd != -1)
redsocks_close(fd);
return -1;
}
int red_socket_server(int type, struct sockaddr_in *bindaddr)
{
int on = 1;
int error;
int fd = red_socket_client(type);
if (fd == -1)
goto fail;
error = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (error) {
log_errno(LOG_ERR, "setsockopt");
goto fail;
}
error = bind(fd, (struct sockaddr*)bindaddr, sizeof(*bindaddr));
if (error) {
log_errno(LOG_ERR, "bind");
goto fail;
}
return fd;
fail:
if (fd != -1)
redsocks_close(fd);
return -1;
}
struct bufferevent* red_connect_relay(struct sockaddr_in *addr, evbuffercb writecb, everrorcb errorcb, void *cbarg)
{
struct bufferevent *retval = NULL;
int relay_fd = -1;
int error;
relay_fd = red_socket_client(SOCK_STREAM);
error = connect(relay_fd, (struct sockaddr*)addr, sizeof(*addr));
if (error && errno != EINPROGRESS) {
log_errno(LOG_NOTICE, "connect");
goto fail;
}
retval = bufferevent_new(relay_fd, NULL, writecb, errorcb, cbarg);
if (!retval) {
log_errno(LOG_ERR, "bufferevent_new");
goto fail;
}
relay_fd = -1;
error = bufferevent_enable(retval, EV_WRITE); // we wait for connection...
if (error) {
log_errno(LOG_ERR, "bufferevent_enable");
goto fail;
}
return retval;
fail:
if (relay_fd != -1)
redsocks_close(relay_fd);
if (retval)
redsocks_bufferevent_free(retval);
return NULL;
}
int red_socket_geterrno(struct bufferevent *buffev)
{
int error;
int pseudo_errno;
socklen_t optlen = sizeof(pseudo_errno);
assert(event_get_fd(&buffev->ev_read) == event_get_fd(&buffev->ev_write));
error = getsockopt(event_get_fd(&buffev->ev_read), SOL_SOCKET, SO_ERROR, &pseudo_errno, &optlen);
if (error) {
log_errno(LOG_ERR, "getsockopt");
return -1;
}
return pseudo_errno;
}
/** simple fcntl(2) wrapper, provides errno and all logging to caller
* I have to use it in event-driven code because of accept(2) (see NOTES)
* and connect(2) (see ERRORS about EINPROGRESS)
*/
int fcntl_nonblock(int fd)
{
int error;
int flags;
flags = fcntl(fd, F_GETFL);
if (flags == -1)
return -1;
error = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
if (error)
return -1;
return 0;
}
int red_is_socket_connected_ok(struct bufferevent *buffev)
{
int pseudo_errno = red_socket_geterrno(buffev);
if (pseudo_errno == -1) {
return 0;
}
else if (pseudo_errno) {
errno = pseudo_errno;
log_errno(LOG_NOTICE, "connect");
return 0;
}
else {
return 1;
}
}
char *red_inet_ntop(const struct sockaddr_in* sa, char* buffer, size_t buffer_size)
{
const char *retval = 0;
size_t len = 0;
uint16_t port;
const char placeholder[] = "???:???";
assert(buffer_size >= RED_INET_ADDRSTRLEN);
memset(buffer, 0, buffer_size);
if (sa->sin_family == AF_INET) {
retval = inet_ntop(AF_INET, &sa->sin_addr, buffer, buffer_size);
port = ((struct sockaddr_in*)sa)->sin_port;
}
else if (sa->sin_family == AF_INET6) {
retval = inet_ntop(AF_INET6, &((const struct sockaddr_in6*)sa)->sin6_addr, buffer, buffer_size);
port = ((struct sockaddr_in6*)sa)->sin6_port;
}
if (retval) {
assert(retval == buffer);
len = strlen(retval);
snprintf(buffer + len, buffer_size - len, ":%d", ntohs(port));
}
else {
strcpy(buffer, placeholder);
}
return buffer;
}
/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */