Imagen acoplable de 5,94 metros con Telegram MTProxy

Como todos ya han escuchado , a fines de mayo Telegram lanzó el servidor oficial MTProto Proxy (también conocido como MTProxy), escrito en el siguiente idioma . En 2018, no hay mucho sin Docker, porque está acompañado por la misma forma "oficial" en formato de configuración cero. Todo estaría bien, pero tres "peros" estropearon un poco la impresión del lanzamiento: la imagen pesa> 130 Mb (hay un Debian bastante regordete, no el Alpine habitual), debido a la "configuración cero" no siempre está convenientemente configurado (solo por parámetros ambientales) y los chicos olvidaron la campaña, diseñaron el Dockerfile.



TL; DR Haremos una imagen de acoplador oficial prácticamente 1 en 1 con base alpina de 5,94 MB de tamaño y la pondremos aquí (y el Dockerfile aquí ); En el camino, descubriremos cómo a veces puedes hacer amigos con el software Alpine usando pinzas y un archivo, y jugaremos un poco de tamaño, exclusivamente por diversión.

Contenido de imagen


Una vez más, ¿por qué tanto alboroto? Veamos qué representa la imagen oficial con el comando de historial :

$ docker history --no-trunc --format "{{.Size}}\t{{.CreatedBy}}" telegrammessenger/proxy 

Las capas se leen de abajo hacia arriba, respectivamente:



La más gruesa es la Debian Jessie, de la cual se hereda la imagen original, primero tenemos que deshacernos de ella (alpine: 3.6, en comparación, pesa 3.97MB); seguido de las dimensiones son rizos y certificados frescos. Para comprender qué significan los otros dos archivos y el directorio, veremos el interior usando el comando de ejecución , reemplazando CMD con bash (esto le permitirá caminar alrededor de la imagen lanzada, conocerse más de cerca, ejecutar ciertos fragmentos, copiar algo útil):

 $ docker run -it --rm telegrammessenger/proxy /bin/bash 

Ahora podemos restaurar fácilmente la imagen del incidente, algo así como el Dockerfile oficial perdido parecía:

 FROM debian:jessie-20180312 RUN set -eux \ && apt-get update \ && apt-get install -y --no-install-recommends curl ca-certificates \ && rm -rf /var/lib/apt/lists/* COPY ./mtproto-proxy /usr/local/bin RUN mkdir /data COPY ./secret/ /etc/telegram/ COPY ./run.sh /run.sh CMD ["/bin/sh", "-c", "/bin/bash /run.sh"] 

Donde mtproto-proxy es un servidor compilado, la carpeta secreta contiene solo el archivo hola-exploradores-cómo-estás-haciendo con la clave de cifrado AES (ver los comandos del servidor, por cierto, hay una recomendación oficial para obtener la clave a través de la API, pero decirlo así) probablemente para evitar la situación cuando la API también está bloqueada), y run.sh hace todos los preparativos para iniciar el proxy.

El contenido del run.sh original
 #!/bin/bash if [ ! -z "$DEBUG" ]; then set -x; fi mkdir /data 2>/dev/null >/dev/null RANDOM=$(printf "%d" "0x$(head -c4 /dev/urandom | od -t x1 -An | tr -d ' ')") if [ -z "$WORKERS" ]; then WORKERS=2 fi echo "####" echo "#### Telegram Proxy" echo "####" echo SECRET_CMD="" if [ ! -z "$SECRET" ]; then echo "[+] Using the explicitly passed secret: '$SECRET'." elif [ -f /data/secret ]; then SECRET="$(cat /data/secret)" echo "[+] Using the secret in /data/secret: '$SECRET'." else if [[ ! -z "$SECRET_COUNT" ]]; then if [[ ! ( "$SECRET_COUNT" -ge 1 && "$SECRET_COUNT" -le 16 ) ]]; then echo "[F] Can generate between 1 and 16 secrets." exit 5 fi else SECRET_COUNT="1" fi echo "[+] No secret passed. Will generate $SECRET_COUNT random ones." SECRET="$(dd if=/dev/urandom bs=16 count=1 2>&1 | od -tx1 | head -n1 | tail -c +9 | tr -d ' ')" for pass in $(seq 2 $SECRET_COUNT); do SECRET="$SECRET,$(dd if=/dev/urandom bs=16 count=1 2>&1 | od -tx1 | head -n1 | tail -c +9 | tr -d ' ')" done fi if echo "$SECRET" | grep -qE '^[0-9a-fA-F]{32}(,[0-9a-fA-F]{32}){,15}$'; then SECRET="$(echo "$SECRET" | tr '[:upper:]' '[:lower:]')" SECRET_CMD="-S $(echo "$SECRET" | sed 's/,/ -S /g')" echo -- "$SECRET_CMD" > /data/secret_cmd echo "$SECRET" > /data/secret else echo '[F] Bad secret format: should be 32 hex chars (for 16 bytes) for every secret; secrets should be comma-separated.' exit 1 fi if [ ! -z "$TAG" ]; then echo "[+] Using the explicitly passed tag: '$TAG'." fi TAG_CMD="" if [[ ! -z "$TAG" ]]; then if echo "$TAG" | grep -qE '^[0-9a-fA-F]{32}$'; then TAG="$(echo "$TAG" | tr '[:upper:]' '[:lower:]')" TAG_CMD="-P $TAG" else echo '[!] Bad tag format: should be 32 hex chars (for 16 bytes).' echo '[!] Continuing.' fi fi curl -s https://core.telegram.org/getProxyConfig -o /etc/telegram/backend.conf || { echo '[F] Cannot download proxy configuration from Telegram servers.' exit 2 } CONFIG=/etc/telegram/backend.conf IP="$(curl -s -4 "https://digitalresistance.dog/myIp")" INTERNAL_IP="$(ip -4 route get 8.8.8.8 | grep '^8\.8\.8\.8\s' | grep -Po 'src\s+\d+\.\d+\.\d+\.\d+' | awk '{print $2}')" if [[ -z "$IP" ]]; then echo "[F] Cannot determine external IP address." exit 3 fi if [[ -z "$INTERNAL_IP" ]]; then echo "[F] Cannot determine internal IP address." exit 4 fi echo "[*] Final configuration:" I=1 echo "$SECRET" | tr ',' '\n' | while read S; do echo "[*] Secret $I: $S" echo "[*] tg:// link for secret $I auto configuration: tg://proxy?server=${IP}&port=443&secret=${S}" echo "[*] t.me link for secret $I: https://t.me/proxy?server=${IP}&port=443&secret=${S}" I=$(($I+1)) done [ ! -z "$TAG" ] && echo "[*] Tag: $TAG" || echo "[*] Tag: no tag" echo "[*] External IP: $IP" echo "[*] Make sure to fix the links in case you run the proxy on a different port." echo echo '[+] Starting proxy...' sleep 1 exec /usr/local/bin/mtproto-proxy -p 2398 -H 443 -M "$WORKERS" -C 60000 --aes-pwd /etc/telegram/hello-explorers-how-are-you-doing -u root $CONFIG --allow-skip-dh --nat-info "$INTERNAL_IP:$IP" $SECRET_CMD $TAG_CMD 

Asamblea


Bajo CentOS 7 MTProxy en Habré ya recopilado , intentaremos recopilar una imagen bajo Alpine y guardar megabytes, comerciales, 130 en la imagen de acoplador resultante.

Una característica distintiva de Alpine Linux es el uso de musl en lugar de glibc. Ambas son bibliotecas estándar de C. Musl es pequeño (no tiene una quinta parte del "estándar"), pero el volumen y el rendimiento (al menos prometido) deciden cuando se trata de Docker. Y poner glibc en Alpine no es racialmente correcto, el tío Jakub Jirutka no lo entenderá , por ejemplo.

También construiremos en Docker para aislar dependencias y ganar libertad para la experimentación, así que cree un nuevo Dockerfile:

 FROM alpine:3.6 RUN apk add --no-cache git make gcc musl-dev linux-headers openssl-dev RUN git clone --single-branch --depth 1 https://github.com/TelegramMessenger/MTProxy.git /mtproxy/sources RUN cd /mtproxy/sources \ && make -j$(getconf _NPROCESSORS_ONLN) 

De las dependencias, git será útil (y no solo para clonar el repositorio oficial, el archivo make adjuntará el sha commit a la versión), make, gcc y archivos de encabezado (el conjunto mínimo se obtuvo empíricamente). Solo clonamos la rama maestra con una profundidad de 1 commit (definitivamente no necesitamos historial). Bueno, intentemos utilizar todos los recursos del host al compilar con el modificador -j. Lo partí deliberadamente en una mayor cantidad de capas para obtener un almacenamiento en caché conveniente durante la reconstrucción (generalmente hay muchas).

Ejecutaremos el comando de compilación (estando en el directorio con el Dockerfile):

 $ docker build -t mtproxy:test . 

Y aquí está el primer problema:

 In file included from ./net/net-connections.h:34:0, from mtproto/mtproto-config.c:44: ./jobs/jobs.h:234:23: error: field 'rand_data' has incomplete type struct drand48_data rand_data; ^~~~~~~~~ 

En realidad, todos los posteriores estarán conectados con él. Primero, para aquellos que no están familiarizados con ellos mismos, el compilador realmente jura por la falta de una declaración de la estructura drand48_data. En segundo lugar, los desarrolladores de musl puntuaron en funciones aleatorias seguras para subprocesos (con el postfix _r) y en todo lo relacionado con ellas (incluidas las estructuras). Y los desarrolladores de Telegram, a su vez, no se molestaron en compilar sistemas donde random_r y sus contrapartes no están implementados (en muchas bibliotecas del sistema operativo puede ver el indicador HAVE_RANDOM_R o su presencia o ausencia arbitraria de este grupo de funciones generalmente se tiene en cuenta en el autoconfigurador).

Bueno, ¿ahora definitivamente instalaremos glibc? No! Copiaremos lo que necesitamos de glibc al mínimo y haremos un parche para las fuentes MTProxy.

Además de los problemas con random_r, tenemos un problema con la función de retroceso (execinfo.h), que se usa para generar el retroceso de la pila en caso de una excepción: podría intentar reemplazarlo con la implementación de libunwind, pero no vale la pena, porque la llamada se enmarcó al verificar __GLIBC__.

Contenido del parche Random_compat.patch
 Index: jobs/jobs.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- jobs/jobs.h (revision cdd348294d86e74442bb29bd6767e48321259bec) +++ jobs/jobs.h (date 1527996954000) @@ -28,6 +28,8 @@ #include "net/net-msg.h" #include "net/net-timers.h" +#include "common/randr_compat.h" + #define __joblocked #define __jobref Index: common/server-functions.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- common/server-functions.c (revision cdd348294d86e74442bb29bd6767e48321259bec) +++ common/server-functions.c (date 1527998325000) @@ -35,7 +35,9 @@ #include <arpa/inet.h> #include <assert.h> #include <errno.h> +#ifdef __GLIBC__ #include <execinfo.h> +#endif #include <fcntl.h> #include <getopt.h> #include <grp.h> @@ -168,6 +170,7 @@ } void print_backtrace (void) { +#ifdef __GLIBC__ void *buffer[64]; int nptrs = backtrace (buffer, 64); kwrite (2, "\n------- Stack Backtrace -------\n", 33); @@ -178,6 +181,7 @@ kwrite (2, s, strlen (s)); kwrite (2, "\n", 1); } +#endif } pthread_t debug_main_pthread_id; Index: common/randr_compat.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- common/randr_compat.h (date 1527998264000) +++ common/randr_compat.h (date 1527998264000) @@ -0,0 +1,72 @@ +/* + The GNU C Library is free software. See the file COPYING.LIB for copying + conditions, and LICENSES for notices about a few contributions that require + these additional notices to be distributed. License copyright years may be + listed using range notation, eg, 2000-2011, indicating that every year in + the range, inclusive, is a copyrightable year that would otherwise be listed + individually. +*/ + +#pragma once + +#include <endian.h> +#include <pthread.h> + +struct drand48_data { + unsigned short int __x[3]; /* Current state. */ + unsigned short int __old_x[3]; /* Old state. */ + unsigned short int __c; /* Additive const. in congruential formula. */ + unsigned short int __init; /* Flag for initializing. */ + unsigned long long int __a; /* Factor in congruential formula. */ +}; + +union ieee754_double +{ + double d; + + /* This is the IEEE 754 double-precision format. */ + struct + { +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned int negative:1; + unsigned int exponent:11; + /* Together these comprise the mantissa. */ + unsigned int mantissa0:20; + unsigned int mantissa1:32; +#endif /* Big endian. */ +#if __BYTE_ORDER == __LITTLE_ENDIAN + /* Together these comprise the mantissa. */ + unsigned int mantissa1:32; + unsigned int mantissa0:20; + unsigned int exponent:11; + unsigned int negative:1; +#endif /* Little endian. */ + } ieee; + + /* This format makes it easier to see if a NaN is a signalling NaN. */ + struct + { +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned int negative:1; + unsigned int exponent:11; + unsigned int quiet_nan:1; + /* Together these comprise the mantissa. */ + unsigned int mantissa0:19; + unsigned int mantissa1:32; +#else + /* Together these comprise the mantissa. */ + unsigned int mantissa1:32; + unsigned int mantissa0:19; + unsigned int quiet_nan:1; + unsigned int exponent:11; + unsigned int negative:1; +#endif + } ieee_nan; +}; + +#define IEEE754_DOUBLE_BIAS 0x3ff /* Added to exponent. */ + +int drand48_r (struct drand48_data *buffer, double *result); +int lrand48_r (struct drand48_data *buffer, long int *result); +int mrand48_r (struct drand48_data *buffer, long int *result); +int srand48_r (long int seedval, struct drand48_data *buffer); \ No newline at end of file Index: Makefile IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- Makefile (revision cdd348294d86e74442bb29bd6767e48321259bec) +++ Makefile (date 1527998107000) @@ -40,6 +40,7 @@ DEPENDENCE_NORM := $(subst ${OBJ}/,${DEP}/,$(patsubst %.o,%.d,${OBJECTS})) LIB_OBJS_NORMAL := \ + ${OBJ}/common/randr_compat.o \ ${OBJ}/common/crc32c.o \ ${OBJ}/common/pid.o \ ${OBJ}/common/sha1.o \ Index: common/randr_compat.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- common/randr_compat.c (date 1527998213000) +++ common/randr_compat.c (date 1527998213000) @@ -0,0 +1,120 @@ +/* + The GNU C Library is free software. See the file COPYING.LIB for copying + conditions, and LICENSES for notices about a few contributions that require + these additional notices to be distributed. License copyright years may be + listed using range notation, eg, 2000-2011, indicating that every year in + the range, inclusive, is a copyrightable year that would otherwise be listed + individually. +*/ + +#include <stddef.h> +#include "common/randr_compat.h" + +int __drand48_iterate (unsigned short int xsubi[3], struct drand48_data *buffer) { + uint64_t X; + uint64_t result; + + /* Initialize buffer, if not yet done. */ + if (!buffer->__init == 0) + { + buffer->__a = 0x5deece66dull; + buffer->__c = 0xb; + buffer->__init = 1; + } + + /* Do the real work. We choose a data type which contains at least + 48 bits. Because we compute the modulus it does not care how + many bits really are computed. */ + + X = (uint64_t) xsubi[2] << 32 | (uint32_t) xsubi[1] << 16 | xsubi[0]; + + result = X * buffer->__a + buffer->__c; + + xsubi[0] = result & 0xffff; + xsubi[1] = (result >> 16) & 0xffff; + xsubi[2] = (result >> 32) & 0xffff; + + return 0; +} + +int __erand48_r (unsigned short int xsubi[3], struct drand48_data *buffer, double *result) { + union ieee754_double temp; + + /* Compute next state. */ + if (__drand48_iterate (xsubi, buffer) < 0) + return -1; + + /* Construct a positive double with the 48 random bits distributed over + its fractional part so the resulting FP number is [0.0,1.0). */ + + temp.ieee.negative = 0; + temp.ieee.exponent = IEEE754_DOUBLE_BIAS; + temp.ieee.mantissa0 = (xsubi[2] << 4) | (xsubi[1] >> 12); + temp.ieee.mantissa1 = ((xsubi[1] & 0xfff) << 20) | (xsubi[0] << 4); + + /* Please note the lower 4 bits of mantissa1 are always 0. */ + *result = temp.d - 1.0; + + return 0; +} + +int __nrand48_r (unsigned short int xsubi[3], struct drand48_data *buffer, long int *result) { + /* Compute next state. */ + if (__drand48_iterate (xsubi, buffer) < 0) + return -1; + + /* Store the result. */ + if (sizeof (unsigned short int) == 2) + *result = xsubi[2] << 15 | xsubi[1] >> 1; + else + *result = xsubi[2] >> 1; + + return 0; +} + +int __jrand48_r (unsigned short int xsubi[3], struct drand48_data *buffer, long int *result) { + /* Compute next state. */ + if (__drand48_iterate (xsubi, buffer) < 0) + return -1; + + /* Store the result. */ + *result = (int32_t) ((xsubi[2] << 16) | xsubi[1]); + + return 0; +} + +int drand48_r (struct drand48_data *buffer, double *result) { + return __erand48_r (buffer->__x, buffer, result); +} + +int lrand48_r (struct drand48_data *buffer, long int *result) { + /* Be generous for the arguments, detect some errors. */ + if (buffer == NULL) + return -1; + + return __nrand48_r (buffer->__x, buffer, result); +} + +int mrand48_r (struct drand48_data *buffer, long int *result) { + /* Be generous for the arguments, detect some errors. */ + if (buffer == NULL) + return -1; + + return __jrand48_r (buffer->__x, buffer, result); +} + +int srand48_r (long int seedval, struct drand48_data *buffer) { + /* The standards say we only have 32 bits. */ + if (sizeof (long int) > 4) + seedval &= 0xffffffffl; + + buffer->__x[2] = seedval >> 16; + buffer->__x[1] = seedval & 0xffffl; + buffer->__x[0] = 0x330e; + + buffer->__a = 0x5deece66dull; + buffer->__c = 0xb; + buffer->__init = 1; + + return 0; +} \ No newline at end of file 

Póngalo en la carpeta ./patches y modifique un poco nuestro Dockerfile para aplicar el parche sobre la marcha:

 FROM alpine:3.6 COPY ./patches /mtproxy/patches RUN apk add --no-cache --virtual .build-deps \ git make gcc musl-dev linux-headers openssl-dev \ && git clone --single-branch --depth 1 https://github.com/TelegramMessenger/MTProxy.git /mtproxy/sources \ && cd /mtproxy/sources \ && patch -p0 -i /mtproxy/patches/randr_compat.patch \ && make -j$(getconf _NPROCESSORS_ONLN) \ && cp /mtproxy/sources/objs/bin/mtproto-proxy /mtproxy/ \ && rm -rf /mtproxy/{sources,patches} \ && apk add --no-cache --virtual .rundeps libcrypto1.0 \ && apk del .build-deps 

Ahora el binario ensamblado mtproto-proxy es al menos lanzado, y podemos seguir adelante.

Liquidación


Es hora de convertir el run.sh original en docker-entrypoint.sh. En mi opinión, esto es lógico cuando el "enlace obligatorio" entra en ENTRYPOINT (siempre se puede sobrecargar desde el exterior), y los argumentos para iniciar la aplicación acoplada se ajustan al máximo en CMD (+ variables de entorno como suplente).

