Commit 389ce135 authored by nanahira's avatar nanahira

docker warp proxy

parent 3286f056
Pipeline #6224 failed with stages
in 5 minutes and 1 second
stages:
- build
- combine
- deploy
variables:
GIT_DEPTH: "1"
CONTAINER_TEST_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
CONTAINER_TEST_ARM_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-arm
CONTAINER_TEST_X86_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-x86
CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
build-x86:
stage: build
tags:
- docker
script:
- docker build --pull -t $CONTAINER_TEST_X86_IMAGE .
- docker push $CONTAINER_TEST_X86_IMAGE
build-arm:
stage: build
tags:
- docker-arm
script:
- docker build --pull -t $CONTAINER_TEST_ARM_IMAGE .
- docker push $CONTAINER_TEST_ARM_IMAGE
combine:
stage: combine
tags:
- docker
script:
- docker pull $CONTAINER_TEST_X86_IMAGE
- docker pull $CONTAINER_TEST_ARM_IMAGE
- docker manifest create $CONTAINER_TEST_IMAGE --amend $CONTAINER_TEST_X86_IMAGE --amend $CONTAINER_TEST_ARM_IMAGE
- docker manifest push $CONTAINER_TEST_IMAGE
deploy_latest:
stage: deploy
tags:
- docker
script:
- docker pull $CONTAINER_TEST_IMAGE
- docker tag $CONTAINER_TEST_IMAGE $CONTAINER_RELEASE_IMAGE
- docker push $CONTAINER_RELEASE_IMAGE
only:
- master
deploy_tag:
stage: deploy
tags:
- docker
variables:
CONTAINER_TAG_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
script:
- docker pull $CONTAINER_TEST_IMAGE
- docker tag $CONTAINER_TEST_IMAGE $CONTAINER_TAG_IMAGE
- docker push $CONTAINER_TAG_IMAGE
only:
- tags
FROM ubuntu:focal as base
RUN apt update && \
apt -y install build-essential && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
FROM base as builder
WORKDIR /build
COPY ./Makefile ./
COPY ./proxy.c ./
RUN make
FROM base
RUN apt update && \
apt -y install curl gnupg2 libcap2-bin && \
curl https://pkg.cloudflareclient.com/pubkey.gpg | apt-key add - && \
echo 'deb http://pkg.cloudflareclient.com/ buster main' | tee /etc/apt/sources.list.d/cloudflare-client.list && \
apt update && \
apt -y install cloudflare-warp && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
mkdir -p /root/.local/share/warp && \
echo 'yes' > ~/.local/share/warp/accepted-tos.txt
WORKDIR /usr/src/app
COPY --from=builder /build/proxy ./proxy
COPY ./entrypoint.sh ./
ENV LOCAL_PROXY_PORT 10080
ENV FORWARD_PROXY_PORT 8080
EXPOSE $FORWARD_PROXY_PORT
ENTRYPOINT [ "./entrypoint.sh" ]
CMD ["bash", "-c", "./proxy -l $FORWARD_PROXY_PORT -h localhost -p $LOCAL_PROXY_PORT -f"]
CFLAGS=-O2 -std=c99 -Wall
OPTFLAGS=-s -DUSE_SPLICE
LDFLAGS=
all:
gcc $(CFLAGS) $(OPTFLAGS) -o proxy proxy.c $(LDFLAGS)
tomato:
mipsel-uclibc-gcc $(CFLAGS) $(OPTFLAGS) -o proxy proxy.c $(LDFLAGS)
openwrt:
mipsel-linux-uclibc-gcc $(CFLAGS) $(OPTFLAGS) -o proxy proxy.c $(LDFLAGS)
backfire:
mipsel-openwrt-linux-uclibc-gcc $(CFLAGS) $(OPTFLAGS) -o proxy proxy.c $(LDFLAGS)
darwin:
gcc $(CFLAGS) -o proxy proxy.c $(LDFLAGS)
clean:
rm -f proxy proxy.exe
#!/bin/bash
warp-svc &
daemonReady=1
while [[ "$daemonReady" != "0" ]]; do
echo "Waiting for daemon start"
grep "cloudflare-warp/warp_service" /var/lib/cloudflare-warp/cfwarp_service_log.txt
daemonReady=$?
done
echo "Daemon started"
warp-cli --accept-tos register
warp-cli set-mode proxy
warp-cli set-proxy-port $LOCAL_PROXY_PORT
warp-cli connect
"$@"
/*
* Tiny TCP proxy server
*
* Author: Krzysztof Kliś <krzysztof.klis@gmail.com>
* Fixes and improvements: Jérôme Poulin <jeromepoulin@gmail.com>
* IPv6 support: 04/2019 Rafael Ferrari <rafaelbf@hotmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version with the following modification:
*
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce an
* executable, regardless of the license terms of these independent modules,
* and to copy and distribute the resulting executable under terms of your choice,
* provided that you also meet, for each linked independent module, the terms
* and conditions of the license of that module. An independent module is a
* module which is not derived from or based on this library. If you modify this
* library, you may extend this exception to your version of the library, but
* you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <netdb.h>
#include <resolv.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <syslog.h>
#include <unistd.h>
#include <sys/wait.h>
#ifdef USE_SYSTEMD
#include <systemd/sd-daemon.h>
#endif
#define BUF_SIZE 16384
#define READ 0
#define WRITE 1
#define SERVER_SOCKET_ERROR -1
#define SERVER_SETSOCKOPT_ERROR -2
#define SERVER_BIND_ERROR -3
#define SERVER_LISTEN_ERROR -4
#define CLIENT_SOCKET_ERROR -5
#define CLIENT_RESOLVE_ERROR -6
#define CLIENT_CONNECT_ERROR -7
#define CREATE_PIPE_ERROR -8
#define BROKEN_PIPE_ERROR -9
#define SYNTAX_ERROR -10
typedef enum {TRUE = 1, FALSE = 0} bool;
int check_ipversion(char * address);
int create_socket(int port);
void sigchld_handler(int signal);
void sigterm_handler(int signal);
void server_loop();
void handle_client(int client_sock, struct sockaddr_storage client_addr);
void forward_data(int source_sock, int destination_sock);
void forward_data_ext(int source_sock, int destination_sock, char *cmd);
int create_connection();
int parse_options(int argc, char *argv[]);
void plog(int priority, const char *format, ...);
int server_sock, client_sock, remote_sock, remote_port = 0;
int connections_processed = 0;
char *bind_addr, *remote_host, *cmd_in, *cmd_out;
bool foreground = FALSE;
bool use_syslog = FALSE;
#define BACKLOG 20 // how many pending connections queue will hold
/* Program start */
int main(int argc, char *argv[]) {
int local_port;
pid_t pid;
bind_addr = NULL;
local_port = parse_options(argc, argv);
if (local_port < 0) {
printf("Syntax: %s [-b bind_address] -l local_port -h remote_host -p remote_port [-i \"input parser\"] [-o \"output parser\"] [-f (stay in foreground)] [-s (use syslog)]\n", argv[0]);
return local_port;
}
if (use_syslog)
openlog("proxy", LOG_PID, LOG_DAEMON);
if ((server_sock = create_socket(local_port)) < 0) { // start server
plog(LOG_CRIT, "Cannot run server: %m");
return server_sock;
}
signal(SIGCHLD, sigchld_handler); // prevent ended children from becoming zombies
signal(SIGTERM, sigterm_handler); // handle KILL signal
if (foreground) {
server_loop();
} else {
switch(pid = fork()) {
case 0: // deamonized child
server_loop();
break;
case -1: // error
plog(LOG_CRIT, "Cannot daemonize: %m");
return pid;
default: // parent
close(server_sock);
}
}
if (use_syslog)
closelog();
return EXIT_SUCCESS;
}
/* Parse command line options */
int parse_options(int argc, char *argv[]) {
int c, local_port = 0;
while ((c = getopt(argc, argv, "b:l:h:p:i:o:fs")) != -1) {
switch(c) {
case 'l':
local_port = atoi(optarg);
break;
case 'b':
bind_addr = optarg;
break;
case 'h':
remote_host = optarg;
break;
case 'p':
remote_port = atoi(optarg);
break;
case 'i':
cmd_in = optarg;
break;
case 'o':
cmd_out = optarg;
break;
case 'f':
foreground = TRUE;
break;
case 's':
use_syslog = TRUE;
break;
}
}
if (local_port && remote_host && remote_port) {
return local_port;
} else {
return SYNTAX_ERROR;
}
}
int check_ipversion(char * address)
{
/* Check for valid IPv4 or Iv6 string. Returns AF_INET for IPv4, AF_INET6 for IPv6 */
struct in6_addr bindaddr;
if (inet_pton(AF_INET, address, &bindaddr) == 1) {
return AF_INET;
} else {
if (inet_pton(AF_INET6, address, &bindaddr) == 1) {
return AF_INET6;
}
}
return 0;
}
/* Create server socket */
int create_socket(int port) {
int server_sock, optval = 1;
int validfamily=0;
struct addrinfo hints, *res=NULL;
char portstr[12];
memset(&hints, 0x00, sizeof(hints));
server_sock = -1;
hints.ai_flags = AI_NUMERICSERV; /* numeric service number, not resolve */
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
/* prepare to bind on specified numeric address */
if (bind_addr != NULL) {
/* check for numeric IP to specify IPv6 or IPv4 socket */
if (validfamily = check_ipversion(bind_addr)) {
hints.ai_family = validfamily;
hints.ai_flags |= AI_NUMERICHOST; /* bind_addr is a valid numeric ip, skip resolve */
}
} else {
/* if bind_address is NULL, will bind to IPv6 wildcard */
hints.ai_family = AF_INET6; /* Specify IPv6 socket, also allow ipv4 clients */
hints.ai_flags |= AI_PASSIVE; /* Wildcard address */
}
sprintf(portstr, "%d", port);
/* Check if specified socket is valid. Try to resolve address if bind_address is a hostname */
if (getaddrinfo(bind_addr, portstr, &hints, &res) != 0) {
return CLIENT_RESOLVE_ERROR;
}
if ((server_sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) < 0) {
return SERVER_SOCKET_ERROR;
}
if (setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
return SERVER_SETSOCKOPT_ERROR;
}
if (bind(server_sock, res->ai_addr, res->ai_addrlen) == -1) {
close(server_sock);
return SERVER_BIND_ERROR;
}
if (listen(server_sock, BACKLOG) < 0) {
return SERVER_LISTEN_ERROR;
}
if (res != NULL)
freeaddrinfo(res);
return server_sock;
}
/* Send log message to stderr or syslog */
void plog(int priority, const char *format, ...)
{
va_list ap;
va_start(ap, format);
if (use_syslog)
vsyslog(priority, format, ap);
else {
vfprintf(stderr, format, ap);
fprintf(stderr, "\n");
}
va_end(ap);
}
/* Update systemd status with connection count */
void update_connection_count()
{
#ifdef USE_SYSTEMD
sd_notifyf(0, "STATUS=Ready. %d connections processed.\n", connections_processed);
#endif
}
/* Handle finished child process */
void sigchld_handler(int signal) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}
/* Handle term signal */
void sigterm_handler(int signal) {
close(client_sock);
close(server_sock);
exit(0);
}
/* Main server loop */
void server_loop() {
struct sockaddr_storage client_addr;
socklen_t addrlen = sizeof(client_addr);
#ifdef USE_SYSTEMD
sd_notify(0, "READY=1\n");
#endif
while (TRUE) {
update_connection_count();
client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &addrlen);
if (fork() == 0) { // handle client connection in a separate process
close(server_sock);
handle_client(client_sock, client_addr);
exit(0);
} else
connections_processed++;
close(client_sock);
}
}
/* Handle client connection */
void handle_client(int client_sock, struct sockaddr_storage client_addr)
{
if ((remote_sock = create_connection()) < 0) {
plog(LOG_ERR, "Cannot connect to host: %m");
goto cleanup;
}
if (fork() == 0) { // a process forwarding data from client to remote socket
if (cmd_out) {
forward_data_ext(client_sock, remote_sock, cmd_out);
} else {
forward_data(client_sock, remote_sock);
}
exit(0);
}
if (fork() == 0) { // a process forwarding data from remote socket to client
if (cmd_in) {
forward_data_ext(remote_sock, client_sock, cmd_in);
} else {
forward_data(remote_sock, client_sock);
}
exit(0);
}
cleanup:
close(remote_sock);
close(client_sock);
}
/* Forward data between sockets */
void forward_data(int source_sock, int destination_sock) {
ssize_t n;
#ifdef USE_SPLICE
int buf_pipe[2];
if (pipe(buf_pipe) == -1) {
plog(LOG_ERR, "pipe: %m");
exit(CREATE_PIPE_ERROR);
}
while ((n = splice(source_sock, NULL, buf_pipe[WRITE], NULL, SSIZE_MAX, SPLICE_F_NONBLOCK|SPLICE_F_MOVE)) > 0) {
if (splice(buf_pipe[READ], NULL, destination_sock, NULL, SSIZE_MAX, SPLICE_F_MOVE) < 0) {
plog(LOG_ERR, "write: %m");
exit(BROKEN_PIPE_ERROR);
}
}
#else
char buffer[BUF_SIZE];
while ((n = recv(source_sock, buffer, BUF_SIZE, 0)) > 0) { // read data from input socket
send(destination_sock, buffer, n, 0); // send data to output socket
}
#endif
if (n < 0) {
plog(LOG_ERR, "read: %m");
exit(BROKEN_PIPE_ERROR);
}
#ifdef USE_SPLICE
close(buf_pipe[0]);
close(buf_pipe[1]);
#endif
shutdown(destination_sock, SHUT_RDWR); // stop other processes from using socket
close(destination_sock);
shutdown(source_sock, SHUT_RDWR); // stop other processes from using socket
close(source_sock);
}
/* Forward data between sockets through external command */
void forward_data_ext(int source_sock, int destination_sock, char *cmd) {
char buffer[BUF_SIZE];
int n, i, pipe_in[2], pipe_out[2];
if (pipe(pipe_in) < 0 || pipe(pipe_out) < 0) { // create command input and output pipes
plog(LOG_CRIT, "Cannot create pipe: %m");
exit(CREATE_PIPE_ERROR);
}
if (fork() == 0) {
dup2(pipe_in[READ], STDIN_FILENO); // replace standard input with input part of pipe_in
dup2(pipe_out[WRITE], STDOUT_FILENO); // replace standard output with output part of pipe_out
close(pipe_in[WRITE]); // close unused end of pipe_in
close(pipe_out[READ]); // close unused end of pipe_out
n = system(cmd); // execute command
exit(n);
} else {
close(pipe_in[READ]); // no need to read from input pipe here
close(pipe_out[WRITE]); // no need to write to output pipe here
while ((n = recv(source_sock, buffer, BUF_SIZE, 0)) > 0) { // read data from input socket
if (write(pipe_in[WRITE], buffer, n) < 0) { // write data to input pipe of external command
plog(LOG_ERR, "Cannot write to pipe: %m");
exit(BROKEN_PIPE_ERROR);
}
if ((i = read(pipe_out[READ], buffer, BUF_SIZE)) > 0) { // read command output
send(destination_sock, buffer, i, 0); // send data to output socket
}
}
shutdown(destination_sock, SHUT_RDWR); // stop other processes from using socket
close(destination_sock);
shutdown(source_sock, SHUT_RDWR); // stop other processes from using socket
close(source_sock);
}
}
/* Create client connection */
int create_connection() {
struct addrinfo hints, *res=NULL;
int sock;
int validfamily=0;
char portstr[12];
memset(&hints, 0x00, sizeof(hints));
hints.ai_flags = AI_NUMERICSERV; /* numeric service number, not resolve */
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
sprintf(portstr, "%d", remote_port);
/* check for numeric IP to specify IPv6 or IPv4 socket */
if (validfamily = check_ipversion(remote_host)) {
hints.ai_family = validfamily;
hints.ai_flags |= AI_NUMERICHOST; /* remote_host is a valid numeric ip, skip resolve */
}
/* Check if specified host is valid. Try to resolve address if remote_host is a hostname */
if (getaddrinfo(remote_host,portstr , &hints, &res) != 0) {
errno = EFAULT;
return CLIENT_RESOLVE_ERROR;
}
if ((sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) < 0) {
return CLIENT_SOCKET_ERROR;
}
if (connect(sock, res->ai_addr, res->ai_addrlen) < 0) {
return CLIENT_CONNECT_ERROR;
}
if (res != NULL)
freeaddrinfo(res);
return sock;
}
/* vim: set et ts=4 sw=4: */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment