commit b021fd4d0d4e9a75f6ccd104286ea84abedb0335 Author: Halim Qarroum Date: Wed Feb 15 03:01:51 2023 +0000 Initial commit diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 0000000..26ada4f --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,7 @@ +version = 1 + +[[analyzers]] +name = "secrets" + +[[analyzers]] +name = "docker" \ No newline at end of file diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000..b08dd05 --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,92 @@ +name: Docker Android CI + +on: + push: + branches: [ "main" ] + paths: + - 'Dockerfile' + - '.github/workflows/*' + - 'deps/*' + - 'keys/*' + - 'scripts/*' + +jobs: + build-api-33: + runs-on: ubuntu-latest + steps: + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + - name: Build Docker image with Android 33 + uses: docker/build-push-action@v4 + with: + push: true + tags: halimqarroum/docker-android:api-33 + build-args: API_LEVEL=33 + + build-api-32: + runs-on: ubuntu-latest + steps: + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + - name: Build Docker image with Android 32 + uses: docker/build-push-action@v4 + with: + push: true + tags: halimqarroum/docker-android:api-32 + build-args: API_LEVEL=32 + + build-api-28: + runs-on: ubuntu-latest + steps: + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + - name: Build Docker image with Android 28 + uses: docker/build-push-action@v4 + with: + push: true + tags: halimqarroum/docker-android:api-28 + build-args: | + "API_LEVEL=28" + "ARCHITECTURE=x86" + + build-api-28-playstore: + runs-on: ubuntu-latest + steps: + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + - name: Build Docker image with Android 28 and the PlayStore + uses: docker/build-push-action@v4 + with: + push: true + tags: halimqarroum/docker-android:api-28-playstore + build-args: | + "API_LEVEL=28" + "ARCHITECTURE=x86" + "IMG_TYPE=google_apis_playstore" + + build-minimal: + runs-on: ubuntu-latest + steps: + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + - name: Build minimal Docker image + uses: docker/build-push-action@v4 + with: + push: true + tags: halimqarroum/docker-android:minimal + build-args: INSTALL_ANDROID_SDK=0 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a1f8682 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: ruby + +services: + - docker + +script: + - docker build -t android-emulator . diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e29cdd0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,81 @@ +FROM adoptopenjdk/openjdk11:alpine-jre + +# Docker labels. +LABEL maintainer "Halim Qarroum " +LABEL description "A Docker image allowing to run an Android emulator" +LABEL version "1.0.0" + +# `redir.c` will be used to redirect +# localhost ADB ports to the container interface. +COPY deps/redir/redir.c /usr/src/redir.c + +# Installing required packages. +RUN apk update && \ + apk upgrade && \ + apk add --no-cache \ + alpine-sdk \ + bash \ + unzip \ + wget \ + libvirt-daemon \ + dbus \ + polkit \ + virt-manager && \ + # Compile `redir`. + gcc /usr/src/redir.c -o /usr/bin/redir && \ + strip /usr/bin/redir && \ + # Cleanup APK. + apk del alpine-sdk && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /var/cache/apk/* + +# Arguments that can be overriden at build-time. +ARG INSTALL_ANDROID_SDK=1 +ARG API_LEVEL=33 +ARG IMG_TYPE=google_apis +ARG ARCHITECTURE=x86_64 +ARG CMD_LINE_VERSION=9477386_latest + +# Environment variables. +ENV ANDROID_SDK_ROOT=/opt/android \ + ANDROID_PLATFORM_VERSION="platforms;android-$API_LEVEL" \ + PACKAGE_PATH="system-images;android-${API_LEVEL};${IMG_TYPE};${ARCHITECTURE}" \ + API_LEVEL=$API_LEVEL \ + ARCHITECTURE=$ARCHITECTURE \ + ABI=${IMG_TYPE}/${ARCHITECTURE} \ + QTWEBENGINE_DISABLE_SANDBOX=1 \ + ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL=10 + +# Exporting environment variables for keeping in the path +# Android SDK binaries and shared libraries. +ENV PATH "${PATH}:${ANDROID_SDK_ROOT}/platform-tools" +ENV PATH "${PATH}:${ANDROID_SDK_ROOT}/emulator" +ENV PATH "${PATH}:${ANDROID_SDK_ROOT}/cmdline-tools/tools/bin" +ENV LD_LIBRARY_PATH "$ANDROID_SDK_ROOT/emulator/lib64:$ANDROID_SDK_ROOT/emulator/lib64/qt/lib" + +# Set the working directory to /opt +WORKDIR /opt + +# Exposing the Android emulator console port +# and the ADB port. +EXPOSE 5554 5555 + +# Initializing the required directories. +RUN mkdir /root/.android/ && \ + touch /root/.android/repositories.cfg + +# Exporting ADB keys. +COPY keys/* /root/.android/ + +# Copy the startup scripts. +COPY scripts/* /opt/ + +# Make the scripts executable. +RUN chmod +x /opt/*.sh + +# This layer will download the Android command-line tools +# to install the Android SDK, emulator and system images. +# It will then install the Android SDK and emulator. +RUN /opt/install-sdk.sh + +# Set the entrypoint +ENTRYPOINT ["/opt/start-emulator.sh"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e38e8e2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Halim Qarroum + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5aeb12e --- /dev/null +++ b/README.md @@ -0,0 +1,141 @@ +


+

+ +