Podríamos instalar bash y un grep completo en nuestra imagen alpina (explicaré más adelante) para evitar dolores de cabeza y usar el código original tal como está, pero esto inflará nuestra imagen en miniatura para deshonrar, por lo que creceremos un verdadero, su madre, bonsai.

Comencemos con el shebang, reemplace #!/bin/bash con #!/bin/sh . El valor predeterminado para la ceniza alpina es capaz de digerir casi toda la sintaxis de "como está" de bash, pero aún encontramos un problema: por razones desconocidas, se negó a aceptar paréntesis en una de las condiciones, por lo tanto lo expandiremos invirtiendo la lógica de comparación:



Ahora estamos esperando un enfrentamiento con grep, que en la entrega de busybox es ligeramente diferente del habitual (y, por cierto, mucho más lento, tenga en cuenta en sus proyectos). En primer lugar, no entiende la expresión {,15} , tendrá que especificar explícitamente {0,15} . En segundo lugar, no admite el indicador -P (estilo perl), pero digiere silenciosamente la expresión cuando se habilita la extensión (-E).

En nuestras dependencias, solo queda curl (no tiene sentido reemplazarlo con wget desde busybox) y libcrypto (es suficiente, no se requiere directamente openssl en este ensamblaje).

Hace un par de años apareció en Docker una hermosa construcción de varias etapas , es ideal, por ejemplo, para aplicaciones Go o para situaciones en las que el ensamblaje es complicado y es más fácil transferir artefactos de una imagen a otra que hacer la limpieza final. Lo usaremos para plantar nuestros bonsai, esto ahorrará un poco.

 FROM alpine:3.6 #         ( ) RUN apk add --no-cache --virtual .build-deps \ # ... ,    && make -j$(getconf _NPROCESSORS_ONLN) FROM alpine:3.6 #   ,  ,   WORKDIR /mtproxy COPY --from=0 /mtproxy/sources/objs/bin/mtproto-proxy . #          #          

Bonsai debería ser bonsai: elimine la instalación de libcrypto. Al compilar, necesitábamos los archivos de encabezado del paquete openssl-dev, que en las dependencias extraerá libcrypto y nuestro ejecutable estará orientado hacia el uso de libcrypto.so.1.0.0. Pero esta es la única dependencia, además, está preinstalado en Alpine (en la versión 3.6 es libcrypto.so.41, 3.7 - libcrypto.so.42, está en / lib /). Ahora me regañan, esta no es la forma más confiable, pero vale la pena y seguimos agregando un enlace simbólico a la versión existente (si tiene una mejor manera, con mucho gusto aceptaré relaciones públicas).

Toques finales y resultado:

Docker hub
Github

Agradecería cualquier consejo y contribución.

Source: https://habr.com/ru/post/es413881/


All Articles