+ +# docker-android +> A minimal and customizable Docker image running the Android emulator as a service. + +[![Docker Image CI](https://github.com/HQarroum/docker-android/actions/workflows/docker-image.yml/badge.svg)](https://github.com/HQarroum/docker-android/actions/workflows/docker-image.yml) +[![DeepSource](https://deepsource.io/gh/HQarroum/docker-android.svg/?label=active+issues&show_trend=true&token=JTfGSHolIiMj0WNfv2ES0I6X)](https://deepsource.io/gh/HQarroum/docker-android/?ref=repository-badge) + +Current version: **1.0.0** + +## ๐Ÿ“‹ Table of contents + +- [Features](#-features) +- [Description](#-description) +- [Usage](#-usage) +- [See also](#-see-also) + +## ๐Ÿ”– Features + +- Minimal Alpine based image bundled with the Android emulator and KVM support. +- Bundles the Java Runtime Environment 11 in the image. +- Customizable Android version, device type and image types. +- Port-forwarding of emulator and ADB on the container network interface built-in. +- Emulator images are wiped each time the emulator re-starts. +- Runs headless, suitable for CI farms. Compatible with [`scrcpy`](https://github.com/Genymobile/scrcpy) to remotely control the Android screen. + +## ๐Ÿ”ฐ Description + +The focus of this project is to provide a size-optimized Docker image bundled with the minimal amount of software required to expose a fully functionning Android emulator that's remotely controllable over the network. This image only contains the Android emulator itself, an ADB server used to remotely connect into the emulator from outside the container, and QEMU with `libvirt` support. + +You can build this image without the Android SDK and without the Android emulator to make the image smaller. Below is a size comparison between some of the possible build variants. + +Variant | Uncompressed | Compressed | +------------------------- | ---------------- | ------------ | +SDK 33 + Emulator | 5.84 GB | 1.97 GB | +SDK 28 + Emulator | 4.29 GB | 1.46 GB | +Without SDK and emulator | 414 MB | 138 MB | + +## ๐Ÿ“˜ Usage + +By default, a build will bundle the Android SDK, platform tools and emulator with the image. + +```bash +docker build -t android-emulator . +``` + +### Running the container + +Once the image is built, you can mount your KVM driver on the container and expose its ADB port. + +> Ensure 4GB of memory and at least 8GB of disk space for API 33. + +```bash +docker run -it --rm --device /dev/kvm -p 5555:5555 android-emulator +``` + +### Connect ADB to the container + +The ADB server in the container will be spawned automatically and listen on all interfaces in the container. After a few seconds, once the kernel has booted, you will be able to connect ADB to the container. + +```bash +adb connect 127.0.0.1:5555 +``` + +Additionally, you can use [`scrcpy`](https://github.com/Genymobile/scrcpy) to control the screen of the emulator remotely. To do so, you simply have to connect ADB and run it locally. + +> By default, the emulator runs with a Pixel preset (1080x1920). + +```bash +scrcpy +``` + +
+ + + + + + +
+ + + + + +
+
+ +### Customize the image + +It is possible to customize the API level (Android version) and the image type (Google APIs vs PlayStore) when building the image. + +> By default, the image will build with API 33 with support for Google APIs for an x86_64 architecture. + +This can come in handy when integrating multiple images as part of a CI pipeline where an application or a set of applications need to be tested against different Android versions. There are 2 variables that can be specified at build time to change the Android image. + +- `API_LEVEL` - Specifies the [API level](https://apilevels.com/) associated with the image. Use this parameter to change the Android version. +- `IMG_TYPE` - Specifies the type of image to install. +- `ARCHITECTURE` Specifies the CPU architecture of the Android image. Note that only `x86_64` and `x86` are actively supported by this image. + +The below example will install Android Pie with support for the Google Play Store. + +```bash +docker build \ + --build-arg API_LEVEL=28 \ + --build-arg IMG_TYPE=google_apis_playstore \ + --build-arg ARCHITECTURE=x86 \ + --tag android-emulator . +``` + +### Mount an external drive in the container + +It might be sometimes useful to have the entire Android SDK folder outside of the container (stored on a shared distributed filesystem such as NFS for example), to significantly reduce the size and the build time of the image. + +To do so, you can specify a specific argument at build time to disable the download and installation of the SDK in the image. + +```bash +docker build -t android-emulator --build-arg INSTALL_ANDROID_SDK=0 . +``` + +> You will need mount the SDK in the container at `/opt/android`. + +```bash +docker run -it --rm --device /dev/kvm -p 5555:5555 -v /shared/android/sdk:/opt/android/ android-emulator +``` + +### Pull from Docker Hub + +Different pre-built images of `docker-android` exist on [Docker Hub](https://hub.docker.com/repository/docker/halimqarroum/docker-android/general). Each image variant is tagged using its the api level and image type. For example, to pull an API 33 image, you can run the following. + +```bash +docker pull halimqarroum/docker-android:api-33 +``` + +## ๐Ÿ‘€ See also + +- The [alpine-android](https://github.com/alvr/alpine-android) project which is based on a different Alpine image. +- The [docker-android](https://github.com/budtmo/docker-android) project which offers a WebRTC interface to an Android emulator. diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000..9fc11cf Binary files /dev/null and b/assets/icon.png differ diff --git a/assets/screenshot-2.png b/assets/screenshot-2.png new file mode 100644 index 0000000..fcccede Binary files /dev/null and b/assets/screenshot-2.png differ diff --git a/assets/screenshot-3.png b/assets/screenshot-3.png new file mode 100644 index 0000000..5463dff Binary files /dev/null and b/assets/screenshot-3.png differ diff --git a/assets/screenshot.png b/assets/screenshot.png new file mode 100644 index 0000000..53bf0d5 Binary files /dev/null and b/assets/screenshot.png differ diff --git a/deps/redir/redir.c b/deps/redir/redir.c new file mode 100644 index 0000000..0ea7214 --- /dev/null +++ b/deps/redir/redir.c @@ -0,0 +1,1140 @@ +/* $Id$ + * + * redir - a utility for redirecting tcp connections + * + * Author: Nigel Metheringham + * Nigel.Metheringham@ThePLAnet.net + * + * Based on, but much modified from, code originally written by + * sammy@freenet.akron.oh.us - original header is below. + * + * redir is released under the GNU General Public license, + * version 2, or at your discretion, any later version. + * + */ + +/* + * redir is currently maintained by Sam Creasey (sammy@oh.verio.com). + * Please send patches, etc. there. + * + */ + +/* 980601: dl9sau + * added some nice new features: + * + * --bind_addr=my.other.ip.address + * forces to use my.other.ip.address for the outgoing connection + * + * you can also specify, that redir listens not on all IP addresses of + * your system but only for the given one, i.e.: + * if my host has the addresses + * irc.thishost.my.domain and mail.thishost.my.domain + * but you want that your users do connect for the irc redir service + * only on irc.thishost.my.domain, then do it this way: + * redir irc.fu-berlin.de irc.thishost.mydomain:6667 6667 + * my need was that: + * addr1.first.domain 6667 redirects to irc.first.net port 6667 + * and addr2.second.domain 6667 redirects to irc.second.net port 6667 + * while addr1 and addr2 are the same maschine and the ports can be equal. + * + * enjoy it! + * - thomas , + * + * btw: i tried without success implementing code for the following scenario: + * redir --force_addr irc.fu-berlin.de 6667 6667 + * if "--force_addr" is given and a user connects to my system, that address + * of my system will be used on the outgoing connection that the user + * connected to. + * i was not successful to determine, to which of my addresses the user + * has connected. + */ + +/* 990320 added support for ftp connection done by the client, now this code + * should work for all ftp clients. + * + * - harald + */ + +/* 991221 added options to simulate a slow connection and to limit + * bandwidth. + * + * - Emmanuel Chantr๏ฟฝau + */ + +#define VERSION "2.2.1" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_TCP_WRAPPERS +#include +#endif + +#define debug(x) if (dodebug) fprintf(stderr, x) +#define debug1(x,y) if (dodebug) fprintf(stderr, x, y) + +/* let's set up some globals... */ +int dodebug = 0; +int dosyslog = 0; +unsigned char reuse_addr = 1; +unsigned char linger_opt = 0; +char * bind_addr = NULL; +struct sockaddr_in addr_out; +int timeout = 0; + +#ifndef NO_FTP +int ftp = 0; +#endif + +int transproxy = 0; + +#ifndef NO_SHAPER +int max_bandwidth = 0; +int random_wait = 0; +int wait_in_out=3; /* bit 0: wait for "in", bit 1: wait for "out" */ +int wait_in=1; +int wait_out=1; +#endif + +unsigned int bufsize=4096; +char *connect_str = NULL; /* CONNECT string passed to proxy */ +char * ident = NULL; + +#ifndef NO_FTP +/* what ftp to redirect */ +#define FTP_PORT 1 +#define FTP_PASV 2 +#endif + +#ifdef USE_TCP_WRAPPERS +struct request_info request; +int allow_severity = LOG_INFO; +int deny_severity = LOG_WARNING; +#endif /* USE_TCP_WRAPPERS */ + +#ifdef NEED_STRRCHR +#define strrchr rindex +#endif /* NEED_STRRCHR */ + +#define REDIR_IN 1 +#define REDIR_OUT 0 + +/* prototype anything needing it */ +void do_accept(int servsock, struct sockaddr_in *target); +int bindsock(char *addr, int port, int fail); + +#ifndef NO_SHAPER +/* Used in this program to write something in a socket, it has the same + parameters and return value as "write", but with the flag "in": true if + it's the "in" socket and false if it's the "out" socket */ +static inline ssize_t redir_write (int fd, const void *buf, size_t size, int in) +{ + ssize_t result; + int wait; + + wait=in ? wait_in : wait_out; + if( random_wait > 0 && wait) { + fd_set empty; + struct timeval waitbw; /* for bandwidth */ + int rand_time; + + FD_ZERO(&empty); + + rand_time=rand()%(random_wait*2); + debug1("random wait: %u\n", rand_time); + waitbw.tv_sec=rand_time/1000; + waitbw.tv_usec=rand_time%1000; + + select (1, &empty, NULL, NULL, &waitbw); + } + + result=write(fd, buf, size); + + if( max_bandwidth > 0 && wait) { + fd_set empty; + unsigned long bits; + struct timeval waitbw; /* for bandwidth */ + + FD_ZERO(&empty); + + /* wait to be sure tu be below the allowed bandwidth */ + bits=size*8; + debug1("bandwidth wait: %lu\n", 1000*bits/max_bandwidth); + waitbw.tv_sec=bits/max_bandwidth; + waitbw.tv_usec=(1000*(bits%max_bandwidth))/max_bandwidth; + + select (1, &empty, NULL, NULL, &waitbw); + } + + return result; +} +#else +/* macro if traffic shaper is disabled */ +#define redir_write(fd, buf, size, in) write(fd, buf,size) +#endif + + +#ifdef NEED_STRDUP +char * +strdup(char * str) +{ + char * result; + + if (result = (char *) malloc(strlen(str) + 1)) + strcpy(result, str); + + return result; +} +#endif /* NEED_STRDUP */ + +void +redir_usage(char *name) +{ + fprintf(stderr,"usage:\n"); + fprintf(stderr, + "\t%s --lport= --cport= [options]\n", + name); + fprintf(stderr, "\t%s --inetd --cport=\n", name); + fprintf(stderr, "\n\tOptions are:-\n"); + fprintf(stderr, "\t\t--lport=\t\tport to listen on\n"); + fprintf(stderr, "\t\t--laddr=IP\t\taddress of interface to listen on\n"); + fprintf(stderr, "\t\t--cport=\t\tport to connect to\n"); + fprintf(stderr, "\t\t--caddr=\t\tremote host to connect to\n"); + fprintf(stderr, "\t\t--inetd\t\trun from inetd\n"); + fprintf(stderr, "\t\t--debug\t\toutput debugging info\n"); + fprintf(stderr, "\t\t--timeout=\tset timeout to n seconds\n"); + fprintf(stderr, "\t\t--syslog\tlog messages to syslog\n"); + fprintf(stderr, "\t\t--name=\ttag syslog messages with 'str'\n"); + fprintf(stderr, "\t\t--connect=\tCONNECT string passed to proxy server\n"); +#ifdef USE_TCP_WRAPPERS + fprintf(stderr, "\t\t \tAlso used as service name for TCP wrappers\n"); +#endif /* USE_TCP_WRAPPERS */ + fprintf(stderr, "\t\t--bind_addr=IP\tbind() outgoing IP to given addr\n"); + +#ifndef NO_FTP + fprintf(stderr, "\t\t--ftp=\t\tredirect ftp connections\n"); + fprintf(stderr, "\t\t\twhere type is either port, pasv, both\n"); +#endif + + fprintf(stderr, "\t\t--transproxy\trun in linux's transparent proxy mode\n"); +#ifndef NO_SHAPER + /* options for bandwidth */ + fprintf(stderr, "\t\t--bufsize=\tsize of the buffer\n"); + fprintf(stderr, "\t\t--maxbandwidth=\tlimit the bandwidth\n"); + fprintf(stderr, "\t\t--random_wait=\twait before each packet\n"); + fprintf(stderr, "\t\t--wait_in_out=\t1 wait for in, 2 out, 3 in&out\n"); + /* end options for bandwidth */ +#endif + fprintf(stderr, "\n\tVersion %s.\n", VERSION); + exit(2); +} + +void +parse_args(int argc, + char * argv[], + char ** target_addr, + int * target_port, + char ** local_addr, + int * local_port, + int * timeout, + int * dodebug, + int * inetd, + int * dosyslog, + char ** bind_addr, +#ifndef NO_FTP + int * ftp, +#endif + int *transproxy, +#ifndef NO_SHAPER + unsigned int * bufsize, + int * max_bandwidth, + int * random_wait, + int * wait_in_out, +#endif + char **connect_str) +{ + static struct option long_options[] = { + {"lport", required_argument, 0, 'l'}, + {"laddr", required_argument, 0, 'a'}, + {"cport", required_argument, 0, 'r'}, + {"caddr", required_argument, 0, 'c'}, + {"bind_addr", required_argument, 0, 'b'}, + {"debug", no_argument, 0, 'd'}, + {"timeout", required_argument, 0, 't'}, + {"inetd", no_argument, 0, 'i'}, + {"ident", required_argument, 0, 'n'}, + {"name", required_argument, 0, 'n'}, + {"syslog", no_argument, 0, 's'}, + {"ftp", required_argument, 0, 'f'}, + {"transproxy", no_argument, 0, 'p'}, + {"connect", required_argument, 0, 'x'}, + {"bufsize", required_argument, 0, 'z'}, + {"max_bandwidth", required_argument, 0, 'm'}, + {"random_wait", required_argument, 0, 'w'}, + {"wait_in_out", required_argument, 0, 'o'}, + {0,0,0,0} /* End marker */ + }; + + int option_index = 0; + extern int optind; + int opt; + struct servent *portdesc; + char *lport = NULL; + char *tport = NULL; +#ifndef NO_FTP + char *ftp_type = NULL; +#endif + + *local_addr = NULL; + *target_addr = NULL; + *target_port = 0; + *local_port = 0; + + while ((opt = getopt_long(argc, argv, "disfpn:t:b:a:l:r:c:x:z:m:w:o:", + long_options, &option_index)) != -1) { + switch (opt) { + case 'x': + *connect_str = optarg; + break; + case 'a': + *local_addr = optarg; + break; + + case 'l': + lport = optarg; + break; + + case 'r': + tport = optarg; + break; + + case 'c': + *target_addr = optarg; + break; + + case 'b': + *bind_addr = optarg; + break; + + case 'd': + (*dodebug)++; + break; + + case 't': + *timeout = atol(optarg); + break; + + case 'i': + (*inetd)++; + break; + + case 'n': + /* This is the ident which is added to syslog messages */ + ident = optarg; + break; + + case 's': + (*dosyslog)++; + break; + +#ifndef NO_FTP + case 'f': + ftp_type = optarg; + if(!ftp_type) { + redir_usage(argv[0]); + exit(1); + } + break; +#endif + + case 'p': + (*transproxy)++; + break; + +#ifndef NO_SHAPER + case 'z': + *bufsize = (unsigned int)atol(optarg); + break; + + case 'm': + *max_bandwidth = atol(optarg); + break; + + case 'w': + *random_wait = atol(optarg); + break; + + case 'o': + *wait_in_out = atol(optarg); + wait_in=*wait_in_out & 1; + wait_out=*wait_in_out & 2; + break; +#endif + default: + redir_usage(argv[0]); + exit(1); + break; + } + } + + if(tport == NULL) + { + redir_usage(argv[0]); + exit(1); + } + + if ((portdesc = getservbyname(tport, "tcp")) != NULL) { + *target_port = ntohs(portdesc->s_port); + } else { + *target_port = atol(tport); + } + + /* only check local port if not running from inetd */ + if(!(*inetd)) { + if(lport == NULL) + { + redir_usage(argv[0]); + exit(1); + } + + if ((portdesc = getservbyname(lport, "tcp")) != NULL) + *local_port = ntohs(portdesc->s_port); + else + *local_port = atol(lport); + } /* if *inetd */ + + if (!ident) { + if ((ident = (char *) strrchr(argv[0], '/'))) { + ident++; + } else { + ident = argv[0]; + } + } + +#ifndef NO_FTP + /* some kind of ftp being forwarded? */ + if(ftp_type) { + if(!strncasecmp(ftp_type, "port", 4)) + *ftp = FTP_PORT; + else if(!strncasecmp(ftp_type, "pasv", 4)) + *ftp = FTP_PASV; + else if(!strncasecmp(ftp_type, "both", 4)) + *ftp = FTP_PORT | FTP_PASV; + else { + redir_usage(argv[0]); + exit(1); + } + } +#endif + + openlog(ident, LOG_PID, LOG_DAEMON); + + return; +} + +#ifndef NO_FTP +/* with the --ftp option, this one changes passive mode replies from + the ftp server to point to a new redirector which we spawn, + now it also change the PORT commando when the client accept the + dataconnection */ + +void ftp_clean(int send, char *buf, unsigned long *bytes, int ftpsrv) +{ + + char *port_start; + int rporthi, lporthi; + int lportlo, rportlo; + int lport, rport; + int remip[4]; + int localsock; + int socksize = sizeof(struct sockaddr_in); + + struct sockaddr_in newsession; + struct sockaddr_in sockname; + + if (ftpsrv == 0) + { + /* is this a port commando ? */ + if(strncmp(buf, "PORT", 4)) { + redir_write(send, buf, (*bytes), REDIR_OUT); + return; + } + /* parse the old address out of the buffer */ + port_start = strchr(buf, ' '); + + sscanf(port_start, " %d,%d,%d,%d,%d,%d", &remip[0], &remip[1], + &remip[2], &remip[3], &rporthi, &rportlo); + } else { + /* is this a passive mode return ? */ + if(strncmp(buf, "227", 3)) { + redir_write(send, buf, (*bytes), REDIR_OUT); + return; + } + + /* parse the old address out of the buffer */ + port_start = strchr(buf, '('); + + sscanf(port_start, "(%d,%d,%d,%d,%d,%d", &remip[0], &remip[1], + &remip[2], &remip[3], &rporthi, &rportlo); + } + + /* get the outside interface so we can listen */ + if(getsockname(send, (struct sockaddr *)&sockname, &socksize) != 0) { + perror("getsockname"); + exit(1); + } + + rport = (rporthi << 8) | rportlo; + + /* we need to listen on a port for the incoming connection. + we will use the port 0, so let the system pick one. */ + + localsock = bindsock(inet_ntoa(sockname.sin_addr), 0, 1); + + + /* get the real info */ + if(getsockname(localsock, (struct sockaddr *)&sockname, &socksize) < 0) { + perror("getsockname"); + if (dosyslog) + syslog(LOG_ERR, "getsockname failed: %m"); + exit(1); + } + + lport = ntohs(sockname.sin_port); + + lporthi=(lport >> 8 ) & 0xff; + lportlo=lport & 0xff; + + /* check to see if we bound */ + if(localsock == -1) { + fprintf(stderr, "ftp: unable to bind new listening address\n"); + exit(1); + } + if (ftpsrv == 0) { + /* send the new port and ipaddress to the server */ + (*bytes) = sprintf(buf, "PORT %d,%d,%d,%d,%d,%d\n", + sockname.sin_addr.s_addr & 0xff, + (sockname.sin_addr.s_addr >> 8) & 0xff, + (sockname.sin_addr.s_addr >> 16) & 0xff, + sockname.sin_addr.s_addr >> 24, lporthi, lportlo); + } else { + /* send the new port and ipaddress to the client */ + (*bytes) = sprintf(buf, "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\n", + sockname.sin_addr.s_addr & 0xff, + (sockname.sin_addr.s_addr >> 8) & 0xff, + (sockname.sin_addr.s_addr >> 16) & 0xff, + sockname.sin_addr.s_addr >> 24, lporthi, lportlo); + } + newsession.sin_port = htons(rport); + newsession.sin_family = AF_INET; + newsession.sin_addr.s_addr = remip[0] | (remip[1] << 8) + | (remip[2] << 16) | (remip[3] << 24); + + debug1("ftpdata server ip: %s\n", inet_ntoa(newsession.sin_addr)); + debug1("ftpdata server port: %d\n", rport); + debug1("listening for ftpdata on port %d\n", lport); + debug1("listening for ftpdata on addr %s\n", inet_ntoa(sockname.sin_addr)); + + + /* now that we're bound and listening, we can safely send the new + string without fear of them getting a connection refused. */ + redir_write(send, buf, (*bytes), REDIR_OUT); + + /* make a new process to handle the dataconnection correctly, + for the PASV mode this isn't a problem because after sending the + PASV command, the data connection, get active. For the PORT command + the server must send a success, if starting here with the copyloop + the success command never arrive the client.*/ + + switch(fork()) + { + case -1: /* Error */ + syslog(LOG_ERR, "Couldn't fork: %m"); + _exit(1); + case 0: /* Child */ + { + /* turn off ftp checking while the data connection is active */ + ftp = 0; + do_accept(localsock, &newsession); + close(localsock); + _exit(0); + } + default: /* Parent */ + { close(localsock); } + } + return; +} +#endif + + +void +copyloop(int insock, + int outsock, + int timeout_secs) +{ + fd_set iofds; + fd_set c_iofds; + int max_fd; /* Maximum numbered fd used */ + struct timeval timeout; + unsigned long bytes; + unsigned long bytes_in = 0; + unsigned long bytes_out = 0; + unsigned int start_time, end_time; + char buf[bufsize]; + + /* Record start time */ + start_time = (unsigned int) time(NULL); + + /* Set up timeout */ + timeout.tv_sec = timeout_secs; + timeout.tv_usec = 0; + + /* file descriptor bits */ + FD_ZERO(&iofds); + FD_SET(insock, &iofds); + FD_SET(outsock, &iofds); + + + if (insock > outsock) { + max_fd = insock; + } else { + max_fd = outsock; + } + + debug1("Entering copyloop() - timeout is %d\n", timeout_secs); + while(1) { + (void) memcpy(&c_iofds, &iofds, sizeof(iofds)); + + + if (select(max_fd + 1, + &c_iofds, + (fd_set *)0, + (fd_set *)0, + (timeout_secs ? &timeout : NULL)) <= 0) { + /* syslog(LLEV,"connection timeout: %d sec",timeout.tv_sec);*/ + break; + } + + if(FD_ISSET(insock, &c_iofds)) { + if((bytes = read(insock, buf, sizeof(buf))) <= 0) + break; +#ifndef NO_FTP + if (ftp & FTP_PORT) + /* if we're correcting FTP, lookup for a PORT commando + in the buffer, if yes change this and establish + a new redirector for the data */ + ftp_clean(outsock, buf, &bytes,0); + else +#endif + if(redir_write(outsock, buf, bytes, REDIR_OUT) != bytes) + break; + bytes_out += bytes; + } + if(FD_ISSET(outsock, &c_iofds)) { + if((bytes = read(outsock, buf, sizeof(buf))) <= 0) + break; + /* if we're correcting for PASV on ftp redirections, then + fix buf and bytes to have the new address, among other + things */ +#ifndef NO_FTP + if(ftp & FTP_PASV) + ftp_clean(insock, buf, &bytes,1); + else +#endif + if(redir_write(insock, buf, bytes, REDIR_IN) != bytes) + break; + bytes_in += bytes; + } + } + debug("Leaving main copyloop\n"); + +/* + setsockopt(insock, SOL_SOCKET, SO_REUSEADDR, &reuse_addr, sizeof(reuse_addr)); + setsockopt(insock, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(SO_LINGER)); + setsockopt(outsock, SOL_SOCKET, SO_REUSEADDR, &reuse_addr, sizeof(reuse_addr)); + setsockopt(outsock, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(SO_LINGER)); +*/ + + shutdown(insock,0); + shutdown(outsock,0); + close(insock); + close(outsock); + debug("copyloop - sockets shutdown and closed\n"); + end_time = (unsigned int) time(NULL); + debug1("copyloop - connect time: %8d seconds\n", end_time - start_time); + debug1("copyloop - transfer in: %8ld bytes\n", bytes_in); + debug1("copyloop - transfer out: %8ld bytes\n", bytes_out); + if (dosyslog) { + syslog(LOG_NOTICE, "disconnect %d secs, %ld in %ld out", + (end_time - start_time), bytes_in, bytes_out); + } + return; +} + +void doproxyconnect(int socket) +{ + char buf[128]; + int x; + /* write CONNECT string to proxy */ + sprintf((char *) &buf, "CONNECT %s HTTP/1.0\n\n", connect_str); + x = write(socket, (char *) &buf, strlen(buf)); + if (x < 1) { + perror("doproxyconnect: failed"); + exit(1); + } + /* now read result */ + x = read(socket, (char *) &buf, sizeof(buf)); + if (x < 1) { + perror("doproxyconnect: failed reading fra proxy"); + exit(1); + } + /* no more error checking for now -- something should be added later */ + /* HTTP/1.0 200 Connection established */ +} + + +/* lwait for a connection and move into copyloop... again, + ftp redir will call this, so we don't dupilcate it. */ + +void +do_accept(int servsock, struct sockaddr_in *target) +{ + + int clisock; + int targetsock; + struct sockaddr_in client; + int clientlen = sizeof(client); + int accept_errno; + + debug("top of accept loop\n"); + if ((clisock = accept(servsock, (struct sockaddr *) &client, + &clientlen)) < 0) { + + accept_errno = errno; + perror("server: accept"); + + if (dosyslog) + syslog(LOG_ERR, "accept failed: %m"); + + /* determine if this error is fatal */ + switch(accept_errno) { + /* non-fatal errors */ + case EHOSTUNREACH: + case ECONNRESET: + case ETIMEDOUT: + return; + + /* all other errors assumed fatal */ + default: + exit(1); + } + + } + + debug1("peer IP is %s\n", inet_ntoa(client.sin_addr)); + debug1("peer socket is %d\n", client.sin_port); + + /* + * Double fork here so we don't have to wait later + * This detaches us from our parent so that the parent + * does not need to pick up dead kids later. + * + * This needs to be done before the hosts_access stuff, because + * extended hosts_access options expect to be run from a child. + */ + switch(fork()) + { + case -1: /* Error */ + perror("(server) fork"); + + if (dosyslog) + syslog(LOG_ERR, "(server) fork failed: %m"); + + _exit(1); + case 0: /* Child */ + break; + default: /* Parent */ + { + int status; + + /* Wait for child (who has forked off grandchild) */ + (void) wait(&status); + + /* Close sockets to prevent confusion */ + close(clisock); + + return; + } + } + + /* We are now the first child. Fork again and exit */ + + switch(fork()) + { + case -1: /* Error */ + perror("(child) fork"); + + if (dosyslog) + syslog(LOG_ERR, "(child) fork failed: %m"); + + _exit(1); + case 0: /* Child */ + break; + default: /* Parent */ + _exit(0); + } + + /* We are now the grandchild */ + +#ifdef USE_TCP_WRAPPERS + request_init(&request, RQ_DAEMON, ident, RQ_FILE, clisock, 0); + sock_host(&request); + sock_hostname(&request); + sock_hostaddr(&request); + + if (!hosts_access(&request)) { + refuse(&request); + _exit(0); + } + + if (dosyslog) + syslog(LOG_INFO, "accepted connect from %s", eval_client(&request)); +#endif /* USE_TCP_WRAPPERS */ + + if ((targetsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + + perror("target: socket"); + + if (dosyslog) + syslog(LOG_ERR, "socket failed: %m"); + + _exit(1); + } + + if(transproxy) { + memcpy(&addr_out, &client, sizeof(struct sockaddr_in)); + addr_out.sin_port = 0; + } + + if (bind_addr || transproxy) { + /* this only makes sense if an outgoing IP addr has been forced; + * at this point, we have a valid targetsock to bind() to.. */ + /* also, if we're in transparent proxy mode, this option + never makes sense */ + + if (bind(targetsock, (struct sockaddr *) &addr_out, + sizeof(struct sockaddr_in)) < 0) { + perror("bind_addr: cannot bind to forcerd outgoing addr"); + +/* the port parameter fetch the really port we are listening, it should + only be different if the input value is 0 (let the system pick a + port) */ + if (dosyslog) + syslog(LOG_ERR, "bind failed: %m"); + + _exit(1); + } + debug1("outgoing IP is %s\n", inet_ntoa(addr_out.sin_addr)); + } + + if (connect(targetsock, (struct sockaddr *) target, + sizeof(struct sockaddr_in)) < 0) { + perror("target: connect"); + + if (dosyslog) + syslog(LOG_ERR, "bind failed: %m"); + + _exit(1); + } + + debug1("connected to %s\n", inet_ntoa(target->sin_addr)); + + /* thanks to Anders Vannman for the fix to make proper syslogging + happen here... */ + + if (dosyslog) { + char tmp1[20], tmp2[20]; + strcpy(tmp1, inet_ntoa(client.sin_addr)); + strcpy(tmp2, inet_ntoa(target->sin_addr)); + + syslog(LOG_NOTICE, "connecting %s/%d to %s/%d", + tmp1, client.sin_port, + tmp2, target->sin_port); + } + + /* do proxy stuff */ + if (connect_str) + doproxyconnect(targetsock); + +#ifndef NO_SHAPER + /* initialise random number if necessary */ + if( random_wait > 0 ) { + srand(getpid()); + } +#endif + + copyloop(clisock, targetsock, timeout); + exit(0); /* Exit after copy */ +} + +/* bind to a new socket, we do this out here because passive-fixups + are going to call it too, and there's no sense dupliciting the + code. */ +/* fail is true if we should just return a -1 on error, false if we + should bail. */ + +int bindsock(char *addr, int port, int fail) +{ + + int servsock; + struct sockaddr_in server; + + /* + * Get a socket to work with. This socket will + * be in the Internet domain, and will be a + * stream socket. + */ + + if ((servsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + if(fail) { + return -1; + } + else { + perror("server: socket"); + + if (dosyslog) + syslog(LOG_ERR, "socket failed: %m"); + + exit(1); + } + } + + memset(&server, 0, sizeof(server)); + server.sin_family = AF_INET; + server.sin_port = htons(port); + if (addr != NULL) { + struct hostent *hp; + + debug1("listening on %s\n", addr); + if ((hp = gethostbyname(addr)) == NULL) { + fprintf(stderr, "%s: cannot resolve hostname.\n", addr); + exit(1); + } + memcpy(&server.sin_addr, hp->h_addr, hp->h_length); + } else { + debug("local IP is default\n"); + server.sin_addr.s_addr = htonl(inet_addr("0.0.0.0")); + } + + setsockopt(servsock, SOL_SOCKET, SO_REUSEADDR, &reuse_addr, sizeof(reuse_addr)); + setsockopt(servsock, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(SO_LINGER)); + + /* + * Try to bind the address to the socket. + */ + + if (bind(servsock, (struct sockaddr *) &server, + sizeof(server)) < 0) { + if(fail) { + close(servsock); + return -1; + } else { + perror("server: bind"); + + if (dosyslog) + syslog(LOG_ERR, "bind failed: %m"); + + exit(1); + } + } + + /* + * Listen on the socket. + */ + + if (listen(servsock, 10) < 0) { + if(fail) { + close(servsock); + return -1; + } else { + perror("server: listen"); + + if (dosyslog) + syslog(LOG_ERR, "listen failed: %m"); + + exit(1); + } + } + + return servsock; +} + +int +main(int argc, char *argv[]) +{ + + struct sockaddr_in target; + char *target_addr; + int target_port; + char *local_addr; + int local_port; + int inetd = 0; + char * target_ip; + char * ip_to_target; + + debug("parse args\n"); + parse_args(argc, argv, &target_addr, &target_port, &local_addr, + &local_port, &timeout, &dodebug, &inetd, &dosyslog, &bind_addr, +#ifndef NO_FTP + &ftp, +#endif + &transproxy, +#ifndef NO_SHAPER + &bufsize, &max_bandwidth, &random_wait, + &wait_in_out, +#endif + &connect_str); + + /* Set up target */ + target.sin_family = AF_INET; + target.sin_port = htons(target_port); + if (target_addr != NULL) { + struct hostent *hp; + + debug1("target is %s\n", target_addr); + if ((hp = gethostbyname(target_addr)) == NULL) { + fprintf(stderr, "%s: host unknown.\n", target_addr); + exit(1); + } + memcpy(&target.sin_addr, hp->h_addr, hp->h_length); + } else { + debug("target is default\n"); + target.sin_addr.s_addr = htonl(inet_addr("0.0.0.0")); + } + + target_ip = strdup(inet_ntoa(target.sin_addr)); + debug1("target IP address is %s\n", target_ip); + debug1("target port is %d\n", target_port); + + /* Set up outgoing IP addr (optional); + * we have to wait for bind until targetsock = socket() is done + */ + if (bind_addr && !transproxy) { + struct hostent *hp; + + fprintf(stderr, "bind_addr is %s\n", bind_addr); + addr_out.sin_family = AF_INET; + addr_out.sin_port = 0; + if ((hp = gethostbyname(bind_addr)) == NULL) { + fprintf(stderr, "%s: cannot resolve forced outgoing IP address.\n", bind_addr); + exit(1); + } + memcpy(&addr_out.sin_addr, hp->h_addr, hp->h_length); + + ip_to_target = strdup(inet_ntoa(addr_out.sin_addr)); + debug1("IP address for target is %s\n", ip_to_target); + } + + + if (inetd) { + int targetsock; + struct sockaddr_in client; + int client_size = sizeof(client); + +#ifdef USE_TCP_WRAPPERS + request_init(&request, RQ_DAEMON, ident, RQ_FILE, 0, 0); + sock_host(&request); + sock_hostname(&request); + sock_hostaddr(&request); + + if (!hosts_access(&request)) + refuse(&request); +#endif /* USE_TCP_WRAPPERS */ + + if (!getpeername(0, (struct sockaddr *) &client, &client_size)) { + debug1("peer IP is %s\n", inet_ntoa(client.sin_addr)); + debug1("peer socket is %d\n", client.sin_port); + } + if ((targetsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + perror("target: socket"); + + if (dosyslog) + syslog(LOG_ERR, "targetsock failed: %m"); + + exit(1); + } + + if(transproxy) { + memcpy(&addr_out, &client, sizeof(struct sockaddr_in)); + addr_out.sin_port = 0; + } + + if (bind_addr || transproxy) { + /* this only makes sense if an outgoing IP addr has been forced; + * at this point, we have a valid targetsock to bind() to.. */ + if (bind(targetsock, (struct sockaddr *) &addr_out, + sizeof(addr_out)) < 0) { + perror("bind_addr: cannot bind to forcerd outgoing addr"); + + if (dosyslog) + syslog(LOG_ERR, "bind failed: %m"); + + exit(1); + } + debug1("outgoing IP is %s\n", inet_ntoa(addr_out.sin_addr)); + } + + if (connect(targetsock, (struct sockaddr *) &target, + sizeof(target)) < 0) { + perror("target: connect"); + + if (dosyslog) + syslog(LOG_ERR, "connect failed: %m"); + + exit(1); + } + + if (dosyslog) { + syslog(LOG_NOTICE, "connecting %s/%d to %s/%d", + inet_ntoa(client.sin_addr), client.sin_port, + target_ip, target.sin_port); + } + + /* Just start copying - one side of the loop is stdin - 0 */ + copyloop(0, targetsock, timeout); + } else { + int servsock; + + if(local_addr) + servsock = bindsock(local_addr, local_port, 0); + else + servsock = bindsock(NULL, local_port, 0); + + /* + * Accept connections. When we accept one, ns + * will be connected to the client. client will + * contain the address of the client. + */ + + while (1) + do_accept(servsock, &target); + } + + /* this should really never be reached */ + + exit(0); + +} \ No newline at end of file diff --git a/keys/adbkey b/keys/adbkey new file mode 100644 index 0000000..2b657c1 --- /dev/null +++ b/keys/adbkey @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCjLJ91cKt62g2W +DksA7UjUHOeuveDHKzuwgswpIr4d75OHSBOYsIs5HBu5jPJ0UOZ0pMNJVZMHx7+T +J4vrSLmcjg02FWkngkr2DaZyxaDnGYhra452ARoTF3fc2pOflrXjiLPRUvxwTeNt +B0kJT7PrmTm82ZsEahsoRqdwvuyfoJzy5H9KzBr1OSdTdbRzvm48up9qOy0mdNbG +0994FyMWMeFWj07Y7MT39wUZhDEKOyLx/JE57s7QWde8OM8UA+et2FNud0eMP6kF +nG7RJdcmnw7e6BVzkNNKojyaGo8xSchHGAxCPykCtZBnl9b0mfjkWAKOPbu0YQEz +jppHmqx5AgMBAAECggEAAQ81m9QW8O3Af9Ro7UEdI29qm6pU7sYblZpLzAqTNGuN +SCgSJIAk70J89ZfyLQonbONy9yNsmHTWDBUKJgVrWxZbBJpf++cvKGjPR1h8InP6 +fEQPATohmrUgzJjIs5UNakRFcpr405vV2avd8AcJ8oIkq1K4XG957KmExckfjSvB +oaWR5ZCZ4TO60C0k8za1VhnnFUpwcXgE5qno8yneEgu3EiH1zxRsUkmp/P0KWJbt +RJMZEAi+xPD85v0/IhAIiNgu15SvQzEFA/OzF2Vmrg8gCyXp0OEu2rTat8PNOBtR +mIeX9XxBK9CJFawFtrmDwPWn5rHhclmrsL8Fh6+zeQKBgQDOz/r6Hu3IoAzGO/X5 +aH8oheiejBNsiqGH3EtSwUxR28ZBZxQ1+U5khsuCRSNzG4QKedbMyL1Qc0X3gLOv +qG0GVxU2V/2Ru9yZ4FErYZWIZwY0PPJtLvCv2HbEANtd8LXAvInzmzXVSQlPVjvy +MPklVt5gSZCmIlIP5Tx6j3XBvQKBgQDJ+68ia6Tl2qw4PIaS0bHbCWcZuDz4UPF4 +BNGUysGm3Utd2JmD8TpDIqn9xjjVBILWlpyCe4lzuPl/YaKHnqEMuxskcxzZr7p5 +WmkEzqDOnzm7e8f0My2vZRvftYMwGqbcA18swpVqi9LR8IFDrO3ZlabXTYf/eLf6 ++3ePgA1bbQKBgGQ5RleQRYvEhGxNApcrnPP2cRTkrUUcqcs9ZwNjyaih6SIo4hKG +DweIi3dGKjfOTd8FzKLRZW1nb7DSHkIyTSOVcCI14hyN/gpUOwHa+KxxwP/vyUxA +Q0P0kxnJkIWl8W7F79SukxgZnPllwQdN1G/T+VCQVhks6LfrT7PVcivBAoGAdEOn +wAxuUtDYqVZNsF18HzT+X9OpZNZS6F79tMQxTDe9ljuDwmssulBNwYJEXHnIRnVT +idhKeo5TL3un7qDk20soiIaqaLO6hvf/04CNF+KXfM/3fR2ognmU3eTBqwLsYWCf +fEhVcYsiwy3JeockMkbuTqiWS/vVC2eMntF63tECgYBQfH7JtZ+b+ckH05fuckbY +tGFKx959xV7a6fJJQUHGHdbUxPdJvk76TdHT8ouBmjNU21iQq7kL0kHTnDcHBRjS +QwAPvOIoXKNNRpfgTQz532vSknCG/AMTxHhZlOFb6Lgsn261oYlNi2OPrG8DrknM +UWezE5O8/B5V4t69i66/rw== +-----END PRIVATE KEY----- diff --git a/keys/adbkey.pub b/keys/adbkey.pub new file mode 100644 index 0000000..f35c917 --- /dev/null +++ b/keys/adbkey.pub @@ -0,0 +1 @@ +QAAAADcCzLh5rJpHmo4zAWG0uz2OAljk+Jn01pdnkLUCKT9CDBhHyEkxjxqaPKJK05BzFejeDp8m1yXRbpwFqT+MR3duU9it5wMUzzi811nQzu45kfzxIjsKMYQZBff3xOzYTo9W4TEWIxd439PG1nQmLTtqn7o8br5ztHVTJzn1GsxKf+TynKCf7L5wp0YoG2oEm9m8OZnrs08JSQdt401w/FLRs4jjtZafk9rcdxcTGgF2jmtriBnnoMVypg32SoInaRU2DY6cuUjriyeTv8cHk1VJw6R05lB08oy5Gxw5i7CYE0iHk+8dviIpzIKwOyvH4L2u5xzUSO0ASw6WDdp6q3B1nyyjFVLaEEvn7qsNCOAjrJUcH/y8LJO8JQLqOdZwsvZFayy8B3egrbOc3StUOpPv9mwrbpe0dt9E5/Yc9iG4QpDsYKHFKTsEJrsZDR7Qu0XAaLXnurGsI7CIwo6FSpCyIwDQ33/P5gNeMuN4sPYKqoc20QZ7WxIRvG9Ro2NiMFAJnz0EUBMeU5ZInqh7gSr2ss5EUSHdOgLtuiI3kedozzV9/EDpiwe+Kld1O7WRN5Fz3Zv2YgW3lau3nbLvJOkFsSP4WXaNfYuwLaA62adHSyme829X+zO+x+X0UTZHKDRe2hQQgHJcRMWHWfmBjuudKgbl8P5ukVxj/IIqxbeyxs4YnwEAAQA= @unknown \ No newline at end of file diff --git a/scripts/emulator-monitoring.sh b/scripts/emulator-monitoring.sh new file mode 100644 index 0000000..7f0d5cc --- /dev/null +++ b/scripts/emulator-monitoring.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -e + +# Writes a log with a key and a value +# as a JSON object on the standard output. +function write_log() { + echo "{ \"type\": \"$1\", \"value\": \"$2\" }" +} + +# Writes a state update on the standard output. +function update_state() { + write_log "state-update" "$1" +} + +# Waits for the emulator to boot and writes +# state updates on the standard output. +function wait_for_boot() { + update_state "booting" + + # Waiting for the ADB server to start. + while [ -n "$(adb wait-for-device > /dev/null)" ]; do + adb wait-for-device + sleep 1 + done + + # Waiting for the boot sequence to be completed. + COMPLETED=$(adb shell getprop sys.boot_completed | tr -d '\r') + while [ "$COMPLETED" != "1" ]; do + COMPLETED=$(adb shell getprop sys.boot_completed | tr -d '\r') + sleep 5 + done + update_state "booted" +} \ No newline at end of file diff --git a/scripts/install-sdk.sh b/scripts/install-sdk.sh new file mode 100644 index 0000000..f79ad4d --- /dev/null +++ b/scripts/install-sdk.sh @@ -0,0 +1,14 @@ +# If the installation flag of the Android SDK is set +# we download the Android command-line tools, +# install the SDK, platform tools and the emulator. +if [ "$INSTALL_ANDROID_SDK" == "1" ]; then + echo "Installing the Android SDK, platform tools and emulator ..." + wget https://dl.google.com/android/repository/commandlinetools-linux-${CMD_LINE_VERSION}.zip -P /tmp && \ + mkdir -p $ANDROID_SDK_ROOT/cmdline-tools/ && \ + unzip -d $ANDROID_SDK_ROOT/cmdline-tools/ /tmp/commandlinetools-linux-${CMD_LINE_VERSION}.zip && \ + mv $ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools/ $ANDROID_SDK_ROOT/cmdline-tools/tools/ && \ + rm /tmp/commandlinetools-linux-${CMD_LINE_VERSION}.zip && \ + yes | sdkmanager --licenses && \ + sdkmanager --install "$PACKAGE_PATH" && \ + yes | sdkmanager --verbose $PACKAGE_PATH $ANDROID_PLATFORM_VERSION platform-tools emulator +fi diff --git a/scripts/start-emulator.sh b/scripts/start-emulator.sh new file mode 100644 index 0000000..7a2dc8b --- /dev/null +++ b/scripts/start-emulator.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +set -e + +source ./emulator-monitoring.sh + +# The emulator console port. +EMULATOR_CONSOLE_PORT=5554 +# The ADB port used to connect to ADB. +ADB_PORT=5555 + +# Start ADB server by listening on all interfaces. +echo "Starting the ADB server ..." +adb -a -P 5037 server nodaemon & + +# Detect ip and forward ADB ports outside to outside interface +ip=$(ip addr list eth0 | grep "inet " | cut -d' ' -f6 | cut -d/ -f1) +redir --laddr=$ip --lport=$EMULATOR_CONSOLE_PORT --caddr=127.0.0.1 --cport=$EMULATOR_CONSOLE_PORT & +redir --laddr=$ip --lport=$ADB_PORT --caddr=127.0.0.1 --cport=$ADB_PORT & + +export USER=root + +# Creating the Android Virtual Emulator. +echo "Creating the Android Virtual Emulator ..." +echo no | avdmanager create avd -n android --abi ${ABI} -k "$PACKAGE_PATH" --device "pixel" + +# Asynchronously write updates on the standard output +# about the state of the boot sequence. +#cat ~/.android/avd/android.avd/config.ini +wait_for_boot & + +# Start the emulator with no audio, no GUI, and no snapshots. +echo "Starting the emulator ..." +if emulator \ + -avd android \ + -noaudio \ + -no-boot-anim \ + -no-window \ + -no-snapshot-save \ + -qemu \ + -enable-kvm; +then + update_state "stopped" +else + update_state "stopped" +fi