From 1b2c4fa86c7dba32ba439dceab8079e6b9e26f16 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:02:07 -0700 Subject: [PATCH] large scale android improvements - Port vanilla-pipe on Android for users with rooted devices and compatible Wi-Fi hardware - Drop OpenSSL as a build dependency (only used for hostap's crypto backend, but didn't work on Android so now we're using hostap's internal crypto) - Improvements to event loop (targets 180Hz input polling + 60Hz video display) - Implement MediaCodec hardware decoding on Android - Use smaller and simpler DHCP client --- CMakeLists.txt | 7 +- README.md | 8 +- android/app/build.gradle | 11 +- android/app/jni/Application.mk | 2 +- android/app/jni/CMakeLists.txt | 4 +- android/app/proguard-rules.pro | 6 + .../com/mattkc/vanilla/VanillaActivity.java | 45 +- gui/CMakeLists.txt | 25 +- gui/lang.c | 3 +- gui/lang.h | 1 + gui/menu/menu_connection.c | 83 ++- gui/menu/menu_game.c | 81 ++- gui/pipemgmt.c | 108 ++- gui/platform_android.c | 42 ++ gui/platform_android.h | 6 + gui/ui/ui_sdl.c | 347 +++++++--- gui/ui/ui_sdl_android.c | 195 ++++++ gui/ui/ui_sdl_android.h | 12 + lib/gamepad/gamepad.c | 77 +-- lib/gamepad/gamepad.h | 12 +- lib/gamepad/input.c | 6 +- pipe/def.h | 2 - pipe/linux/CMakeLists.txt | 110 +-- pipe/linux/android.c | 472 +++++++++++++ pipe/linux/android.h | 15 + pipe/linux/dhcp/AUTHORS | 13 - pipe/linux/dhcp/CMakeLists.txt | 14 - pipe/linux/dhcp/COPYING | 339 --------- pipe/linux/dhcp/README.udhcpc | 142 ---- pipe/linux/dhcp/clientpacket.c | 249 ------- pipe/linux/dhcp/clientpacket.h | 14 - pipe/linux/dhcp/clientsocket.c | 62 -- pipe/linux/dhcp/clientsocket.h | 7 - pipe/linux/dhcp/common.c | 162 ----- pipe/linux/dhcp/common.h | 56 -- pipe/linux/dhcp/dhcp.c | 467 +++++++++++++ pipe/linux/dhcp/dhcp.h | 24 + pipe/linux/dhcp/dhcpc.c | 535 --------------- pipe/linux/dhcp/dhcpc.h | 41 -- pipe/linux/dhcp/dhcpd.h | 141 ---- pipe/linux/dhcp/files.h | 17 - pipe/linux/dhcp/leases.h | 23 - pipe/linux/dhcp/libbb_udhcp.h | 54 -- pipe/linux/dhcp/options.c | 234 ------- pipe/linux/dhcp/options.h | 40 -- pipe/linux/dhcp/packet.c | 211 ------ pipe/linux/dhcp/packet.h | 41 -- pipe/linux/dhcp/pidfile.c | 75 -- pipe/linux/dhcp/pidfile.h | 25 - pipe/linux/dhcp/script.c | 242 ------- pipe/linux/dhcp/script.h | 6 - pipe/linux/dhcp/signalpipe.c | 78 --- pipe/linux/dhcp/signalpipe.h | 22 - pipe/linux/dhcp/socket.c | 137 ---- pipe/linux/dhcp/socket.h | 8 - pipe/linux/dhcp/version.h | 6 - pipe/linux/wpa.c | 642 ++++++++++++------ pipe/linux/wpa.h | 5 +- 58 files changed, 2350 insertions(+), 3462 deletions(-) create mode 100644 gui/platform_android.c create mode 100644 gui/platform_android.h create mode 100644 gui/ui/ui_sdl_android.c create mode 100644 gui/ui/ui_sdl_android.h create mode 100644 pipe/linux/android.c create mode 100644 pipe/linux/android.h delete mode 100644 pipe/linux/dhcp/AUTHORS delete mode 100644 pipe/linux/dhcp/CMakeLists.txt delete mode 100644 pipe/linux/dhcp/COPYING delete mode 100644 pipe/linux/dhcp/README.udhcpc delete mode 100644 pipe/linux/dhcp/clientpacket.c delete mode 100644 pipe/linux/dhcp/clientpacket.h delete mode 100644 pipe/linux/dhcp/clientsocket.c delete mode 100644 pipe/linux/dhcp/clientsocket.h delete mode 100644 pipe/linux/dhcp/common.c delete mode 100644 pipe/linux/dhcp/common.h create mode 100644 pipe/linux/dhcp/dhcp.c create mode 100644 pipe/linux/dhcp/dhcp.h delete mode 100644 pipe/linux/dhcp/dhcpc.c delete mode 100644 pipe/linux/dhcp/dhcpc.h delete mode 100644 pipe/linux/dhcp/dhcpd.h delete mode 100644 pipe/linux/dhcp/files.h delete mode 100644 pipe/linux/dhcp/leases.h delete mode 100644 pipe/linux/dhcp/libbb_udhcp.h delete mode 100644 pipe/linux/dhcp/options.c delete mode 100644 pipe/linux/dhcp/options.h delete mode 100644 pipe/linux/dhcp/packet.c delete mode 100644 pipe/linux/dhcp/packet.h delete mode 100644 pipe/linux/dhcp/pidfile.c delete mode 100644 pipe/linux/dhcp/pidfile.h delete mode 100644 pipe/linux/dhcp/script.c delete mode 100644 pipe/linux/dhcp/script.h delete mode 100644 pipe/linux/dhcp/signalpipe.c delete mode 100644 pipe/linux/dhcp/signalpipe.h delete mode 100644 pipe/linux/dhcp/socket.c delete mode 100644 pipe/linux/dhcp/socket.h delete mode 100644 pipe/linux/dhcp/version.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 54a643a..3f7b49f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,9 +19,14 @@ else() set(vendored_default FALSE) endif() +set(vanilla_pipe_default OFF) +if(LINUX OR ANDROID) + set(vanilla_pipe_default ON) +endif() + OPTION(VANILLA_BUILD_GUI "Build SDL2 GUI app" ON) OPTION(VANILLA_BUILD_TESTS "Build unit tests for Vanilla" OFF) -OPTION(VANILLA_BUILD_PIPE "Build vanilla-pipe for connecting to Wii U (Linux only)" ${LINUX}) +OPTION(VANILLA_BUILD_PIPE "Build vanilla-pipe for connecting to Wii U" ${vanilla_pipe_default}) OPTION(VANILLA_BUILD_VENDORED "Build Vanilla with \"vendored\" third-party libraries" ${vendored_default}) add_subdirectory(lib) diff --git a/README.md b/README.md index 7dc5776..d9cdd23 100644 --- a/README.md +++ b/README.md @@ -90,19 +90,19 @@ Vanilla currently requires the following dependencies: - **Debian/Ubuntu** ```sh - apt install build-essential cmake libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev libavformat-dev libavcodec-dev libavutil-dev libswscale-dev libnl-genl-3-dev libnl-route-3-dev libssl-dev libxml2-dev libnm-dev libpolkit-agent-1-dev + apt install build-essential cmake libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev libavformat-dev libavcodec-dev libavutil-dev libswscale-dev libnl-genl-3-dev libnl-route-3-dev libxml2-dev libnm-dev libpolkit-agent-1-dev ``` - **Fedora** ```sh - dnf install libavcodec-free-devel libavutil-free-devel libavfilter-free-devel libnl3-devel SDL2-devel SDL2_image-devel SDL2_ttf-devel openssl-devel make automake gcc gcc-c++ kernel-devel cmake libxml2-devel NetworkManager-libnm-devel polkit-devel + dnf install libavcodec-free-devel libavutil-free-devel libavfilter-free-devel libnl3-devel SDL2-devel SDL2_image-devel SDL2_ttf-devel make automake gcc gcc-c++ kernel-devel cmake libxml2-devel NetworkManager-libnm-devel polkit-devel ``` - **Arch** ```sh - pacman -S base-devel make cmake ffmpeg libnl sdl2 sdl2_image sdl2_ttf libxml2 libnm openssl polkit + pacman -S base-devel make cmake ffmpeg libnl sdl2 sdl2_image sdl2_ttf libxml2 libnm polkit ``` - **Alpine/postmarketOS** ```sh - apk add build-base cmake sdl2-dev sdl2_image-dev sdl2_ttf-dev ffmpeg-dev libnl3-dev libxml2-dev openssl-dev networkmanager-dev polkit-dev + apk add build-base cmake sdl2-dev sdl2_image-dev sdl2_ttf-dev ffmpeg-dev libnl3-dev libxml2-dev networkmanager-dev polkit-dev ``` The build process is otherwise normal for a CMake program: diff --git a/android/app/build.gradle b/android/app/build.gradle index 30fb34d..7462cf3 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -13,7 +13,7 @@ android { } compileSdkVersion 34 defaultConfig { - minSdkVersion 19 + minSdkVersion 21 targetSdkVersion 34 versionCode 1 versionName "1.0" @@ -23,7 +23,7 @@ android { // abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' // } cmake { - arguments "-DANDROID_APP_PLATFORM=android-19", "-DANDROID_STL=c++_static" + arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static" abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' version "3.31" } @@ -35,6 +35,13 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + packagingOptions { + jniLibs { + // Ensure libvanilla_pipe.so is accessible alongside main executable + useLegacyPackaging true + } + } + sourceSets.main.assets.setSrcDirs(['../../gui/assets']) applicationVariants.all { variant -> tasks["merge${variant.name.capitalize()}Assets"] .dependsOn("externalNativeBuild${variant.name.capitalize()}") diff --git a/android/app/jni/Application.mk b/android/app/jni/Application.mk index 023bc20..1f7c0c1 100644 --- a/android/app/jni/Application.mk +++ b/android/app/jni/Application.mk @@ -7,4 +7,4 @@ APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 # Min runtime API level -APP_PLATFORM=android-16 +APP_PLATFORM=android-21 diff --git a/android/app/jni/CMakeLists.txt b/android/app/jni/CMakeLists.txt index 6d08018..4c76536 100644 --- a/android/app/jni/CMakeLists.txt +++ b/android/app/jni/CMakeLists.txt @@ -9,5 +9,5 @@ project(GAME) # Android has no need for a "windowed" option set(VANILLA_GUI_ENABLE_WINDOWED OFF) -# Your game and its CMakeLists.txt are in a subfolder named "src" -add_subdirectory(../../.. "${CMAKE_SOURCE_DIR}/build") +# Add source files and keep in individual per-ABI dirs +add_subdirectory(../../.. "${CMAKE_CURRENT_BINARY_DIR}/vanilla") diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index a4c9886..36d4c7f 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -96,3 +96,9 @@ void hapticRun(int, float, int); void hapticStop(int); } + +-keepclassmembers class com.mattkc.vanilla.VanillaActivity { + public static android.view.Surface createVideoSurface(int, int, int); + public static float[] updateVideoSurfaceTexture(); + public static void destroyVideoSurface(); +} diff --git a/android/app/src/main/java/com/mattkc/vanilla/VanillaActivity.java b/android/app/src/main/java/com/mattkc/vanilla/VanillaActivity.java index afa533b..fef603f 100644 --- a/android/app/src/main/java/com/mattkc/vanilla/VanillaActivity.java +++ b/android/app/src/main/java/com/mattkc/vanilla/VanillaActivity.java @@ -1,18 +1,59 @@ package com.mattkc.vanilla; +import android.graphics.SurfaceTexture; +import android.view.Surface; + +import java.util.concurrent.atomic.AtomicBoolean; + import org.libsdl.app.SDLActivity; public class VanillaActivity extends SDLActivity { + private static SurfaceTexture videoSurfaceTexture; + private static Surface videoSurface; + private static final AtomicBoolean videoFrameAvailable = new AtomicBoolean(false); + private static final float[] videoTransform = new float[16]; + @Override protected String[] getLibraries() { return new String[] { "SDL2", "SDL2_image", - // "SDL2_mixer", - // "SDL2_net", "SDL2_ttf", "xml2", "vanilla" }; } + + public static synchronized Surface createVideoSurface(int textureName, int width, int height) { + destroyVideoSurface(); + + videoSurfaceTexture = new SurfaceTexture(textureName); + videoSurfaceTexture.setDefaultBufferSize(width, height); + videoSurfaceTexture.setOnFrameAvailableListener(surfaceTexture -> videoFrameAvailable.set(true)); + videoSurface = new Surface(videoSurfaceTexture); + return videoSurface; + } + + // Called from SDL render thread where owning GLES context is current + public static synchronized float[] updateVideoSurfaceTexture() { + if (videoSurfaceTexture == null || !videoFrameAvailable.getAndSet(false)) { + return null; + } + + videoSurfaceTexture.updateTexImage(); + videoSurfaceTexture.getTransformMatrix(videoTransform); + return videoTransform; + } + + public static synchronized void destroyVideoSurface() { + videoFrameAvailable.set(false); + if (videoSurface != null) { + videoSurface.release(); + videoSurface = null; + } + if (videoSurfaceTexture != null) { + videoSurfaceTexture.release(); + videoSurfaceTexture = null; + } + } } diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index ebe7822..0d8b822 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -87,6 +87,11 @@ function(compile_ffmpeg _target_name _system_name _system_processor) ) elseif(ANDROID) set(FFMPEG_TARGET_OS "android") + list(APPEND FFMPEG_CONFIGURE_ARGS + --enable-jni + --enable-mediacodec + --enable-decoder=h264_mediacodec + ) # Get Android platform version (inexplicably apparently there literally isn't a better way to do this) string(REPLACE "-" " " _android_platform_args ${ANDROID_PLATFORM}) @@ -296,6 +301,13 @@ list(APPEND VANILLA_GUI_SRC ui/ui_keyboard.c ) +if (ANDROID) + list(APPEND VANILLA_GUI_SRC + platform_android.c + ui/ui_sdl_android.c + ) +endif() + if (LINUX) # Link with DRM if available option(VANILLA_BUILD_USE_DRM "Enable DRM video output" ON) @@ -386,7 +398,10 @@ target_link_libraries(vanillagui PRIVATE if (ANDROID) target_link_libraries(vanillagui PRIVATE SDL2main + android + GLESv2 log + mediandk ) endif() @@ -487,19 +502,13 @@ target_include_directories(vanillagui PRIVATE ) # Install assets -if(ANDROID) - # Put assets in correct place relative to build - add_custom_command( - TARGET vanilla POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/assets" "${CMAKE_CURRENT_SOURCE_DIR}/../android/app/src/main/assets" - ) -elseif(APPLE) +if(APPLE) # Put assets in correct place relative to build add_custom_command( TARGET vanilla POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different "${CMAKE_CURRENT_SOURCE_DIR}/assets" "$/../Resources/assets" ) -else() +elseif(NOT ANDROID) # Put assets in correct place relative to build add_custom_command( TARGET vanilla POST_BUILD diff --git a/gui/lang.c b/gui/lang.c index 3dde299..2d854e4 100644 --- a/gui/lang.c +++ b/gui/lang.c @@ -58,7 +58,8 @@ static const char *lang_str[__VPI_LANG_T_COUNT] = { "Reset", "Full Screen", "Hardware Decoding", - "Cursor In Full Screen" + "Cursor In Full Screen", + "Local connection on Android requires root access." }; const char *lang(vpi_lang_t id) diff --git a/gui/lang.h b/gui/lang.h index 952ab43..342d76e 100644 --- a/gui/lang.h +++ b/gui/lang.h @@ -60,6 +60,7 @@ typedef enum { VPI_LANG_FULLSCREEN, VPI_LANG_HWDEC, VPI_LANG_CURSOR_IN_FS, + VPI_LANG_ANDROID_ROOT_REQUIRED, __VPI_LANG_T_COUNT } vpi_lang_t; diff --git a/gui/menu/menu_connection.c b/gui/menu/menu_connection.c index dba799c..8ca3376 100644 --- a/gui/menu/menu_connection.c +++ b/gui/menu/menu_connection.c @@ -5,10 +5,14 @@ typedef uint32_t in_addr_t; #else #include +#ifdef ANDROID +#include +#endif #endif #include #include +#include #include #include @@ -17,6 +21,7 @@ typedef uint32_t in_addr_t; #include "menu_common.h" #include "menu_settings.h" #include "pipemgmt.h" +#include "platform.h" #include "ui/ui_anim.h" #include "ui/ui_util.h" @@ -26,8 +31,10 @@ static int error_lbl; static int ip_textedit; #define MAX_WIRELESS_ENTRIES 4 +static size_t detected_wireless_interface_count = 0; static char detected_wireless_interfaces[256][256]; -static int intf_start = 0; +#define MAX_DETECTED_WIRELESS_INTERFACES (sizeof(detected_wireless_interfaces) / sizeof(detected_wireless_interfaces[0])) +static int intf_start = 0; // NOTE: Not currently implemented, but may be used for pagination in the future static int intf_buttons[MAX_WIRELESS_ENTRIES]; static const char *checkmark = 0;//"checkmark.svg"; @@ -66,13 +73,39 @@ static void return_to_connection(vui_context_t *vui, int btn, void *v) vui_transition_fade_layer_out(vui, layer, cb, cb_data); } -static void populate_wireless_intf() +static void add_wireless_interface(const char *intf) { - // LINUX ONLY: + // Sanity checks + if (!intf || !intf[0] || detected_wireless_interface_count >= MAX_DETECTED_WIRELESS_INTERFACES) { + return; + } + + // Ensure interface hasn't already been added + for (size_t i = 0; i < detected_wireless_interface_count; i++) { + if (!strcmp(detected_wireless_interfaces[i], intf)) { + return; + } + } + + // Add interface + vui_strncpy( + detected_wireless_interfaces[detected_wireless_interface_count], + intf, + sizeof(detected_wireless_interfaces[detected_wireless_interface_count])); + detected_wireless_interface_count++; +} + +static void populate_wireless_intf(void) +{ +#ifdef ANDROID + static const char *android_prefixes[] = {"wlan", "wifi", "swlan", "mlan", "ath"}; + static const size_t android_prefix_count = sizeof(android_prefixes) / sizeof(android_prefixes[0]); +#endif + struct dirent *dir; DIR *d = opendir("/sys/class/net"); char buf[1024]; - size_t cur = 0; + detected_wireless_interface_count = 0; if (d) { while ((dir = readdir(d)) != 0) { const char *intf = dir->d_name; @@ -80,14 +113,38 @@ static void populate_wireless_intf() snprintf(buf, sizeof(buf), "/sys/class/net/%s/wireless", intf); DIR *wifi_check = opendir(buf); if (wifi_check) { - vui_strncpy(detected_wireless_interfaces[cur], intf, sizeof(detected_wireless_interfaces[cur])); - cur++; + add_wireless_interface(intf); closedir(wifi_check); + } else { +#ifdef ANDROID + // Fallback heuristic approach for Android + for (size_t prefix = 0; prefix < android_prefix_count; prefix++) { + const char *p = android_prefixes[prefix]; + if (!strncmp(intf, p, strlen(p))) { + add_wireless_interface(intf); + break; + } + } +#endif } } } closedir(d); } + +#ifdef ANDROID + // Some Android releases may restrict /sys/class/net altogether. Probe + // heuristically. + for (size_t prefix = 0; prefix < android_prefix_count; prefix++) { + for (unsigned int index = 0; index < 8; index++) { + char intf[IF_NAMESIZE]; + snprintf(intf, sizeof(intf), "%s%u", android_prefixes[prefix], index); + if (if_nametoindex(intf)) { + add_wireless_interface(intf); + } + } + } +#endif } static void intf_pressed(vui_context_t *vui, int button, void *v) @@ -241,6 +298,7 @@ void vpi_menu_connection_and_return_to(vui_context_t *vui, int fade_fglayer, vui int is_local = vpi_config.server_address == VANILLA_ADDRESS_LOCAL; int is_via_server = vpi_config.server_address != VANILLA_ADDRESS_LOCAL; + int local_available = 1; const int lbl2_width = bkg_rect.w*2/3; const char *lbl2_txt; @@ -250,6 +308,15 @@ void vpi_menu_connection_and_return_to(vui_context_t *vui, int fade_fglayer, vui #else lbl2_txt = lang(VPI_LANG_NO_LOCAL_AVAILABLE); is_local = 0; + local_available = 0; +#endif + +#ifdef ANDROID + if (local_available && !vpi_android_has_root_access()) { + lbl2_txt = lang(VPI_LANG_ANDROID_ROOT_REQUIRED); + is_local = 0; + local_available = 0; + } #endif vui_label_create(vui, bkg_rect.x + bkg_rect.w/2 - lbl2_width/2, bkg_rect.y + lbl_margin + bkg_rect.h/8, lbl2_width, bkg_rect.h - lbl_margin - lbl_margin, lbl2_txt, vui_color_create(0.66f,0.66f,0.66f,1), VUI_FONT_SIZE_SMALL, fglayer); @@ -263,9 +330,7 @@ void vpi_menu_connection_and_return_to(vui_context_t *vui, int fade_fglayer, vui vui_button_update_checked(vui, local_btn, is_local); vui_button_update_checked(vui, via_server_btn, is_via_server); -#ifndef VANILLA_PIPE_AVAILABLE - vui_button_update_enabled(vui, local_btn, 0); -#endif + vui_button_update_enabled(vui, local_btn, local_available); // Back button vpi_menu_create_back_button(vui, fglayer, return_to_settings, (void *) (intptr_t) bglayer); diff --git a/gui/menu/menu_game.c b/gui/menu/menu_game.c index b6ef09c..848108e 100644 --- a/gui/menu/menu_game.c +++ b/gui/menu/menu_game.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,11 @@ #include "ui/ui_anim.h" #include "ui/ui_util.h" +#ifdef ANDROID +#include +#include "ui/ui_sdl_android.h" +#endif + #ifdef VANILLA_V4L2REQUEST_AVAILABLE #include #include @@ -242,6 +248,21 @@ static enum AVPixelFormat drm_get_format(struct AVCodecContext *s, const enum AV return AV_PIX_FMT_NONE; } +#ifdef ANDROID +static enum AVPixelFormat mediacodec_get_format(struct AVCodecContext *s, + const enum AVPixelFormat *fmt) +{ + while (*fmt != AV_PIX_FMT_NONE) { + if (*fmt == AV_PIX_FMT_MEDIACODEC) { + return *fmt; + } + fmt++; + } + + return AV_PIX_FMT_NONE; +} +#endif + typedef struct { const char *name; const AVCodec *codec; @@ -249,6 +270,9 @@ typedef struct { } hwdec_t; enum HwDecoderType { +#ifdef ANDROID + HWDEC_TYPE_MEDIACODEC, +#endif HWDEC_TYPE_NVDEC, HWDEC_TYPE_VAAPI, HWDEC_TYPE_V4L2REQUEST, @@ -356,27 +380,24 @@ int open_decoder(vpi_decode_state_t *s, hwdec_t *dec) s->codec_ctx->get_format = dec->get_format; } - const int BUILD_AVCC = 0; - if (BUILD_AVCC) { - uint8_t sps[100], pps[100]; - uint16_t sps_size = vanilla_generate_sps_params(sps, sizeof(sps)); - uint16_t pps_size = vanilla_generate_pps_params(pps, sizeof(pps)); - - const uint8_t *p_sps = sps; - const uint8_t *p_pps = pps; - - s->codec_ctx->extradata = av_malloc(1024); - s->codec_ctx->extradata_size = build_avcc( - s->codec_ctx->extradata, 1024, - &p_sps, &sps_size, 1, - &p_pps, &pps_size, 1, - 4 - ); + if (!strcmp(dec->codec->name, "h264_mediacodec")) { + // MediaCodec requires the AVCC/SPS/PPS upfront + uint8_t header[200]; + size_t header_size = vanilla_generate_h264_header(header, sizeof(header)); + s->codec_ctx->extradata = av_mallocz(header_size + AV_INPUT_BUFFER_PADDING_SIZE); + if (!s->codec_ctx->extradata) { + vpilog("Failed to allocate H.264 extradata\n"); + return VANILLA_ERR_GENERIC; + } + memcpy(s->codec_ctx->extradata, header, header_size); + s->codec_ctx->extradata_size = (int) header_size; + s->codec_ctx->width = 854; + s->codec_ctx->height = 480; } ffmpeg_err = avcodec_open2(s->codec_ctx, dec->codec, NULL); if (ffmpeg_err < 0) { - vpilog("Failed to open decoder: %i\n", ffmpeg_err); + vpilog("Failed to open decoder %s: %s (%i)\n", dec->name, av_err2str(ffmpeg_err), ffmpeg_err); return VANILLA_ERR_GENERIC; } @@ -394,6 +415,12 @@ int vpi_decode_init(vpi_decode_state_t *s) // Initialize decoding context, preferring hardware decoding when available hwdec_t decoders[HWDEC_TYPE_COUNT]; +#ifdef ANDROID + decoders[HWDEC_TYPE_MEDIACODEC].name = "MediaCodec"; + decoders[HWDEC_TYPE_MEDIACODEC].codec = avcodec_find_decoder_by_name("h264_mediacodec"); + decoders[HWDEC_TYPE_MEDIACODEC].get_format = mediacodec_get_format; +#endif + decoders[HWDEC_TYPE_NVDEC].name = "NVDEC"; decoders[HWDEC_TYPE_NVDEC].codec = avcodec_find_decoder_by_name("h264_cuvid"); decoders[HWDEC_TYPE_NVDEC].get_format = nvdec_get_format; @@ -418,6 +445,26 @@ int vpi_decode_init(vpi_decode_state_t *s) int r = VANILLA_ERR_GENERIC; if (!vpi_config.force_software_decode) { +#ifdef ANDROID + // MediaCodec renders decoded buffers directly into the SurfaceTexture + // owned by SDL's external OES texture. + if (r != VANILLA_SUCCESS && vui_sdl_android_get_video_surface()) { + vpi_decode_exit(s); + s->hw_device_ctx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_MEDIACODEC); + if (s->hw_device_ctx) { + AVHWDeviceContext *device_ctx = (AVHWDeviceContext *) s->hw_device_ctx->data; + AVMediaCodecDeviceContext *mediacodec_ctx = (AVMediaCodecDeviceContext *) device_ctx->hwctx; + mediacodec_ctx->surface = vui_sdl_android_get_video_surface(); + + ffmpeg_err = av_hwdevice_ctx_init(s->hw_device_ctx); + if (ffmpeg_err >= 0) { + r = open_decoder(s, &decoders[HWDEC_TYPE_MEDIACODEC]); + } else { + vpilog("Failed to initialize MediaCodec device: %s\n", av_err2str(ffmpeg_err)); + } + } + } +#endif #ifdef VANILLA_CUDA_AVAILABLE // See if we can create an NVDEC context (most NVIDIA GPUs) if (r != VANILLA_SUCCESS) { diff --git a/gui/pipemgmt.c b/gui/pipemgmt.c index d994a40..8814c1c 100644 --- a/gui/pipemgmt.c +++ b/gui/pipemgmt.c @@ -1,3 +1,5 @@ +#define _GNU_SOURCE + #include "pipemgmt.h" #ifdef VANILLA_PIPE_AVAILABLE @@ -15,6 +17,11 @@ #include #include +#ifdef ANDROID +#include +#include +#endif + #include "config.h" #include "platform.h" @@ -193,7 +200,7 @@ void *vpi_pipe_log_thread(void *data) if (buf[buf_read] == '\n') { // Print line and reset buf[buf_read + 1] = '\0'; - vpilog(buf); + vpilog("%s", buf); buf_read = 0; } else { @@ -270,9 +277,6 @@ void vpi_cancel_pw() potential_pipe_pid = -1; close(in_pipes[1]); close(err_pipes[0]); - - close(err_pipes[1]); - close(in_pipes[0]); } vpi_close_polkit_session(); } @@ -290,19 +294,28 @@ int vpi_start_epilog() int ret = VANILLA_ERR_GENERIC; char ready_buf[500]; - memset(ready_buf, 0, sizeof(ready_buf)); while (1) { size_t read_count = 0; - char c; - while (read_count < sizeof(ready_buf) && read(err_pipes[0], &c, 1) == 1) { + int reached_eof = 0; + while (read_count + 1 < sizeof(ready_buf)) { + char c; + ssize_t read_size = read(err_pipes[0], &c, 1); + if (read_size < 0 && errno == EINTR) { + continue; + } + if (read_size != 1) { + reached_eof = 1; + break; + } if (c == '\n') { break; } - ready_buf[read_count] = c; - read_count++; + ready_buf[read_count++] = c; } - if (!memcmp(ready_buf, "READY", 5)) { + ready_buf[read_count] = 0; + + if (!strcmp(ready_buf, "READY")) { ret = VANILLA_SUCCESS; pipe_input = in_pipes[1]; @@ -313,24 +326,27 @@ int vpi_start_epilog() pipe_pid = potential_pipe_pid; break; - } else { - // vpilog("GOT INVALID SIGNAL: %.*s\n", sizeof(ready_buf), ready_buf); - vpilog("%.*s\n", sizeof(ready_buf), ready_buf); + } - if (potential_pipe_pid != -1) { - kill(potential_pipe_pid, SIGKILL); - potential_pipe_pid = -1; - } - - close(in_pipes[1]); - close(err_pipes[0]); + if (read_count) { + vpilog("%s\n", ready_buf); + } + if (reached_eof) { break; } } - close(err_pipes[1]); - close(in_pipes[0]); + if (ret != VANILLA_SUCCESS) { + if (potential_pipe_pid != -1) { + kill(potential_pipe_pid, SIGKILL); + waitpid(potential_pipe_pid, NULL, 0); + potential_pipe_pid = -1; + } + + close(in_pipes[1]); + close(err_pipes[0]); + } #ifdef VANILLA_POLKIT_AVAILABLE vpi_close_polkit_session(); @@ -437,30 +453,70 @@ int vpi_start_pipe() setsid(); - // Get current working directory + // Execute process (this will replace the running code) +#ifdef ANDROID + // HACK: Use dladdr to get the path of us + Dl_info info; + if (!dladdr((void *) &vpi_start_pipe, &info) || !info.dli_fname) { + vpilog("Could not get running path of current Android executable\n"); + return VANILLA_ERR_GENERIC; + } + + // Copy path into our own string + char our_path[PATH_MAX]; + if (snprintf(our_path, sizeof(our_path), "%s", info.dli_fname) >= sizeof(our_path)) { + vpilog("Android library path too long - this is a bug, tell maintainers\n"); + return VANILLA_ERR_GENERIC; + } + + // Get dirname of string and add libvanilla_pipe.so + char *library_dir = dirname(our_path); + char pipe_path[PATH_MAX]; + if (snprintf(pipe_path, sizeof(pipe_path), "%s/libvanilla_pipe.so", library_dir) >= sizeof(pipe_path)) { + vpilog("Android pipe path too long - this is a bug, tell maintainers\n"); + return VANILLA_ERR_GENERIC; + } + + char command[PATH_MAX + 256]; + snprintf(command, sizeof(command), "exec '%s' -local '%s'", pipe_path, vpi_config.wireless_interface); + r = execlp("su", "su", "-c", command, (const char *) 0); +#else char exe[4096]; - ssize_t link_len = readlink("/proc/self/exe", exe, sizeof(exe)); + ssize_t link_len = readlink("/proc/self/exe", exe, sizeof(exe) - 1); + if (link_len <= 0) { + _exit(1); + } exe[link_len] = 0; dirname(exe); strcat(exe, "/vanilla-pipe"); - // Execute process (this will replace the running code) #ifdef VANILLA_POLKIT_AVAILABLE const char *pkexec = "pkexec"; r = execlp(pkexec, pkexec, exe, "-local", vpi_config.wireless_interface, (const char *) 0); #else - r = execlp(exe, exe, "-local", vpi_config.wireless_interface, (const char *) 0); + r = execlp(exe, exe, "-local", vpi_config.wireless_interface, (const char *) 0); +#endif + #endif // Handle failure to execute, use _exit so we don't interfere with the host _exit(1); } else if (pid < 0) { // Fork error + close(in_pipes[0]); + close(in_pipes[1]); + close(err_pipes[0]); + close(err_pipes[1]); return VANILLA_ERR_GENERIC; } else { // Continuation of parent int ret = VANILLA_ERR_GENERIC; + // Only the child writes stderr and reads stdin. Closing the parent's + // copies here lets startup detect a child that exits before READY. + close(err_pipes[1]); + close(in_pipes[0]); + struct sigaction sa; sa.sa_handler = sigint_handler; sigemptyset(&sa.sa_mask); diff --git a/gui/platform_android.c b/gui/platform_android.c new file mode 100644 index 0000000..873e68c --- /dev/null +++ b/gui/platform_android.c @@ -0,0 +1,42 @@ +#include "platform_android.h" + +#include +#include +#include +#include + +// Check for whether we have root access on Android (required to run vanilla-pipe) +int vpi_android_has_root_access(void) +{ + static int cached_result = -1; + if (cached_result >= 0) { + return cached_result; + } + + // Attempt to run `id -u` as superuser - if it works, it'll return "0" meaning root + FILE *command = popen("su -c 'id -u' 2>/dev/null", "r"); + if (!command) { + cached_result = 0; + return cached_result; + } + + // See if command indeed returned "0" + int reported_root = 0; + char line[128]; + while (fgets(line, sizeof(line), command)) { + char *end = NULL; + errno = 0; + long uid = strtol(line, &end, 10); + while (end && isspace((unsigned char) *end)) { + end++; + } + if (!errno && end != line && end && !*end && uid == 0) { + reported_root = 1; + } + } + + // Clean up, cache and return result + int status = pclose(command); + cached_result = reported_root && status >= 0 && WIFEXITED(status) && WEXITSTATUS(status) == 0; + return cached_result; +} diff --git a/gui/platform_android.h b/gui/platform_android.h new file mode 100644 index 0000000..f18d42b --- /dev/null +++ b/gui/platform_android.h @@ -0,0 +1,6 @@ +#ifndef VANILLA_PI_PLATFORM_ANDROID_H +#define VANILLA_PI_PLATFORM_ANDROID_H + +int vpi_android_has_root_access(void); + +#endif // VANILLA_PI_PLATFORM_ANDROID_H diff --git a/gui/ui/ui_sdl.c b/gui/ui/ui_sdl.c index 4ad0938..6e5cdee 100644 --- a/gui/ui/ui_sdl.c +++ b/gui/ui/ui_sdl.c @@ -1,7 +1,6 @@ #include "ui_sdl.h" #include -#include #include #include #include @@ -15,6 +14,11 @@ #include #include +#ifdef ANDROID +#include +#include "ui_sdl_android.h" +#endif + #ifdef VANILLA_HAS_EGL #include #include @@ -51,6 +55,11 @@ #define AUDIO_BUFFER_SIZE 1664 // Wii U never deviates from this so we can hardcode it #define AUDIO_BUFFER_COUNT 4 +// Our tick rate is 180Hz, which matches the original gamepad input rate. +// Our render tick rate is 60Hz (or 1/3 the input tick rate). +#define VUI_INPUT_TICK_RATE 180 +#define VUI_RENDER_TICK_DIVISOR 3 + typedef struct { SDL_Texture *texture; int w; @@ -80,12 +89,25 @@ typedef struct { SDL_GameController *controller; SDL_GameController *controller_gyros; SDL_Texture *game_tex; +#ifdef ANDROID + SDL_Texture *android_video_tex; + float android_video_transform[16]; +#endif int last_shown_toast; SDL_Texture *toast_tex; struct timeval toast_expiry; AVFrame *frame; AVFrame *held_frame; // keeps the displayed dmabuf alive until the next frame replaces it uint64_t present_frame_sequence; + Uint64 update_tick_origin; + Uint64 update_tick_frequency; + uint64_t update_tick; +#ifdef VANILLA_DRM_AVAILABLE + int fast_drm_active; +#endif +#ifdef ANDROID + int android_video_ready; +#endif SDL_Texture *pw_tex; uint8_t audio_buffer[AUDIO_BUFFER_COUNT][AUDIO_BUFFER_SIZE]; @@ -661,6 +683,15 @@ int vui_sdl_event_thread(void *data) case SDL_TEXTEDITING: vpilog("text editing!\n"); break; + case SDL_APP_WILLENTERBACKGROUND: + // On mobile, if the user is about to switch away from us, + // disconnect from the console so it doesn't remain burning up + // resources in the background + if (vui_game_mode_get(vui)) { + // Send shutdown signal + vpi_game_shutdown(); + } + break; } } // } @@ -707,6 +738,14 @@ int vui_init_sdl(vui_context_t *ctx, int fullscreen) // SDL_SetHintWithPriority("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD", "0", SDL_HINT_OVERRIDE); SDL_SetHintWithPriority(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, "", SDL_HINT_OVERRIDE); +#ifdef ANDROID + // Force SDL to use landscape orientations only + SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight"); + + // Force opengles2 for hardware decode interoperability + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengles2"); +#endif + // Force EGL when using X11 so that VAAPI works SDL_SetHint(SDL_HINT_VIDEO_X11_FORCE_EGL, "1"); @@ -873,6 +912,13 @@ int vui_init_sdl(vui_context_t *ctx, int fullscreen) sdl_ctx->game_tex = 0; +#ifdef ANDROID + sdl_ctx->android_video_tex = NULL; + if (vui_sdl_android_create_video_texture(sdl_ctx->renderer, ctx->screen_width, ctx->screen_height, &sdl_ctx->android_video_tex) < 0) { + vpilog("Android hardware decoding unavailable, falling back to software\n"); + } +#endif + sdl_ctx->background = 0; sdl_ctx->last_shown_toast = 0; sdl_ctx->toast_tex = 0; @@ -937,6 +983,13 @@ void vui_close_sdl(vui_context_t *ctx) } SDL_DestroyTexture(sdl_ctx->toast_tex); +#ifdef ANDROID + vui_sdl_android_destroy_video_texture(); + if (sdl_ctx->game_tex == sdl_ctx->android_video_tex) { + sdl_ctx->game_tex = 0; + } + SDL_DestroyTexture(sdl_ctx->android_video_tex); +#endif if (sdl_ctx->game_tex) SDL_DestroyTexture(sdl_ctx->game_tex); SDL_DestroyTexture(sdl_ctx->background); @@ -1270,28 +1323,43 @@ void vui_sdl_draw_background(vui_context_t *ctx, SDL_Renderer *renderer) } } +static SDL_Texture *vui_sdl_get_layer_texture(vui_context_t *ctx, + SDL_Renderer *renderer, + int layer) +{ + vui_sdl_context_t *sdl_ctx = (vui_sdl_context_t *) ctx->platform_data; + if (sdl_ctx->layer_data[layer]) { + return sdl_ctx->layer_data[layer]; + } + + sdl_ctx->layer_data[layer] = SDL_CreateTexture( + renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET, + ctx->screen_width, ctx->screen_height); + if (!sdl_ctx->layer_data[layer]) { + return NULL; + } + + SDL_BlendMode bm = SDL_ComposeCustomBlendMode( + SDL_BLENDFACTOR_ONE, + SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + SDL_BLENDOPERATION_ADD, + SDL_BLENDFACTOR_ONE, + SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + SDL_BLENDOPERATION_ADD + ); + + SDL_SetRenderDrawBlendMode(renderer, bm); + SDL_SetTextureBlendMode(sdl_ctx->layer_data[layer], bm); + return sdl_ctx->layer_data[layer]; +} + void vui_draw_sdl(vui_context_t *ctx, SDL_Renderer *renderer) { vui_sdl_context_t *sdl_ctx = (vui_sdl_context_t *) ctx->platform_data; for (int layer = 0; layer < ctx->layers; layer++) { - if (!sdl_ctx->layer_data[layer]) { - // Create a new layer here - sdl_ctx->layer_data[layer] = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET, ctx->screen_width, ctx->screen_height); - - SDL_BlendMode bm = SDL_ComposeCustomBlendMode( - SDL_BLENDFACTOR_ONE, - SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, - SDL_BLENDOPERATION_ADD, - SDL_BLENDFACTOR_ONE, - SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, - SDL_BLENDOPERATION_ADD - ); - - SDL_SetRenderDrawBlendMode(sdl_ctx->renderer, bm); - SDL_SetTextureBlendMode(sdl_ctx->layer_data[layer], bm); - } - SDL_SetRenderTarget(renderer, sdl_ctx->layer_data[layer]); + SDL_Texture *layer_texture = vui_sdl_get_layer_texture(ctx, renderer, layer); + SDL_SetRenderTarget(renderer, layer_texture); if (layer == 0 && ctx->background_enabled) { // Draw background @@ -1855,20 +1923,108 @@ int get_texture_from_drm_prime_frame(vui_sdl_context_t *sdl_ctx, AVFrame *f) #endif } +#ifdef ANDROID +static SDL_FPoint transform_android_texcoord(const float matrix[16], + float s, float t) +{ + // Flip Y + t = 1.0f - t; + SDL_FPoint point = { + matrix[0] * s + matrix[4] * t + matrix[12], + matrix[1] * s + matrix[5] * t + matrix[13] + }; + return point; +} + +static int render_android_video_texture(vui_sdl_context_t *sdl_ctx, + int width, int height) +{ + const float *matrix = sdl_ctx->android_video_transform; + const SDL_Color white = { 255, 255, 255, 255 }; + SDL_Vertex vertices[4] = { + { { 0.0f, 0.0f }, white, transform_android_texcoord(matrix, 0.0f, 0.0f) }, + { { (float) width, 0.0f }, white, transform_android_texcoord(matrix, 1.0f, 0.0f) }, + { { (float) width, (float) height }, white, transform_android_texcoord(matrix, 1.0f, 1.0f) }, + { { 0.0f, (float) height }, white, transform_android_texcoord(matrix, 0.0f, 1.0f) } + }; + static const int indices[] = { 0, 1, 2, 0, 2, 3 }; + + if (SDL_RenderGeometry(sdl_ctx->renderer, sdl_ctx->android_video_tex, vertices, 4, indices, 6) < 0) { + vpilog("Failed to render Android external texture: %s\n", SDL_GetError()); + return 0; + } + + return 1; +} +#endif + +static Uint64 vui_sdl_tick_deadline(const vui_sdl_context_t *sdl_ctx, + uint64_t tick) +{ + Uint64 whole_seconds = tick / VUI_INPUT_TICK_RATE; + Uint64 partial_ticks = tick % VUI_INPUT_TICK_RATE; + return sdl_ctx->update_tick_origin + + whole_seconds * sdl_ctx->update_tick_frequency + + partial_ticks * sdl_ctx->update_tick_frequency + / VUI_INPUT_TICK_RATE; +} + +static void vui_sdl_wait_for_next_tick(vui_sdl_context_t *sdl_ctx) +{ + sdl_ctx->update_tick++; + + Uint64 now = SDL_GetPerformanceCounter(); + Uint64 deadline = vui_sdl_tick_deadline(sdl_ctx, sdl_ctx->update_tick); + if (now >= deadline) { + Uint64 elapsed = now - sdl_ctx->update_tick_origin; + uint64_t elapsed_ticks = + (elapsed / sdl_ctx->update_tick_frequency) * VUI_INPUT_TICK_RATE + + (elapsed % sdl_ctx->update_tick_frequency) + * VUI_INPUT_TICK_RATE / sdl_ctx->update_tick_frequency; + sdl_ctx->update_tick = elapsed_ticks + 1; + deadline = vui_sdl_tick_deadline(sdl_ctx, sdl_ctx->update_tick); + } + + pthread_mutex_lock(&vpi_present_frame_mutex); + while ((now = SDL_GetPerformanceCounter()) < deadline) { + Uint64 remaining = deadline - now; + uint64_t wait_ns = remaining * 1000000000ULL + / sdl_ctx->update_tick_frequency; + if (!wait_ns) + wait_ns = 1; + + struct timespec timeout; + clock_gettime(CLOCK_REALTIME, &timeout); + timeout.tv_sec += wait_ns / 1000000000ULL; + timeout.tv_nsec += wait_ns % 1000000000ULL; + if (timeout.tv_nsec >= 1000000000L) { + timeout.tv_sec++; + timeout.tv_nsec -= 1000000000L; + } + + pthread_cond_timedwait(&vpi_present_frame_cond, &vpi_present_frame_mutex, &timeout); + } + pthread_mutex_unlock(&vpi_present_frame_mutex); +} + // Rendering/main thread int vui_update_sdl(vui_context_t *vui) { - static Uint32 last_update_time = 0; - vui_sdl_event_thread(vui); vui_sdl_context_t *sdl_ctx = (vui_sdl_context_t *) vui->platform_data; + if (!sdl_ctx->update_tick_frequency) { + sdl_ctx->update_tick_frequency = SDL_GetPerformanceFrequency(); + sdl_ctx->update_tick_origin = SDL_GetPerformanceCounter(); + } + int render_tick = sdl_ctx->update_tick % VUI_RENDER_TICK_DIVISOR == 0; + SDL_Renderer *renderer = sdl_ctx->renderer; vui_update(vui); - SDL_Texture *main_tex; + SDL_Texture *main_tex = NULL; #ifdef VANILLA_DRM_AVAILABLE static vanilla_drm_ctx_t *drm_ctx = NULL; @@ -1877,13 +2033,14 @@ int vui_update_sdl(vui_context_t *vui) static vui_cuda_context_t *cuda_ctx = NULL; #endif // VANILLA_CUDA_AVAILABLE - int handle_final_blit = 1; + int sdl_frame_ready = 0; if (!vui->game_mode) { #ifdef VANILLA_DRM_AVAILABLE if (drm_ctx) { vui_sdl_drm_free(&drm_ctx); // will set to null } + sdl_ctx->fast_drm_active = 0; #endif // VANILLA_DRM_AVAILABLE #ifdef VANILLA_CUDA_AVAILABLE if (cuda_ctx) { @@ -1894,30 +2051,33 @@ int vui_update_sdl(vui_context_t *vui) } #endif // VANILLA_CUDA_AVAILABLE - // Draw vui to a custom texture - vui_draw_sdl(vui, renderer); + if (render_tick) { + // Draw vui to a custom texture + vui_draw_sdl(vui, renderer); - // Flatten layers - int el[MAX_BUTTON_COUNT]; - int el_count = 0; - for (int i = 0; i < vui->layers; i++) { - if (vui->layer_enabled[i]) { - el[el_count] = i; - el_count++; + // Flatten layers + int el[MAX_BUTTON_COUNT]; + int el_count = 0; + for (int i = 0; i < vui->layers; i++) { + if (vui->layer_enabled[i]) { + el[el_count] = i; + el_count++; + } } - } - for (int i = el_count - 1; i > 0; i--) { - SDL_Texture *bg = sdl_ctx->layer_data[el[i-1]]; - SDL_Texture *fg = sdl_ctx->layer_data[el[i]]; + for (int i = el_count - 1; i > 0; i--) { + SDL_Texture *bg = sdl_ctx->layer_data[el[i-1]]; + SDL_Texture *fg = sdl_ctx->layer_data[el[i]]; - SDL_SetRenderTarget(renderer, bg); - SDL_SetTextureColorMod(fg, vui->layer_opacity[i] * 0xFF, vui->layer_opacity[i] * 0xFF, vui->layer_opacity[i] * 0xFF); - SDL_SetTextureAlphaMod(fg, vui->layer_opacity[i] * 0xFF); - SDL_RenderCopy(renderer, fg, NULL, NULL); + SDL_SetRenderTarget(renderer, bg); + SDL_SetTextureColorMod(fg, vui->layer_opacity[i] * 0xFF, vui->layer_opacity[i] * 0xFF, vui->layer_opacity[i] * 0xFF); + SDL_SetTextureAlphaMod(fg, vui->layer_opacity[i] * 0xFF); + SDL_RenderCopy(renderer, fg, NULL, NULL); + } + + main_tex = sdl_ctx->layer_data[0]; + sdl_frame_ready = main_tex != NULL; } - - main_tex = sdl_ctx->layer_data[0]; } else { pthread_mutex_lock(&vpi_present_frame_mutex); if (vpi_present_frame @@ -1948,14 +2108,15 @@ int vui_update_sdl(vui_context_t *vui) SDL_RenderPresent(renderer); } } - if (drm_ctx && - vui_sdl_drm_present(drm_ctx, sdl_ctx->frame)) { - handle_final_blit = 0; + if (drm_ctx && vui_sdl_drm_present(drm_ctx, sdl_ctx->frame)) { + sdl_ctx->fast_drm_active = 1; } else { + sdl_ctx->fast_drm_active = 0; get_texture_from_drm_prime_frame(sdl_ctx, sdl_ctx->frame); } } else { + sdl_ctx->fast_drm_active = 0; get_texture_from_drm_prime_frame(sdl_ctx, sdl_ctx->frame); } #endif // VANILLA_DRM_AVAILABLE @@ -1989,6 +2150,22 @@ int vui_update_sdl(vui_context_t *vui) } break; } +#ifdef ANDROID + case AV_PIX_FMT_MEDIACODEC: + { + AVMediaCodecBuffer *buffer = (AVMediaCodecBuffer *) sdl_ctx->frame->data[3]; + if (!sdl_ctx->android_video_tex || !buffer) { + vpilog("MediaCodec produced a frame without an Android video surface\n"); + break; + } + if (av_mediacodec_release_buffer(buffer, 1) < 0) { + vpilog("Failed to render MediaCodec output buffer\n"); + break; + } + sdl_ctx->game_tex = sdl_ctx->android_video_tex; + break; + } +#endif case AV_PIX_FMT_YUV420P: get_texture_from_cpu_frame(sdl_ctx, sdl_ctx->frame); break; @@ -1996,23 +2173,51 @@ int vui_update_sdl(vui_context_t *vui) av_frame_unref(sdl_ctx->held_frame); av_frame_move_ref(sdl_ctx->held_frame, sdl_ctx->frame); + } - if (sdl_ctx->game_tex) { - if (handle_final_blit) { - main_tex = sdl_ctx->layer_data[0]; - SDL_SetRenderTarget(renderer, main_tex); - SDL_RenderCopy(renderer, sdl_ctx->game_tex, 0, 0); - } else { - main_tex = sdl_ctx->game_tex; +#ifdef ANDROID + /* MediaCodec publishes into SurfaceTexture asynchronously. Poll it at + * the input rate, but only draw the retained image on render ticks. */ + if (sdl_ctx->game_tex == sdl_ctx->android_video_tex + && SDL_RenderFlush(renderer) == 0 + && vui_sdl_android_update_video_texture( + sdl_ctx->android_video_transform)) { + sdl_ctx->android_video_ready = 1; + } +#endif + + int sdl_handles_final_blit = 1; +#ifdef VANILLA_DRM_AVAILABLE + sdl_handles_final_blit = !sdl_ctx->fast_drm_active; +#endif + if (render_tick && sdl_handles_final_blit) { + main_tex = vui_sdl_get_layer_texture(vui, renderer, 0); + if (main_tex) { + SDL_SetRenderTarget(renderer, main_tex); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + + if (sdl_ctx->game_tex) { +#ifdef ANDROID + if (sdl_ctx->game_tex == sdl_ctx->android_video_tex) { + if (sdl_ctx->android_video_ready) { + render_android_video_texture( + sdl_ctx, vui->screen_width, + vui->screen_height); + } + } else +#endif + { + SDL_RenderCopy(renderer, sdl_ctx->game_tex, NULL, + NULL); + } } + sdl_frame_ready = 1; } - } else { - // Didn't get a frame, nothing to be done - handle_final_blit = 0; } } - if (handle_final_blit) { + if (sdl_frame_ready) { // Draw toast on screen if necessary const int TOAST_PADDING = 12; int cur_toast; @@ -2120,37 +2325,9 @@ int vui_update_sdl(vui_context_t *vui) SDL_RenderPresent(renderer); } - // Frame limiter to save CPU cycles - const Uint32 target = 5; // No need to update faster than 200Hz (gamepad polls at 180Hz, but this is easier to calculate) - Uint32 frame_delta = SDL_GetTicks() - last_update_time; - if (frame_delta < target) { - Uint32 wait_ms = target - frame_delta; - if (vui->game_mode) { - struct timespec deadline; - clock_gettime(CLOCK_REALTIME, &deadline); - deadline.tv_nsec += (long) wait_ms * 1000000; - if (deadline.tv_nsec >= 1000000000) { - deadline.tv_sec++; - deadline.tv_nsec -= 1000000000; - } - - pthread_mutex_lock(&vpi_present_frame_mutex); - while (sdl_ctx->present_frame_sequence == vpi_present_frame_sequence) { - int err = pthread_cond_timedwait( - &vpi_present_frame_cond, - &vpi_present_frame_mutex, - &deadline - ); - if (err == ETIMEDOUT) { - break; - } - } - pthread_mutex_unlock(&vpi_present_frame_mutex); - } else { - SDL_Delay(wait_ms); - } + if (!vui->quit) { + vui_sdl_wait_for_next_tick(sdl_ctx); } - last_update_time = SDL_GetTicks(); return !vui->quit; } diff --git a/gui/ui/ui_sdl_android.c b/gui/ui/ui_sdl_android.c new file mode 100644 index 0000000..1638657 --- /dev/null +++ b/gui/ui/ui_sdl_android.c @@ -0,0 +1,195 @@ +#include "ui_sdl_android.h" + +#include +#include +#include +#include + +#include "platform.h" + +static jclass video_class; +static jobject video_surface; +static jmethodID create_surface_method; +static jmethodID update_texture_method; +static jmethodID destroy_surface_method; + +static int handle_jni_exception(JNIEnv *env, const char *operation) +{ + if (!(*env)->ExceptionCheck(env)) { + return 0; + } + + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + vpilog("Android video surface: %s failed\n", operation); + return -1; +} + +static int init_jni(JNIEnv *env) +{ + if (video_class) { + return 0; + } + + jobject activity = (jobject) SDL_AndroidGetActivity(); + if (!activity) { + vpilog("SDL_AndroidGetActivity returned NULL\n"); + return -1; + } + + jclass local_class = (*env)->GetObjectClass(env, activity); + (*env)->DeleteLocalRef(env, activity); + if (!local_class || handle_jni_exception(env, "looking up activity class") < 0) { + return -1; + } + + video_class = (jclass) (*env)->NewGlobalRef(env, local_class); + (*env)->DeleteLocalRef(env, local_class); + if (!video_class) { + vpilog("Android video surface: could not retain activity class\n"); + return -1; + } + + create_surface_method = (*env)->GetStaticMethodID(env, video_class, "createVideoSurface", "(III)Landroid/view/Surface;"); + update_texture_method = (*env)->GetStaticMethodID(env, video_class, "updateVideoSurfaceTexture", "()[F"); + destroy_surface_method = (*env)->GetStaticMethodID(env, video_class, "destroyVideoSurface", "()V"); + if (!create_surface_method || !update_texture_method || !destroy_surface_method || + handle_jni_exception(env, "looking up video surface methods") < 0) { + (*env)->DeleteGlobalRef(env, video_class); + video_class = NULL; + return -1; + } + + JavaVM *vm = NULL; + if ((*env)->GetJavaVM(env, &vm) != JNI_OK || + av_jni_set_java_vm(vm, NULL) < 0) { + vpilog("Android video surface: could not register the Java VM with FFmpeg\n"); + (*env)->DeleteGlobalRef(env, video_class); + video_class = NULL; + create_surface_method = NULL; + update_texture_method = NULL; + destroy_surface_method = NULL; + return -1; + } + + return 0; +} + +int vui_sdl_android_create_video_texture(SDL_Renderer *renderer, int width, int height, SDL_Texture **texture) +{ + JNIEnv *env = (JNIEnv *) SDL_AndroidGetJNIEnv(); + SDL_RendererInfo info; + GLint texture_name = 0; + SDL_Texture *external_texture = NULL; + + if (!env || !renderer || !texture || init_jni(env) < 0) { + goto die; + } + + if (SDL_GetRendererInfo(renderer, &info) < 0) { + vpilog("Failed to determine SDL Android renderer info\n"); + goto die; + } + + if (SDL_strcmp(info.name, "opengles2") != 0) { + vpilog("Android video decoding requires opengles2 (currently using %s)\n", info.name); + goto die; + } + + external_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_EXTERNAL_OES, SDL_TEXTUREACCESS_STATIC, width, height); + if (!external_texture) { + vpilog("Failed to create Android external texture: %s\n", SDL_GetError()); + goto die; + } + + SDL_RenderFlush(renderer); + if (SDL_GL_BindTexture(external_texture, NULL, NULL) < 0) { + vpilog("Failed to bind Android external texture: %s\n", SDL_GetError()); + goto die_and_destroy_texture; + } + + glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texture_name); + SDL_GL_UnbindTexture(external_texture); + if (!texture_name) { + vpilog("Failed to retrieve GL object from Android external texture\n"); + goto die_and_destroy_texture; + } + + jobject local_surface = (*env)->CallStaticObjectMethod(env, video_class, create_surface_method, (jint) texture_name, (jint) width, (jint) height); + if (handle_jni_exception(env, "creating SurfaceTexture") < 0 || !local_surface) { + goto die_and_destroy_texture; + } + + video_surface = (*env)->NewGlobalRef(env, local_surface); + (*env)->DeleteLocalRef(env, local_surface); + if (!video_surface) { + (*env)->CallStaticVoidMethod(env, video_class, destroy_surface_method); + handle_jni_exception(env, "destroying unretained SurfaceTexture"); + goto die_and_destroy_texture; + } + + *texture = external_texture; + vpilog("Android hardware video decode surface initialized\n"); + return 0; + +die_and_destroy_texture: + SDL_DestroyTexture(external_texture); + +die: + return -1; +} + +void *vui_sdl_android_get_video_surface(void) +{ + return video_surface; +} + +int vui_sdl_android_update_video_texture(float transform[16]) +{ + JNIEnv *env = (JNIEnv *) SDL_AndroidGetJNIEnv(); + if (!env || !video_class || !video_surface) { + return 0; + } + + jfloatArray matrix = (jfloatArray) (*env)->CallStaticObjectMethod(env, video_class, update_texture_method); + if (handle_jni_exception(env, "updating SurfaceTexture") < 0 || !matrix) { + return 0; + } + + if ((*env)->GetArrayLength(env, matrix) != 16) { + (*env)->DeleteLocalRef(env, matrix); + vpilog("Android video surface returned an invalid transform matrix\n"); + return 0; + } + + (*env)->GetFloatArrayRegion(env, matrix, 0, 16, transform); + (*env)->DeleteLocalRef(env, matrix); + if (handle_jni_exception(env, "reading SurfaceTexture transform") < 0) { + return 0; + } + return 1; +} + +void vui_sdl_android_destroy_video_texture(void) +{ + JNIEnv *env = (JNIEnv *) SDL_AndroidGetJNIEnv(); + if (!env) { + return; + } + + if (video_class && destroy_surface_method) { + (*env)->CallStaticVoidMethod(env, video_class, destroy_surface_method); + handle_jni_exception(env, "destroying SurfaceTexture"); + } + if (video_surface) { + (*env)->DeleteGlobalRef(env, video_surface); + video_surface = NULL; + } + if (video_class) { + (*env)->DeleteGlobalRef(env, video_class); + video_class = NULL; + } + create_surface_method = NULL; + update_texture_method = NULL; + destroy_surface_method = NULL; +} diff --git a/gui/ui/ui_sdl_android.h b/gui/ui/ui_sdl_android.h new file mode 100644 index 0000000..ce7af18 --- /dev/null +++ b/gui/ui/ui_sdl_android.h @@ -0,0 +1,12 @@ +#ifndef VANILLA_UI_SDL_ANDROID_H +#define VANILLA_UI_SDL_ANDROID_H + +#include + +int vui_sdl_android_create_video_texture(SDL_Renderer *renderer, int width, + int height, SDL_Texture **texture); +void *vui_sdl_android_get_video_surface(void); +int vui_sdl_android_update_video_texture(float transform[16]); +void vui_sdl_android_destroy_video_texture(void); + +#endif diff --git a/lib/gamepad/gamepad.c b/lib/gamepad/gamepad.c index 293eb9d..6eaceab 100644 --- a/lib/gamepad/gamepad.c +++ b/lib/gamepad/gamepad.c @@ -37,38 +37,24 @@ static inline int skterr() #endif } -void create_sockaddr(sockaddr_u *addr, size_t *size, in_addr_t inaddr, uint16_t port, int local, int delete) +static void create_sockaddr(struct sockaddr_in *addr, size_t *size, in_addr_t inaddr, uint16_t port) { -#ifndef _WIN32 - if (local) { - memset(&addr->un, 0, sizeof(addr->un)); - addr->un.sun_family = AF_UNIX; - snprintf(addr->un.sun_path, sizeof(addr->un.sun_path) - 1, VANILLA_PIPE_LOCAL_SOCKET, port); - if (delete) - unlink(addr->un.sun_path); + memset(addr, 0, sizeof(*addr)); + addr->sin_family = AF_INET; + addr->sin_port = htons(port); + addr->sin_addr.s_addr = inaddr; - if (size) *size = sizeof(struct sockaddr_un); - } else { -#endif - memset(&addr->in, 0, sizeof(addr->in)); - addr->in.sin_family = AF_INET; - addr->in.sin_port = htons(port); - addr->in.sin_addr.s_addr = inaddr; - - if (size) *size = sizeof(struct sockaddr_in); -#ifndef _WIN32 - } -#endif + if (size) *size = sizeof(*addr); } -void create_server_sockaddr(sockaddr_u *addr, size_t *size, uint16_t port, int delete) +void create_server_sockaddr(struct sockaddr_in *addr, size_t *size, uint16_t port) { // The Wii U always places itself at this address in_addr_t ip = (SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) ? inet_addr("192.168.1.10") : SERVER_ADDRESS; - return create_sockaddr(addr, size, ip, port, 0, delete); + create_sockaddr(addr, size, ip, port); } -void send_to_sockaddr(int fd, const void *data, size_t data_size, const sockaddr_u *sockaddr, size_t sockaddr_size) +void send_to_sockaddr(int fd, const void *data, size_t data_size, const struct sockaddr_in *sockaddr, size_t sockaddr_size) { ssize_t sent = sendto(fd, data, data_size, 0, (const struct sockaddr *) sockaddr, sockaddr_size); if (sent == -1) { @@ -81,12 +67,12 @@ void send_to_sockaddr(int fd, const void *data, size_t data_size, const sockaddr void send_to_console(int fd, const void *data, size_t data_size, uint16_t port) { - sockaddr_u addr; + struct sockaddr_in addr; size_t addr_size; in_port_t console_port = port - 100; - create_server_sockaddr(&addr, &addr_size, console_port, 0); + create_server_sockaddr(&addr, &addr_size, console_port); send_to_sockaddr(fd, data, data_size, &addr, addr_size); } @@ -106,15 +92,14 @@ void set_socket_rcvtimeo(int skt, uint64_t microseconds) int create_socket(int *socket_out, in_port_t port, int pipe) { - sockaddr_u addr; + struct sockaddr_in addr; size_t addr_size; - int is_pipe_and_local = (pipe && SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL); - int domain = is_pipe_and_local ? AF_UNIX : AF_INET; + in_addr_t bind_address = (pipe && SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) ? htonl(INADDR_LOOPBACK) : INADDR_ANY; - create_sockaddr(&addr, &addr_size, INADDR_ANY, port, is_pipe_and_local, 1); + create_sockaddr(&addr, &addr_size, bind_address, port); - int skt = socket(domain, SOCK_DGRAM, 0); + int skt = socket(AF_INET, SOCK_DGRAM, 0); if (skt == -1) { vanilla_log("FAILED TO CREATE SOCKET: %i", skterr()); return VANILLA_ERR_BAD_SOCKET; @@ -128,21 +113,21 @@ int create_socket(int *socket_out, in_port_t port, int pipe) // vanilla_log("SUCCESSFULLY BOUND SOCKET %i ON PORT %i", skt, port); - (*socket_out) = skt; - set_socket_rcvtimeo(skt, 250000); // int buf_sz = 16777216; // setsockopt(skt, SOL_SOCKET, SO_RCVBUF, &buf_sz, sizeof(buf_sz)); // setsockopt(skt, SOL_SOCKET, SO_SNDBUF, &buf_sz, sizeof(buf_sz)); -#if !defined(_WIN32) && !defined(__APPLE__) +#if !defined(_WIN32) && !defined(__APPLE__) && !defined(ANDROID) if (!pipe && SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) { // Bind to wireless device setsockopt(skt, SOL_SOCKET, SO_BINDTODEVICE, wireless_interface, strlen(wireless_interface)); } #endif + (*socket_out) = skt; + // int val = 1000; // microseconds to busy-poll per recv() before sleeping // if (setsockopt(skt, SOL_SOCKET, SO_BUSY_POLL, &val, sizeof(val)) < 0) { // perror("setsockopt SO_BUSY_POLL"); @@ -153,20 +138,19 @@ int create_socket(int *socket_out, in_port_t port, int pipe) int send_pipe_cc(int skt, vanilla_pipe_command_t *cmd, size_t cmd_size, int wait_for_reply) { - sockaddr_u addr; + struct sockaddr_in addr; size_t addr_size; - int pipe_is_local = (SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL); - in_addr_t pipe_addr = pipe_is_local ? INADDR_ANY : SERVER_ADDRESS; + in_addr_t pipe_addr = (SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) ? htonl(INADDR_LOOPBACK) : SERVER_ADDRESS; - create_sockaddr(&addr, &addr_size, pipe_addr, VANILLA_PIPE_CMD_SERVER_PORT, pipe_is_local, 0); + create_sockaddr(&addr, &addr_size, pipe_addr, VANILLA_PIPE_CMD_SERVER_PORT); ssize_t read_size; uint8_t recv_cc; for (int retries = 0; retries < MAX_PIPE_RETRY; retries++) { if (sendto(skt, (const char *) cmd, cmd_size, 0, (const struct sockaddr *) &addr, addr_size) == -1) { - vanilla_log("Failed to write control code to socket"); + vanilla_log("Failed to write control code to socket: %i", skterr()); return 0; } @@ -178,8 +162,15 @@ int send_pipe_cc(int skt, vanilla_pipe_command_t *cmd, size_t cmd_size, int wait if (recv_cc == VANILLA_PIPE_CC_BIND_ACK) { return 1; } + if (recv_cc == VANILLA_PIPE_CC_BUSY) { + return -1; + } - vanilla_log("STILL WAITING FOR REPLY"); + if (read_size < 0) { + vanilla_log("STILL WAITING FOR REPLY: %i", skterr()); + } else { + vanilla_log("UNEXPECTED PIPE REPLY: 0x%02x", recv_cc); + } sleep(1); } @@ -205,7 +196,13 @@ int connect_to_backend(int *socket, vanilla_pipe_command_t *cmd, size_t cmd_size set_socket_rcvtimeo(pipe_cc_skt, 2000000); - if (!send_pipe_cc(pipe_cc_skt, cmd, cmd_size, 1)) { + int send_result = send_pipe_cc(pipe_cc_skt, cmd, cmd_size, 1); + if (send_result < 0) { + vanilla_log("PIPE IS BUSY"); + close(pipe_cc_skt); + return VANILLA_ERR_BUSY; + } + if (!send_result) { vanilla_log("FAILED TO BIND TO PIPE"); close(pipe_cc_skt); return VANILLA_ERR_PIPE_UNRESPONSIVE; diff --git a/lib/gamepad/gamepad.h b/lib/gamepad/gamepad.h index 5d03d0f..f2b95de 100644 --- a/lib/gamepad/gamepad.h +++ b/lib/gamepad/gamepad.h @@ -11,7 +11,6 @@ typedef uint16_t in_port_t; #else #include #include -#include #endif #include "vanilla.h" @@ -55,18 +54,11 @@ typedef struct thread_data_t vanilla_psk_t psk; } thread_data_t; -typedef union { - struct sockaddr_in in; -#ifndef _WIN32 - struct sockaddr_un un; -#endif -} sockaddr_u; - void sync_internal(thread_data_t *data); void connect_as_gamepad_internal(thread_data_t *data); int install_polkit_internal(thread_data_t *data, int install); -void create_server_sockaddr(sockaddr_u *addr, size_t *size, uint16_t port, int delete); -void send_to_sockaddr(int fd, const void *data, size_t data_size, const sockaddr_u *sockaddr, size_t sockaddr_size); +void create_server_sockaddr(struct sockaddr_in *addr, size_t *size, uint16_t port); +void send_to_sockaddr(int fd, const void *data, size_t data_size, const struct sockaddr_in *sockaddr, size_t sockaddr_size); void send_to_console(int fd, const void *data, size_t data_size, uint16_t port); int push_event(event_loop_t *loop, int type, const void *data, size_t size); int get_event(event_loop_t *loop, vanilla_event_t *event, int wait); diff --git a/lib/gamepad/input.c b/lib/gamepad/input.c index 6029d0a..e32b4e6 100644 --- a/lib/gamepad/input.c +++ b/lib/gamepad/input.c @@ -200,7 +200,7 @@ void set_battery_status(int status) pthread_mutex_unlock(&button_mtx); } -void send_input(int socket_hid, const sockaddr_u *addr, size_t addr_size) +void send_input(int socket_hid, const struct sockaddr_in *addr, size_t addr_size) { InputPacket ip; memset(&ip, 0, sizeof(ip)); @@ -298,9 +298,9 @@ void *listen_input(void *x) pthread_mutex_init(&button_mtx, NULL); - sockaddr_u addr; + struct sockaddr_in addr; size_t addr_size; - create_server_sockaddr(&addr, &addr_size, PORT_HID - 100, 0); + create_server_sockaddr(&addr, &addr_size, PORT_HID - 100); do { send_input(info->socket_hid, &addr, addr_size); diff --git a/pipe/def.h b/pipe/def.h index cee93aa..5189e3b 100644 --- a/pipe/def.h +++ b/pipe/def.h @@ -20,8 +20,6 @@ #define VANILLA_PIPE_CC_UNINSTALL_POLKIT 0x8B #define VANILLA_PIPE_CC_QUIT 0x90 -#define VANILLA_PIPE_LOCAL_SOCKET "/tmp/vanilla-pipe_%i.sock" - #define POLKIT_ACTION_DST "/usr/share/polkit-1/actions/com.mattkc.vanilla.policy" #define POLKIT_RULE_DST "/usr/share/polkit-1/rules.d/com.mattkc.vanilla.rules" diff --git a/pipe/linux/CMakeLists.txt b/pipe/linux/CMakeLists.txt index d38487d..39ec7b6 100644 --- a/pipe/linux/CMakeLists.txt +++ b/pipe/linux/CMakeLists.txt @@ -1,15 +1,31 @@ include(ExternalProject) -# Add vanilla-pipe executalbe +# Add vanilla-pipe executable add_executable(vanilla-pipe + dhcp/dhcp.c main.c wpa.c ) +if(ANDROID) + target_sources(vanilla-pipe PRIVATE android.c) +endif() + # Install vanilla-pipe install(TARGETS vanilla-pipe) -option(VANILLA_BUILD_USE_LIBNM "Build with support for communicating to NetworkManager" ON) +# vanilla-pipe remains an executable, but is given a .so extension so Gradle will +# place it alongside main executable libvanilla.so +if(ANDROID) + set_target_properties(vanilla-pipe PROPERTIES + OUTPUT_NAME vanilla_pipe + PREFIX "lib" + SUFFIX ".so" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}" + ) +endif() + +option(VANILLA_BUILD_USE_LIBNM "Build with support for communicating to NetworkManager" ${LINUX}) if (VANILLA_BUILD_USE_LIBNM) find_package(PkgConfig REQUIRED) pkg_check_modules(LIBNM REQUIRED libnm) @@ -18,34 +34,27 @@ if (VANILLA_BUILD_USE_LIBNM) target_compile_definitions(vanilla-pipe PRIVATE USE_LIBNM) endif() -if (VANILLA_BUILD_VENDORED) - # Build OpenSSL - set(OPENSSL_DIR ${CMAKE_CURRENT_BINARY_DIR}/openssl) - set(OPENSSL_INSTALL_DIR ${OPENSSL_DIR}/install) - set(OPENSSL_PROCESSOR ${CMAKE_SYSTEM_PROCESSOR}) - set(OPENSSL_EXTRA_FLAGS "") - if ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "arm") - set(OPENSSL_PROCESSOR "armv4") - set(OPENSSL_EXTRA_FLAGS "${CMAKE_C_FLAGS}") - endif() - set(OPENSSL_LIBRARIES "${OPENSSL_DIR}/libssl.a" "${OPENSSL_DIR}/libcrypto.a") - ExternalProject_Add(openssl - SOURCE_DIR ${OPENSSL_DIR} - BINARY_DIR ${OPENSSL_DIR} - GIT_REPOSITORY https://github.com/openssl/openssl.git - GIT_TAG openssl-3.5.0 - CONFIGURE_COMMAND env CC=${CMAKE_C_COMPILER} ./Configure --prefix=${OPENSSL_INSTALL_DIR} linux-${OPENSSL_PROCESSOR} - INSTALL_COMMAND make install_sw - UPDATE_COMMAND "" - BUILD_BYPRODUCTS - ${OPENSSL_LIBRARIES} +set(PIPE_TOOLCHAIN_FLAGS "") +if(ANDROID) + set(PIPE_TOOLCHAIN_FLAGS + "--target=${ANDROID_LLVM_TRIPLE} --sysroot=${CMAKE_SYSROOT}" ) - add_dependencies(vanilla-pipe openssl) +endif() +if (VANILLA_BUILD_VENDORED) # Build LibNL set(LIBNL_DIR ${CMAKE_CURRENT_BINARY_DIR}/libnl) set(LIBNL_INSTALL_DIR ${LIBNL_DIR}/install) - if ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "arm") + set(LIBNL_EXTRA_FLAGS "") + set(LIBNL_CFLAGS "${CMAKE_C_FLAGS} ${PIPE_TOOLCHAIN_FLAGS}") + if(ANDROID) + set(LIBNL_EXTRA_FLAGS --disable-pthreads) + string(APPEND LIBNL_CFLAGS " -D__UAPI_DEF_IN_ADDR=0 -include bits/in_addr.h") + endif() + + if (ANDROID) + set(LIBNL_HOST ${ANDROID_LLVM_TRIPLE}) + elseif ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "arm") set(LIBNL_HOST arm-linux-gnueabihf) else() set(LIBNL_HOST ${CMAKE_SYSTEM_PROCESSOR}-linux-gnu) @@ -59,7 +68,14 @@ if (VANILLA_BUILD_VENDORED) BINARY_DIR ${LIBNL_DIR} GIT_REPOSITORY https://github.com/thom311/libnl.git GIT_TAG libnl3_11_0 - CONFIGURE_COMMAND autoreconf -i && ./configure --prefix=${LIBNL_INSTALL_DIR} --host=${LIBNL_HOST} + PATCH_COMMAND autoreconf -i + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env + CC=${CMAKE_C_COMPILER} + AR=${CMAKE_AR} + RANLIB=${CMAKE_RANLIB} + "CFLAGS=${LIBNL_CFLAGS}" + "LDFLAGS=${CMAKE_EXE_LINKER_FLAGS}" + ./configure --prefix=${LIBNL_INSTALL_DIR} --host=${LIBNL_HOST} --disable-shared --enable-static --disable-cli ${LIBNL_EXTRA_FLAGS} UPDATE_COMMAND "" BUILD_BYPRODUCTS ${LibNL_LIBRARY} @@ -69,27 +85,33 @@ if (VANILLA_BUILD_VENDORED) ) add_dependencies(vanilla-pipe libnl) else() - # Find LibNL + # Find system LibNL find_package(LibNL REQUIRED) - - # Find OpenSSL and libnm - find_package(OpenSSL REQUIRED) endif() # Build WPA supplicant set(HOSTAP_DIR ${CMAKE_CURRENT_BINARY_DIR}/hostap) +set(HOSTAP_CFLAGS "${PIPE_TOOLCHAIN_FLAGS}") set(HOSTAP_ENV CC=${CMAKE_C_COMPILER} + AR=${CMAKE_AR} + RANLIB=${CMAKE_RANLIB} + CONFIG_TLS=internal + CONFIG_CRYPTO=internal + CONFIG_INTERNAL_LIBTOMMATH=y + CONFIG_INTERNAL_LIBTOMMATH_FAST=y ) if (VANILLA_BUILD_VENDORED) - set(HOSTAP_CFLAGS "-I${LIBNL_INSTALL_DIR}/include/libnl3 -I${OPENSSL_INSTALL_DIR}/include") - set(HOSTAP_LDFLAGS "-L${LIBNL_INSTALL_DIR}/lib -L${OPENSSL_INSTALL_DIR}/lib64 -L${OPENSSL_INSTALL_DIR}/lib") + string(APPEND HOSTAP_CFLAGS " -I${LIBNL_INSTALL_DIR}/include/libnl3") + set(HOSTAP_LDFLAGS "-L${LIBNL_INSTALL_DIR}/lib") list(APPEND HOSTAP_ENV PKG_CONFIG_PATH=${LIBNL_INSTALL_DIR}/lib/pkgconfig - EXTRA_CFLAGS=${HOSTAP_CFLAGS} LDFLAGS=${HOSTAP_LDFLAGS} ) endif() +if(HOSTAP_CFLAGS) + list(APPEND HOSTAP_ENV "EXTRA_CFLAGS=${HOSTAP_CFLAGS}") +endif() find_package(Git REQUIRED) ExternalProject_Add(hostap SOURCE_DIR ${HOSTAP_DIR} @@ -101,10 +123,11 @@ ExternalProject_Add(hostap #GIT_REPOSITORY https://github.com/vanilla-wiiu/drc-hostap.git DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E remove_directory "${HOSTAP_DIR}" && - ${CMAKE_COMMAND} -E env --unset=GIT_DIR -- ${GIT_EXECUTABLE} clone https://github.com/vanilla-wiiu/drc-hostap.git "${HOSTAP_DIR}" && - ${CMAKE_COMMAND} -E env --unset=GIT_DIR -- ${GIT_EXECUTABLE} -C "${HOSTAP_DIR}" checkout 257096accc39f9c2750a7718ff5751108d15f668 + ${CMAKE_COMMAND} -E env --unset=GIT_DIR ${GIT_EXECUTABLE} clone https://github.com/vanilla-wiiu/drc-hostap.git "${HOSTAP_DIR}" && + ${CMAKE_COMMAND} -E env --unset=GIT_DIR ${GIT_EXECUTABLE} -C "${HOSTAP_DIR}" checkout a2f705daa143791180754100194c91714f7e8801 CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy_if_different conf/wpa_supplicant.config wpa_supplicant/.config - BUILD_COMMAND env ${HOSTAP_ENV} make -C wpa_supplicant -j${CMAKE_BUILD_PARALLEL_LEVEL} libwpa_supplicant.a libwpa_client.a + BUILD_COMMAND env ${HOSTAP_ENV} make -C wpa_supplicant clean && + env ${HOSTAP_ENV} make -C wpa_supplicant -j${CMAKE_BUILD_PARALLEL_LEVEL} libwpa_supplicant.a libwpa_client.a INSTALL_COMMAND "" UPDATE_COMMAND "" BUILD_BYPRODUCTS @@ -112,7 +135,7 @@ ExternalProject_Add(hostap ${HOSTAP_DIR}/wpa_supplicant/libwpa_client.a ) if (VANILLA_BUILD_VENDORED) - add_dependencies(hostap openssl libnl) + add_dependencies(hostap libnl) endif() # Define wpa_client library from hostap @@ -131,26 +154,25 @@ set_target_properties(wpa_supplicant_drc PROPERTIES ) add_dependencies(wpa_supplicant_drc hostap) -# Compile custom dhclient library -add_subdirectory(dhcp EXCLUDE_FROM_ALL) - # Define vanilla-pipe include directories target_include_directories(vanilla-pipe PRIVATE ${HOSTAP_DIR}/src ${HOSTAP_DIR}/src/common ${HOSTAP_DIR}/wpa_supplicant - ${CMAKE_SOURCE_DIR}/lib + ${CMAKE_CURRENT_SOURCE_DIR}/../../lib ${LibNL_INCLUDE_DIR} ) # Link our library with the client library target_link_libraries(vanilla-pipe PRIVATE wpa_client - pthread - udhcpc wpa_supplicant_drc ${LibNL_LIBRARY} ${LibNL_ROUTE_LIBRARY} ${LibNL_GENL_LIBRARY} - ${OPENSSL_LIBRARIES} + ${CMAKE_DL_LIBS} + m ) +if(NOT ANDROID) + target_link_libraries(vanilla-pipe PRIVATE pthread) +endif() diff --git a/pipe/linux/android.c b/pipe/linux/android.c new file mode 100644 index 0000000..a5fd051 --- /dev/null +++ b/pipe/linux/android.c @@ -0,0 +1,472 @@ +#include "android.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wpa.h" + +static int restore_wifi; +static int restore_wifi_scan_always; + +#define ANDROID_WIFI_SHUTDOWN_TIMEOUT_MS 30000 +#define ANDROID_WIFI_QUIET_PERIOD_MS 6000 +#define ANDROID_WIFI_POLL_INTERVAL_US 250000 +#define ANDROID_WIFI_STATUS_POLL_INTERVAL_MS 1000 + +#define VANILLA_CONSOLE_ADDRESS "192.168.1.10/32" +#define VANILLA_CONSOLE_RULE_PRIORITY 9999 + +static struct rtnl_route *console_route; +static struct rtnl_rule *console_rule; + +typedef enum { + ANDROID_WIFI_STATE_UNKNOWN, + ANDROID_WIFI_STATE_DISABLED, + ANDROID_WIFI_STATE_NOT_DISABLED, +} android_wifi_state_t; + +typedef enum { + ANDROID_WIFI_STATUS_UNAVAILABLE, + ANDROID_WIFI_STATUS_CMD, + ANDROID_WIFI_STATUS_DUMPSYS, +} android_wifi_status_source_t; + +static struct rtnl_route *create_console_route(int ifindex, + struct nl_addr *destination, + struct nl_addr *source_address) +{ + struct rtnl_route *route = rtnl_route_alloc(); + struct rtnl_nexthop *next_hop = rtnl_route_nh_alloc(); + if (!route || !next_hop) { + if (route) + rtnl_route_put(route); + if (next_hop) + rtnl_route_nh_free(next_hop); + return NULL; + } + + rtnl_route_set_family(route, AF_INET); + rtnl_route_set_table(route, RT_TABLE_MAIN); + rtnl_route_set_protocol(route, RTPROT_STATIC); + rtnl_route_set_scope(route, RT_SCOPE_LINK); + rtnl_route_set_type(route, RTN_UNICAST); + if (rtnl_route_set_dst(route, destination) < 0 || + rtnl_route_set_pref_src(route, source_address) < 0) { + rtnl_route_put(route); + rtnl_route_nh_free(next_hop); + return NULL; + } + + rtnl_route_nh_set_ifindex(next_hop, ifindex); + rtnl_route_add_nexthop(route, next_hop); + return route; +} + +static struct rtnl_rule *create_console_rule(struct nl_addr *destination) +{ + struct rtnl_rule *rule = rtnl_rule_alloc(); + if (rule) { + rtnl_rule_set_family(rule, AF_INET); + rtnl_rule_set_prio(rule, VANILLA_CONSOLE_RULE_PRIORITY); + rtnl_rule_set_table(rule, RT_TABLE_MAIN); + rtnl_rule_set_action(rule, FR_ACT_TO_TBL); + rtnl_rule_set_dst(rule, destination); + } + return rule; +} + +int vanilla_android_install_console_routing(struct nl_sock *socket, + int ifindex, + struct nl_addr *source_address) +{ + // Remove any previously made routing (during this session) + vanilla_android_remove_console_routing(socket); + + // Sanity check input + struct nl_addr *destination = NULL; + if (!socket || ifindex <= 0 || !source_address || nl_addr_parse(VANILLA_CONSOLE_ADDRESS, AF_INET, &destination) < 0) { + nlprint("ANDROID: INVALID INPUT"); + goto die; + } + + // Build route and rule + struct rtnl_route *route = create_console_route(ifindex, destination, source_address); + struct rtnl_rule *rule = create_console_rule(destination); + nl_addr_put(destination); + if (!route || !rule) { + nlprint("ANDROID: FAILED TO BUILD WII U ROUTING"); + if (route) { + rtnl_route_put(route); + } + if (rule) { + rtnl_rule_put(rule); + } + goto die; + } + + // Remove an existing rule if a previous pipe was killed without cleaning up + rtnl_rule_delete(socket, rule, 0); + + // Add route + int result = rtnl_route_add(socket, route, NLM_F_CREATE | NLM_F_REPLACE); + if (result < 0) { + nlprint("ANDROID: FAILED TO ADD WII U ROUTE: %s", nl_geterror(result)); + goto die_and_put; + } + + // Add rule + result = rtnl_rule_add(socket, rule, NLM_F_CREATE | NLM_F_EXCL); + if (result < 0) { + nlprint("ANDROID: FAILED TO ADD WII U ROUTING RULE: %s", nl_geterror(result)); + goto die_and_delete_route; + } + + // Everything worked, store route/rule so we can remove later + console_route = route; + console_rule = rule; + nlprint("ANDROID: NETWORK ROUTE AND RULE ADDED"); + return 0; + +die_and_delete_route: + rtnl_route_delete(socket, route, 0); + +die_and_put: + rtnl_route_put(route); + rtnl_rule_put(rule); + +die: + return -1; +} + +void vanilla_android_remove_console_routing(struct nl_sock *socket) +{ + if (!socket) { + return; + } + + if (console_rule) { + rtnl_rule_delete(socket, console_rule, 0); + rtnl_rule_put(console_rule); + console_rule = NULL; + } + + if (console_route) { + rtnl_route_delete(socket, console_route, 0); + rtnl_route_put(console_route); + console_route = NULL; + } +} + +static android_wifi_state_t parse_wifi_state(const char *status) +{ + if (strstr(status, "Wifi is disabled") || + strstr(status, "Wi-Fi is disabled")) + return ANDROID_WIFI_STATE_DISABLED; + + if (strstr(status, "Wifi is enabled") || + strstr(status, "Wi-Fi is enabled") || + strstr(status, "Wifi is enabling") || + strstr(status, "Wi-Fi is enabling") || + strstr(status, "Wifi is disabling") || + strstr(status, "Wi-Fi is disabling")) + return ANDROID_WIFI_STATE_NOT_DISABLED; + + return ANDROID_WIFI_STATE_UNKNOWN; +} + +static android_wifi_state_t query_wifi_state(android_wifi_status_source_t source) +{ + const char **command; + const char *cmd_status[] = {"cmd", "wifi", "status", NULL}; + const char *dumpsys_status[] = {"dumpsys", "wifi", NULL}; + + if (source == ANDROID_WIFI_STATUS_CMD) { + command = cmd_status; + } else if (source == ANDROID_WIFI_STATUS_DUMPSYS) { + command = dumpsys_status; + } else { + return ANDROID_WIFI_STATE_UNKNOWN; + } + + char status[256]; + ssize_t status_len = run_process_and_read_stdout( + command, status, sizeof(status) - 1); + if (status_len <= 0) + return ANDROID_WIFI_STATE_UNKNOWN; + + status[status_len] = 0; + return parse_wifi_state(status); +} + +static android_wifi_status_source_t find_wifi_status_source(android_wifi_state_t *state) +{ + *state = query_wifi_state(ANDROID_WIFI_STATUS_CMD); + if (*state != ANDROID_WIFI_STATE_UNKNOWN) { + return ANDROID_WIFI_STATUS_CMD; + } + + *state = query_wifi_state(ANDROID_WIFI_STATUS_DUMPSYS); + if (*state != ANDROID_WIFI_STATE_UNKNOWN) { + return ANDROID_WIFI_STATUS_DUMPSYS; + } + + return ANDROID_WIFI_STATUS_UNAVAILABLE; +} + +static int query_wifi_enabled_setting(int *enabled) +{ + char status[64]; + ssize_t status_len = run_process_and_read_stdout((const char *[]) {"settings", "get", "global", "wifi_on", NULL}, status, sizeof(status) - 1); + if (status_len <= 0) + return -1; + + status[status_len] = 0; + char *end; + errno = 0; + long value = strtol(status, &end, 10); + while (*end && isspace((unsigned char) *end)) { + end++; + } + + if (errno || end == status || *end) { + return -1; + } + + // WifiSettingsStore uses 1 for enabled and 3 for enabled while overriding + // airplane mode (0 and 2 mean disabled) + *enabled = value == 1 || value == 3; + return 0; +} + +static int query_wifi_scan_always_setting(int *enabled) +{ + char status[64]; + ssize_t status_len = run_process_and_read_stdout((const char *[]) {"settings", "get", "global", "wifi_scan_always_enabled", NULL}, status, sizeof(status) - 1); + if (status_len <= 0) { + return -1; + } + + status[status_len] = 0; + char *end; + errno = 0; + long value = strtol(status, &end, 10); + while (*end && isspace((unsigned char) *end)) { + end++; + } + if (errno || end == status || *end) { + return -1; + } + + *enabled = value != 0; + return 0; +} + +static int set_wifi_scan_always_setting(int enabled) +{ + return run_process_and_read_stdout((const char *[]) {"settings", "put", "global", "wifi_scan_always_enabled", enabled ? "1" : "0", NULL}, NULL, 0) < 0 ? -1 : 0; +} + +static void restore_android_wifi_state(void) +{ + if (restore_wifi) { + nlprint("ANDROID: RESTORING THE SYSTEM WI-FI"); + run_process_and_read_stdout((const char *[]) {"svc", "wifi", "enable", NULL}, NULL, 0); + restore_wifi = 0; + } + + if (restore_wifi_scan_always) { + nlprint("ANDROID: RESTORING BACKGROUND WI-FI SCANNING"); + set_wifi_scan_always_setting(1); + restore_wifi_scan_always = 0; + } +} + +static int process_named_is_running(const char *name) +{ + DIR *proc = opendir("/proc"); + if (!proc) { + return 0; + } + + int found = 0; + struct dirent *entry; + while (!found && (entry = readdir(proc))) { + if (!isdigit((unsigned char) entry->d_name[0])) { + continue; + } + + char comm_path[PATH_MAX]; + if (snprintf(comm_path, sizeof(comm_path), "/proc/%s/comm", entry->d_name) >= (int) sizeof(comm_path)) { + continue; + } + + FILE *comm = fopen(comm_path, "r"); + if (!comm) { + continue; + } + + char process_name[64]; + if (fgets(process_name, sizeof(process_name), comm)) { + process_name[strcspn(process_name, "\r\n")] = 0; + found = !strcmp(process_name, name); + } + fclose(comm); + } + + closedir(proc); + return found; +} + +static int stock_supplicant_is_running(void) +{ + return process_named_is_running("wpa_supplicant") || process_named_is_running("p2p_supplicant"); +} + +static int64_t monotonic_milliseconds(void) +{ + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) { + return -1; + } + + return (int64_t) now.tv_sec * 1000 + now.tv_nsec / 1000000; +} + +static int wait_for_wifi_shutdown(android_wifi_status_source_t status_source) +{ + int64_t started_at = monotonic_milliseconds(); + int64_t quiet_since = -1; + int64_t next_status_check = started_at; + android_wifi_state_t last_state = ANDROID_WIFI_STATE_UNKNOWN; + int supplicant_running = 0; + + if (started_at < 0) { + return -1; + } + + nlprint("ANDROID: WAITING FOR THE SYSTEM WI-FI TO RELEASE WLAN"); + while (1) { + int64_t now = monotonic_milliseconds(); + if (now < 0) { + return -1; + } + + if (status_source != ANDROID_WIFI_STATUS_UNAVAILABLE + && now >= next_status_check) { + last_state = query_wifi_state(status_source); + now = monotonic_milliseconds(); + if (now < 0) { + return -1; + } + next_status_check = now + ANDROID_WIFI_STATUS_POLL_INTERVAL_MS; + } + + // Some Android builds may keep an idle supplicant service alive even + // after disabling Wi-Fi. Check for it. + supplicant_running = status_source == ANDROID_WIFI_STATUS_UNAVAILABLE && stock_supplicant_is_running(); + + // Some Android builds may attempt to recover the Wi-Fi, wait to ensure + // that doesn't happen + int framework_disabled = status_source == ANDROID_WIFI_STATUS_UNAVAILABLE || last_state == ANDROID_WIFI_STATE_DISABLED; + if (framework_disabled && !supplicant_running) { + if (quiet_since < 0) { + quiet_since = now; + } + if (now - quiet_since >= ANDROID_WIFI_QUIET_PERIOD_MS) { + nlprint("ANDROID: SYSTEM WI-FI RELEASED WLAN"); + return 0; + } + } else { + quiet_since = -1; + } + + if (now - started_at >= ANDROID_WIFI_SHUTDOWN_TIMEOUT_MS) { + break; + } + + usleep(ANDROID_WIFI_POLL_INTERVAL_US); + } + + if (status_source == ANDROID_WIFI_STATUS_UNAVAILABLE) { + nlprint("ANDROID: TIMED OUT WAITING FOR THE SYSTEM WI-FI " + "(supplicant=%s)", + supplicant_running ? "running" : "stopped"); + } else { + nlprint("ANDROID: TIMED OUT WAITING FOR THE SYSTEM WI-FI " + "(state=%s)", + last_state == ANDROID_WIFI_STATE_DISABLED ? "disabled" : + last_state == ANDROID_WIFI_STATE_NOT_DISABLED ? "active" : + "unknown"); + } + return -1; +} + +int vanilla_android_acquire_wifi(const char *wireless_interface) +{ + android_wifi_state_t initial_state; + android_wifi_status_source_t status_source = find_wifi_status_source(&initial_state); + if (query_wifi_enabled_setting(&restore_wifi) < 0) { + restore_wifi = initial_state != ANDROID_WIFI_STATE_DISABLED; + } + + // Android may continue doing location scans even while Wi-Fi is off, + // conflicting with us. Ensure background scanning is also off. + restore_wifi_scan_always = 0; + int scan_always_enabled; + if (query_wifi_scan_always_setting(&scan_always_enabled) == 0 && + scan_always_enabled) { + nlprint("ANDROID: TEMPORARILY DISABLING BACKGROUND WI-FI SCANNING"); + if (set_wifi_scan_always_setting(0) < 0) { + nlprint("ANDROID: FAILED TO DISABLE BACKGROUND WI-FI SCANNING"); + return -1; + } + restore_wifi_scan_always = 1; + } + + nlprint("ANDROID: TEMPORARILY DISABLING THE SYSTEM WI-FI"); + if (run_process_and_read_stdout( + (const char *[]) {"svc", "wifi", "disable", NULL}, NULL, 0) < 0) { + nlprint("ANDROID: FAILED TO DISABLE THE SYSTEM WI-FI"); + restore_android_wifi_state(); + return -1; + } + + if (wait_for_wifi_shutdown(status_source) < 0) { + restore_android_wifi_state(); + return -1; + } + + if (!if_nametoindex(wireless_interface)) { + nlprint("ANDROID: INTERFACE %s DISAPPEARED WHEN WI-FI WAS DISABLED; " + "THIS DEVICE NEEDS A VENDOR-SPECIFIC RADIO BACKEND", + wireless_interface); + restore_android_wifi_state(); + return -1; + } + + return 0; +} + +void vanilla_android_release_wifi(void) +{ + restore_android_wifi_state(); +} diff --git a/pipe/linux/android.h b/pipe/linux/android.h new file mode 100644 index 0000000..636b1b3 --- /dev/null +++ b/pipe/linux/android.h @@ -0,0 +1,15 @@ +#ifndef VANILLA_PIPE_ANDROID_H +#define VANILLA_PIPE_ANDROID_H + +struct nl_sock; +struct nl_addr; + +int vanilla_android_acquire_wifi(const char *wireless_interface); +void vanilla_android_release_wifi(void); + +int vanilla_android_install_console_routing(struct nl_sock *socket, + int ifindex, + struct nl_addr *source_address); +void vanilla_android_remove_console_routing(struct nl_sock *socket); + +#endif // VANILLA_PIPE_ANDROID_H diff --git a/pipe/linux/dhcp/AUTHORS b/pipe/linux/dhcp/AUTHORS deleted file mode 100644 index f3f4336..0000000 --- a/pipe/linux/dhcp/AUTHORS +++ /dev/null @@ -1,13 +0,0 @@ -udhcp server/client package ------------------------ - -Russ Dill -Matthew Ramsay -Chris Trew - -Other Credits: --------------- -Moreton Bay (http://www.moretonbay.com/) -Vladimir Oleynik Size optimizations - - diff --git a/pipe/linux/dhcp/CMakeLists.txt b/pipe/linux/dhcp/CMakeLists.txt deleted file mode 100644 index 7aaeb57..0000000 --- a/pipe/linux/dhcp/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -add_library(udhcpc STATIC - common.c - options.c - packet.c - pidfile.c - signalpipe.c - socket.c - dhcpc.c - clientpacket.c - clientsocket.c - script.c -) - -target_compile_definitions(udhcpc PRIVATE _GNU_SOURCE $<$:UDHCP_DEBUG>) diff --git a/pipe/linux/dhcp/COPYING b/pipe/linux/dhcp/COPYING deleted file mode 100644 index a43ea21..0000000 --- a/pipe/linux/dhcp/COPYING +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 675 Mass Ave, Cambridge, MA 02139, USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - Appendix: How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) 19yy - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) 19yy name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General -Public License instead of this License. diff --git a/pipe/linux/dhcp/README.udhcpc b/pipe/linux/dhcp/README.udhcpc deleted file mode 100644 index 61741d1..0000000 --- a/pipe/linux/dhcp/README.udhcpc +++ /dev/null @@ -1,142 +0,0 @@ -udhcp client (udhcpc) --------------------- - -The udhcp client negotiates a lease with the DHCP server and notifies -a set of scripts when a leases is obtained or lost. - - -command line options -------------------- - -The command line options for the udhcp client are: - --c, --clientid=CLIENTID Client identifier --H, --hostname=HOSTNAME Client hostname --h, Alias for -H --F, --fqdn=FQDN Client fully qualified domain name --f, --foreground Do not fork after getting lease --b, --background Fork to background if lease cannot be - immediately negotiated. --i, --interface=INTERFACE Interface to use (default: eth0) --n, --now Exit with failure if lease cannot be - immediately negotiated. --p, --pidfile=file Store process ID of daemon in file --q, --quit Quit after obtaining lease --r, --request=IP IP address to request (default: none) --s, --script=file Run file at dhcp events (default: - /etc/udhcpc/default.script) --v, --version Display version - - -If the requested IP address cannot be obtained, the client accepts the -address that the server offers. - - -udhcp client scripts -------------------- - -When an event occurs, udhcpc calls the action script. udhcpc never does -any configuration of the network interface itself, but instead relies on -a set of scripts. The script by default is -/etc/udhcpc/default.script but this can be changed via the command -line arguments. The three possible arguments to the script are: - - deconfig: This argument is used when udhcpc starts, and - when a leases is lost. The script must put the interface in an - up, but deconfigured state, ie: ifconfig $interface 0.0.0.0. - - bound: This argument is used when udhcpc moves from an - unbound, to a bound state. All of the paramaters are set in - enviromental variables, The script should configure the interface, - and set any other relavent parameters (default gateway, dns server, - etc). - - renew: This argument is used when a DHCP lease is renewed. All of - the paramaters are set in enviromental variables. This argument is - used when the interface is already configured, so the IP address, - will not change, however, the other DHCP paramaters, such as the - default gateway, subnet mask, and dns server may change. - - nak: This argument is used with udhcpc receives a NAK message. - The script with the deconfig argument will be called directly - afterwards, so no changes to the network interface are neccessary. - This hook is provided for purely informational purposes (the - message option may contain a reason for the NAK). - -The paramaters for enviromental variables are as follows: - - $HOME - The set $HOME env or "/" - $PATH - the set $PATH env or "/bin:/usr/bin:/sbin:/usr/sbin" - $1 - What action the script should perform - interface - The interface this was obtained on - ip - The obtained IP - mask - The number of bits in the netmask (ie: 24) - siaddr - The bootp next server option - sname - The bootp server name option - boot_file - The bootp boot file option - subnet - The assigend subnet mask - timezone - Offset in seconds from UTC - router - A list of routers - timesvr - A list of time servers - namesvr - A list of IEN 116 name servers - dns - A list of DNS server - logsvr - A list of MIT-LCS UDP log servers - cookiesvr - A list of RFC 865 cookie servers - lprsvr - A list of LPR servers - hostname - The assigned hostname - bootsize - The length in 512 octect blocks of the bootfile - domain - The domain name of the network - swapsvr - The IP address of the client's swap server - rootpath - The path name of the client's root disk - ipttl - The TTL to use for this network - mtu - The MTU to use for this network - broadcast - The broadcast address for this network - ntpsrv - A list of NTP servers - wins - A list of WINS servers - lease - The lease time, in seconds - dhcptype - DHCP message type (safely ignored) - serverid - The IP of the server - message - Reason for a DHCPNAK - tftp - The TFTP server name - bootfile - The bootfile name - -additional options are easily added in options.c. - - -note on udhcpc's random seed ---------------------------- - -udhcpc will seed its random number generator (used for generating xid's) -by reading /dev/urandom. If you have a lot of embedded systems on the same -network, with no entropy, you can either seed /dev/urandom by a method of -your own, or doing the following on startup: - -ifconfig eth0 > /dev/urandom - -in order to seed /dev/urandom with some data (mac address) unique to your -system. If reading /dev/urandom fails, udhcpc will fall back to its old -behavior of seeding with time(0). - - -signals accepted by udhcpc -------------------------- - -udhcpc also responds to SIGUSR1 and SIGUSR2. SIGUSR1 will force a renew state, -and SIGUSR2 will force a release of the current lease, and cause udhcpc to -go into an inactive state (until it is killed, or receives a SIGUSR1). You do -not need to sleep between sending signals, as signals received are processed -sequencially in the order they are received. - - -compile time options -------------------- - -options.c contains a set of dhcp options for the client: - - name[10]: The name of the option as it will appear in scripts - - flags: The type of option, as well as if it will be requested - by the client (OPTION_REQ) - - code: The DHCP code for this option - diff --git a/pipe/linux/dhcp/clientpacket.c b/pipe/linux/dhcp/clientpacket.c deleted file mode 100644 index c5e1c21..0000000 --- a/pipe/linux/dhcp/clientpacket.c +++ /dev/null @@ -1,249 +0,0 @@ -/* clientpacket.c - * - * Packet generation and dispatching functions for the DHCP client. - * - * Russ Dill July 2001 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include -#include -#include -#if __GLIBC__ >=2 && __GLIBC_MINOR >= 1 -#include -#include -#else -#include -#include -#include -#endif -#include -#include -#include -#include -#include -#include - - -#include "dhcpd.h" -#include "clientpacket.h" -#include "options.h" -#include "dhcpc.h" -#include "common.h" - - -/* Create a random xid */ -unsigned long random_xid(void) -{ - static int initialized; - if (!initialized) { - int fd; - unsigned long seed; - - fd = open("/dev/urandom", 0); - if (fd < 0 || read(fd, &seed, sizeof(seed)) < 0) { - LOG(LOG_WARNING, "Could not load seed from /dev/urandom: %m"); - seed = time(0); - } - if (fd >= 0) close(fd); - srand(seed); - initialized++; - } - return rand(); -} - - -/* initialize a packet with the proper defaults */ -static void init_packet(struct dhcpMessage *packet, char type) -{ - struct vendor { - char vendor, length; - char str[sizeof("udhcp "VERSION)]; - } vendor_id = { DHCP_VENDOR, sizeof("udhcp "VERSION) - 1, "udhcp "VERSION}; - - init_header(packet, type); - memcpy(packet->chaddr, client_config.arp, 6); - add_option_string(packet->options, client_config.clientid); - if (client_config.hostname) add_option_string(packet->options, client_config.hostname); - if (client_config.fqdn) add_option_string(packet->options, client_config.fqdn); - add_option_string(packet->options, (uint8_t *) &vendor_id); -} - - -/* Add a parameter request list for stubborn DHCP servers. Pull the data - * from the struct in options.c. Don't do bounds checking here because it - * goes towards the head of the packet. */ -static void add_requests(struct dhcpMessage *packet) -{ - int end = end_option(packet->options); - int i, len = 0; - - packet->options[end + OPT_CODE] = DHCP_PARAM_REQ; - for (i = 0; dhcp_options[i].code; i++) - if (dhcp_options[i].flags & OPTION_REQ) - packet->options[end + OPT_DATA + len++] = dhcp_options[i].code; - packet->options[end + OPT_LEN] = len; - packet->options[end + OPT_DATA + len] = DHCP_END; - -} - - -/* Broadcast a DHCP discover packet to the network, with an optionally requested IP */ -int send_discover(unsigned long xid, unsigned long requested) -{ - struct dhcpMessage packet; - - init_packet(&packet, DHCPDISCOVER); - packet.xid = xid; - if (requested) - add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); - - add_requests(&packet); - LOG(LOG_DEBUG, "Sending discover..."); - return raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, - SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); -} - - -/* Broadcasts a DHCP request message */ -int send_selecting(unsigned long xid, unsigned long server, unsigned long requested) -{ - struct dhcpMessage packet; - struct in_addr addr; - - init_packet(&packet, DHCPREQUEST); - packet.xid = xid; - - add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); - add_simple_option(packet.options, DHCP_SERVER_ID, server); - - add_requests(&packet); - addr.s_addr = requested; - LOG(LOG_DEBUG, "Sending select for %s...", inet_ntoa(addr)); - return raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, - SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); -} - - -/* Unicasts or broadcasts a DHCP renew message */ -int send_renew(unsigned long xid, unsigned long server, unsigned long ciaddr) -{ - struct dhcpMessage packet; - int ret = 0; - - init_packet(&packet, DHCPREQUEST); - packet.xid = xid; - packet.ciaddr = ciaddr; - - add_requests(&packet); - LOG(LOG_DEBUG, "Sending renew..."); - if (server) - ret = kernel_packet(&packet, ciaddr, CLIENT_PORT, server, SERVER_PORT); - else ret = raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, - SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); - return ret; -} - - -/* Unicasts a DHCP release message */ -int send_release(unsigned long server, unsigned long ciaddr) -{ - struct dhcpMessage packet; - - init_packet(&packet, DHCPRELEASE); - packet.xid = random_xid(); - packet.ciaddr = ciaddr; - - add_simple_option(packet.options, DHCP_REQUESTED_IP, ciaddr); - add_simple_option(packet.options, DHCP_SERVER_ID, server); - - LOG(LOG_DEBUG, "Sending release..."); - return kernel_packet(&packet, ciaddr, CLIENT_PORT, server, SERVER_PORT); -} - - -/* return -1 on errors that are fatal for the socket, -2 for those that aren't */ -int get_raw_packet(struct dhcpMessage *payload, int fd) -{ - int bytes; - struct udp_dhcp_packet packet; - uint32_t source, dest; - uint16_t check; - - memset(&packet, 0, sizeof(struct udp_dhcp_packet)); - bytes = read(fd, &packet, sizeof(struct udp_dhcp_packet)); - if (bytes < 0) { - DEBUG(LOG_INFO, "couldn't read on raw listening socket -- ignoring"); - usleep(500000); /* possible down interface, looping condition */ - return -1; - } - - if (bytes < (int) (sizeof(struct iphdr) + sizeof(struct udphdr))) { - DEBUG(LOG_INFO, "message too short, ignoring"); - return -2; - } - - if (bytes < ntohs(packet.ip.tot_len)) { - DEBUG(LOG_INFO, "Truncated packet"); - return -2; - } - - /* ignore any extra garbage bytes */ - bytes = ntohs(packet.ip.tot_len); - - /* Make sure its the right packet for us, and that it passes sanity checks */ - if (packet.ip.protocol != IPPROTO_UDP || packet.ip.version != IPVERSION || - packet.ip.ihl != sizeof(packet.ip) >> 2 || packet.udp.dest != htons(CLIENT_PORT) || - bytes > (int) sizeof(struct udp_dhcp_packet) || - ntohs(packet.udp.len) != (uint16_t) (bytes - sizeof(packet.ip))) { - DEBUG(LOG_INFO, "unrelated/bogus packet"); - return -2; - } - - /* check IP checksum */ - check = packet.ip.check; - packet.ip.check = 0; - if (check != checksum(&(packet.ip), sizeof(packet.ip))) { - DEBUG(LOG_INFO, "bad IP header checksum, ignoring"); - return -1; - } - - /* verify the UDP checksum by replacing the header with a psuedo header */ - source = packet.ip.saddr; - dest = packet.ip.daddr; - check = packet.udp.check; - packet.udp.check = 0; - memset(&packet.ip, 0, sizeof(packet.ip)); - - packet.ip.protocol = IPPROTO_UDP; - packet.ip.saddr = source; - packet.ip.daddr = dest; - packet.ip.tot_len = packet.udp.len; /* cheat on the psuedo-header */ - if (check && check != checksum(&packet, bytes)) { - DEBUG(LOG_ERR, "packet with bad UDP checksum received, ignoring"); - return -2; - } - - memcpy(payload, &(packet.data), bytes - (sizeof(packet.ip) + sizeof(packet.udp))); - - if (ntohl(payload->cookie) != DHCP_MAGIC) { - LOG(LOG_ERR, "received bogus message (bad magic) -- ignoring"); - return -2; - } - DEBUG(LOG_INFO, "oooooh!!! got some!"); - return bytes - (sizeof(packet.ip) + sizeof(packet.udp)); - -} diff --git a/pipe/linux/dhcp/clientpacket.h b/pipe/linux/dhcp/clientpacket.h deleted file mode 100644 index 8e5441b..0000000 --- a/pipe/linux/dhcp/clientpacket.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef _CLIENTPACKET_H -#define _CLIENTPACKET_H - -#include "packet.h" - -unsigned long random_xid(void); -int send_discover(unsigned long xid, unsigned long requested); -int send_selecting(unsigned long xid, unsigned long server, unsigned long requested); -int send_renew(unsigned long xid, unsigned long server, unsigned long ciaddr); -int send_renew(unsigned long xid, unsigned long server, unsigned long ciaddr); -int send_release(unsigned long server, unsigned long ciaddr); -int get_raw_packet(struct dhcpMessage *payload, int fd); - -#endif diff --git a/pipe/linux/dhcp/clientsocket.c b/pipe/linux/dhcp/clientsocket.c deleted file mode 100644 index 7c1b6e8..0000000 --- a/pipe/linux/dhcp/clientsocket.c +++ /dev/null @@ -1,62 +0,0 @@ -/* - * clientsocket.c -- DHCP client socket creation - * - * udhcp client - * - * Russ Dill July 2001 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include -#include -#include -#include -#include -#if __GLIBC__ >=2 && __GLIBC_MINOR >= 1 -#include -#include -#else -#include -#include -#include -#endif - -#include "clientsocket.h" -#include "common.h" - - -int raw_socket(int ifindex) -{ - int fd; - struct sockaddr_ll sock; - - DEBUG(LOG_INFO, "Opening raw socket on ifindex %d", ifindex); - if ((fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) { - DEBUG(LOG_ERR, "socket call failed: %m"); - return -1; - } - - sock.sll_family = AF_PACKET; - sock.sll_protocol = htons(ETH_P_IP); - sock.sll_ifindex = ifindex; - if (bind(fd, (struct sockaddr *) &sock, sizeof(sock)) < 0) { - DEBUG(LOG_ERR, "bind call failed: %m"); - close(fd); - return -1; - } - - return fd; -} diff --git a/pipe/linux/dhcp/clientsocket.h b/pipe/linux/dhcp/clientsocket.h deleted file mode 100644 index 17a55c1..0000000 --- a/pipe/linux/dhcp/clientsocket.h +++ /dev/null @@ -1,7 +0,0 @@ -/* clientsocket.h */ -#ifndef _CLIENTSOCKET_H -#define _CLIENTSOCKET_H - -int raw_socket(int ifindex); - -#endif diff --git a/pipe/linux/dhcp/common.c b/pipe/linux/dhcp/common.c deleted file mode 100644 index bf2ac44..0000000 --- a/pipe/linux/dhcp/common.c +++ /dev/null @@ -1,162 +0,0 @@ -/* common.c - * - * Functions for debugging and logging as well as some other - * simple helper functions. - * - * Russ Dill 2001-2003 - * Rewritten by Vladimir Oleynik (C) 2003 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "common.h" -#include "pidfile.h" - - -static int daemonized; - -long uptime(void) -{ - struct sysinfo info; - sysinfo(&info); - return info.uptime; -} - - -/* - * This function makes sure our first socket calls - * aren't going to fd 1 (printf badness...) and are - * not later closed by daemon() - */ -static inline void sanitize_fds(void) -{ - int zero; - if ((zero = open(_PATH_DEVNULL, O_RDWR, 0)) < 0) return; - while (zero < 3) zero = dup(zero); - close(zero); -} - - -void background(const char *pidfile) -{ -#ifdef __uClinux__ - LOG(LOG_ERR, "Cannot background in uclinux (yet)"); -#else /* __uClinux__ */ - int pid_fd; - - /* hold lock during fork. */ - pid_fd = pidfile_acquire(pidfile); - if (daemon(0, 0) == -1) { - perror("fork"); - exit(1); - } - daemonized++; - pidfile_write_release(pid_fd); -#endif /* __uClinux__ */ -} - - -#ifdef UDHCP_SYSLOG -void udhcp_logging(int level, const char *fmt, ...) -{ - va_list p; - va_list p2; - - va_start(p, fmt); - __va_copy(p2, p); - if(!daemonized) { - vprintf(fmt, p); - putchar('\n'); - } - vsyslog(level, fmt, p2); - va_end(p); -} - - -void start_log_and_pid(const char *client_server, const char *pidfile) -{ - int pid_fd; - - /* Make sure our syslog fd isn't overwritten */ - sanitize_fds(); - - /* do some other misc startup stuff while we are here to save bytes */ - pid_fd = pidfile_acquire(pidfile); - pidfile_write_release(pid_fd); - - /* equivelent of doing a fflush after every \n */ - setlinebuf(stdout); - - openlog(client_server, LOG_PID | LOG_CONS, LOG_LOCAL0); - udhcp_logging(LOG_INFO, "%s (v%s) started", client_server, VERSION); -} - - -#else - - -static char *syslog_level_msg[] = { - [LOG_EMERG] = "EMERGENCY!", - [LOG_ALERT] = "ALERT!", - [LOG_CRIT] = "critical!", - [LOG_WARNING] = "warning", - [LOG_ERR] = "error", - [LOG_INFO] = "info", - [LOG_DEBUG] = "debug" -}; - - -void udhcp_logging(int level, const char *fmt, ...) -{ - va_list p; - - va_start(p, fmt); - if(!daemonized) { - printf("%s, ", syslog_level_msg[level]); - vprintf(fmt, p); - putchar('\n'); - } - va_end(p); -} - - -void start_log_and_pid(const char *client_server, const char *pidfile) -{ - int pid_fd; - - /* Make sure our syslog fd isn't overwritten */ - sanitize_fds(); - - /* do some other misc startup stuff while we are here to save bytes */ - pid_fd = pidfile_acquire(pidfile); - pidfile_write_release(pid_fd); - - /* equivelent of doing a fflush after every \n */ - setlinebuf(stdout); - - udhcp_logging(LOG_INFO, "%s (v%s) started", client_server, VERSION); -} -#endif - diff --git a/pipe/linux/dhcp/common.h b/pipe/linux/dhcp/common.h deleted file mode 100644 index ca19a24..0000000 --- a/pipe/linux/dhcp/common.h +++ /dev/null @@ -1,56 +0,0 @@ -/* common.h - * - * Russ Dill September 2001 - * Rewritten by Vladimir Oleynik (C) 2003 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#ifndef _COMMON_H -#define _COMMON_H - -#include "version.h" -#include "libbb_udhcp.h" - - -#ifndef UDHCP_SYSLOG -enum syslog_levels { - LOG_EMERG = 0, - LOG_ALERT, - LOG_CRIT, - LOG_WARNING, - LOG_ERR, - LOG_INFO, - LOG_DEBUG -}; -#else -#include -#endif - -long uptime(void); -void background(const char *pidfile); -void start_log_and_pid(const char *client_server, const char *pidfile); -void background(const char *pidfile); -void udhcp_logging(int level, const char *fmt, ...); - -#define LOG(level, str, args...) udhcp_logging(level, str, ## args) - -#ifdef UDHCP_DEBUG -# define DEBUG(level, str, args...) LOG(level, str, ## args) -#else -# define DEBUG(level, str, args...) do {;} while(0) -#endif - -#endif diff --git a/pipe/linux/dhcp/dhcp.c b/pipe/linux/dhcp/dhcp.c new file mode 100644 index 0000000..5962113 --- /dev/null +++ b/pipe/linux/dhcp/dhcp.c @@ -0,0 +1,467 @@ +#define _DEFAULT_SOURCE + +#include "dhcp.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DHCP_CLIENT_PORT 68 +#define DHCP_SERVER_PORT 67 + +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPACK 5 +#define DHCPNAK 6 + +#define DHCP_MAGIC_COOKIE 0x63825363u +#define DHCP_FLAGS_BROADCAST 0x8000 + +#define DHCP_OPTION_SUBNET_MASK 1 +#define DHCP_OPTION_ROUTER 3 +#define DHCP_OPTION_DNS 6 +#define DHCP_OPTION_REQUESTED_IP 50 +#define DHCP_OPTION_LEASE_TIME 51 +#define DHCP_OPTION_MSG_TYPE 53 +#define DHCP_OPTION_SERVER_IDENTIFIER 54 +#define DHCP_OPTION_PARAMETER_REQUEST 55 +#define DHCP_OPTION_END 255 +#define DHCP_OPTION_PAD 0 + +#define DHCP_HTYPE_ETHERNET 1 +#define DHCP_HLEN_ETHERNET 6 +#define DHCP_OP_BOOTREQUEST 1 +#define DHCP_OP_BOOTREPLY 2 + +#define DHCP_OPTIONS_SIZE 312 +#define DHCP_MIN_SIZE 300 +#define PKT_BUF_SIZE 1500 + +#pragma pack(push, 1) +typedef struct { + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + uint32_t xid; + uint16_t secs; + uint16_t flags; + uint32_t ciaddr; + uint32_t yiaddr; + uint32_t siaddr; + uint32_t giaddr; + uint8_t chaddr[16]; + uint8_t sname[64]; + uint8_t file[128]; + uint32_t magic_cookie; + uint8_t options[DHCP_OPTIONS_SIZE]; +} dhcp_packet_t; + +typedef struct { + uint16_t src; + uint16_t dst; + uint16_t len; + uint16_t check; +} sdhcp_udp_header_t; +#pragma pack(pop) + +static uint16_t ip_checksum(const void *data, size_t len) +{ + const uint8_t *p = data; + uint32_t sum = 0; + + while (len > 1) { + sum += ((uint16_t)p[0] << 8) | p[1]; + p += 2; + len -= 2; + } + if (len) { + sum += ((uint16_t)p[0] << 8); + } + while (sum >> 16) { + sum = (sum & 0xffff) + (sum >> 16); + } + return (uint16_t)~sum; +} + +static uint32_t make_xid(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint32_t)ts.tv_nsec ^ (uint32_t)ts.tv_sec ^ (uint32_t)getpid(); +} + +static int get_mac(const char *ifname, uint8_t mac[6]) +{ + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) return -1; + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); + + if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) { + close(fd); + return -1; + } + + memcpy(mac, ifr.ifr_hwaddr.sa_data, 6); + close(fd); + return 0; +} + +static int open_sock(const char *ifname, int *ifindex_out) +{ + int ifindex = if_nametoindex(ifname); + if (!ifindex) { + errno = ENODEV; + return -1; + } + + int fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); + if (fd < 0) return -1; + + struct sockaddr_ll addr; + memset(&addr, 0, sizeof(addr)); + addr.sll_family = AF_PACKET; + addr.sll_protocol = htons(ETH_P_IP); + addr.sll_ifindex = ifindex; + + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(fd); + return -1; + } + + *ifindex_out = ifindex; + return fd; +} + +static size_t dhcp_init(dhcp_packet_t *d, uint32_t xid, const uint8_t mac[6], uint8_t msg_type) +{ + memset(d, 0, sizeof(*d)); + d->op = DHCP_OP_BOOTREQUEST; + d->htype = DHCP_HTYPE_ETHERNET; + d->hlen = DHCP_HLEN_ETHERNET; + d->xid = htonl(xid); + d->flags = htons(DHCP_FLAGS_BROADCAST); + memcpy(d->chaddr, mac, 6); + d->magic_cookie = htonl(DHCP_MAGIC_COOKIE); + + size_t o = 0; + d->options[o++] = DHCP_OPTION_MSG_TYPE; + d->options[o++] = 1; + d->options[o++] = msg_type; + + return o; +} + +static size_t build_discover(dhcp_packet_t *d, uint32_t xid, const uint8_t mac[6]) +{ + size_t o = dhcp_init(d, xid, mac, DHCPDISCOVER); + + uint8_t prl[] = { + DHCP_OPTION_SUBNET_MASK, + DHCP_OPTION_ROUTER, + DHCP_OPTION_DNS, + DHCP_OPTION_LEASE_TIME, + DHCP_OPTION_SERVER_IDENTIFIER + }; + + d->options[o++] = DHCP_OPTION_PARAMETER_REQUEST; + d->options[o++] = sizeof(prl); + memcpy(&d->options[o], prl, sizeof(prl)); + o += sizeof(prl); + + d->options[o++] = DHCP_OPTION_END; + + size_t len = offsetof(dhcp_packet_t, options) + o; + return len < DHCP_MIN_SIZE ? DHCP_MIN_SIZE : len; +} + +static size_t build_request(dhcp_packet_t *d, uint32_t xid, const uint8_t mac[6], + uint32_t req_ip, uint32_t server_id) +{ + size_t o = dhcp_init(d, xid, mac, DHCPREQUEST); + + d->options[o++] = DHCP_OPTION_REQUESTED_IP; + d->options[o++] = 4; + memcpy(&d->options[o], &req_ip, 4); + o += 4; + + d->options[o++] = DHCP_OPTION_SERVER_IDENTIFIER; + d->options[o++] = 4; + memcpy(&d->options[o], &server_id, 4); + o += 4; + + uint8_t prl[] = { + DHCP_OPTION_SUBNET_MASK, + DHCP_OPTION_ROUTER, + DHCP_OPTION_DNS, + DHCP_OPTION_LEASE_TIME + }; + + d->options[o++] = DHCP_OPTION_PARAMETER_REQUEST; + d->options[o++] = sizeof(prl); + memcpy(&d->options[o], prl, sizeof(prl)); + o += sizeof(prl); + + d->options[o++] = DHCP_OPTION_END; + + size_t len = offsetof(dhcp_packet_t, options) + o; + return len < DHCP_MIN_SIZE ? DHCP_MIN_SIZE : len; +} + +static int build_ip_udp(uint8_t *buf, size_t buflen, + const dhcp_packet_t *dhcp, size_t dhcp_len, + size_t *out_len) +{ + size_t total = sizeof(struct iphdr) + sizeof(sdhcp_udp_header_t) + dhcp_len; + if (buflen < total) { + errno = EMSGSIZE; + return -1; + } + + struct iphdr *ip = (struct iphdr *)buf; + sdhcp_udp_header_t *udp = (sdhcp_udp_header_t *)(buf + sizeof(struct iphdr)); + uint8_t *payload = buf + sizeof(struct iphdr) + sizeof(sdhcp_udp_header_t); + + memset(buf, 0, sizeof(struct iphdr) + sizeof(sdhcp_udp_header_t)); + memcpy(payload, dhcp, dhcp_len); + + ip->version = 4; + ip->ihl = 5; + ip->ttl = 64; + ip->protocol = IPPROTO_UDP; + ip->tot_len = htons(total); + ip->saddr = htonl(INADDR_ANY); + ip->daddr = htonl(INADDR_BROADCAST); + ip->check = htons(ip_checksum(ip, sizeof(*ip))); + + udp->src = htons(DHCP_CLIENT_PORT); + udp->dst = htons(DHCP_SERVER_PORT); + udp->len = htons(sizeof(sdhcp_udp_header_t) + dhcp_len); + udp->check = 0; /* IPv4 allows zero UDP checksum */ + + *out_len = total; + return 0; +} + +static int send_buf(int fd, int ifindex, const void *buf, size_t len) +{ + struct sockaddr_ll dst; + memset(&dst, 0, sizeof(dst)); + dst.sll_family = AF_PACKET; + dst.sll_protocol = htons(ETH_P_IP); + dst.sll_ifindex = ifindex; + dst.sll_halen = ETH_ALEN; + memset(dst.sll_addr, 0xff, ETH_ALEN); + + ssize_t n = sendto(fd, buf, len, 0, (struct sockaddr *)&dst, sizeof(dst)); + return n == (ssize_t)len ? 0 : -1; +} + +static int parse_options(const dhcp_packet_t *d, size_t len, + uint8_t *msg_type, sdhcp_lease_t *lease) +{ + size_t base = offsetof(dhcp_packet_t, options); + if (len < base) return -1; + if (ntohl(d->magic_cookie) != DHCP_MAGIC_COOKIE) return -1; + + *msg_type = 0; + lease->subnet_mask.s_addr = 0; + lease->router.s_addr = 0; + lease->dns_count = 0; + lease->lease_time = 0; + lease->server_identifier = 0; + + size_t i = 0, max = len - base; + while (i < max) { + uint8_t code = d->options[i++]; + if (code == DHCP_OPTION_PAD) continue; + if (code == DHCP_OPTION_END) break; + if (i >= max) return -1; + + uint8_t optlen = d->options[i++]; + if ((size_t)optlen > max - i) return -1; + + const uint8_t *v = &d->options[i]; + + switch (code) { + case DHCP_OPTION_MSG_TYPE: + if (optlen == 1) *msg_type = v[0]; + break; + case DHCP_OPTION_SUBNET_MASK: + if (optlen == 4) memcpy(&lease->subnet_mask.s_addr, v, 4); + break; + case DHCP_OPTION_ROUTER: + if (optlen >= 4) memcpy(&lease->router.s_addr, v, 4); + break; + case DHCP_OPTION_DNS: { + int count = optlen / 4; + if (count > SDHCP_MAX_DNS) count = SDHCP_MAX_DNS; + for (int j = 0; j < count; j++) { + memcpy(&lease->dns[j].s_addr, v + j * 4, 4); + } + lease->dns_count = count; + break; + } + case DHCP_OPTION_LEASE_TIME: + if (optlen == 4) { + uint32_t t; + memcpy(&t, v, 4); + lease->lease_time = ntohl(t); + } + break; + case DHCP_OPTION_SERVER_IDENTIFIER: + if (optlen == 4) memcpy(&lease->server_identifier, v, 4); + break; + } + + i += optlen; + } + + return 0; +} + +static int recv_dhcp(int fd, uint32_t xid, const uint8_t mac[6], int timeout_ms, + dhcp_packet_t *out, size_t *out_len, uint8_t *msg_type, + sdhcp_lease_t *lease) +{ + uint8_t buf[PKT_BUF_SIZE]; + struct timeval start, now; + + gettimeofday(&start, NULL); + + for (;;) { + gettimeofday(&now, NULL); + long elapsed = (now.tv_sec - start.tv_sec) * 1000L + + (now.tv_usec - start.tv_usec) / 1000L; + long remain = timeout_ms - elapsed; + if (remain <= 0) { + errno = ETIMEDOUT; + return -1; + } + + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + struct timeval tv; + tv.tv_sec = remain / 1000; + tv.tv_usec = (remain % 1000) * 1000; + + int rc = select(fd + 1, &rfds, NULL, NULL, &tv); + if (rc < 0) return -1; + if (rc == 0) { + errno = ETIMEDOUT; + return -1; + } + + ssize_t n = recv(fd, buf, sizeof(buf), 0); + if (n < (ssize_t)(sizeof(struct iphdr) + sizeof(sdhcp_udp_header_t))) continue; + + struct iphdr *ip = (struct iphdr *)buf; + if (ip->version != 4 || ip->ihl < 5 || ip->protocol != IPPROTO_UDP) continue; + + size_t ip_hlen = ip->ihl * 4; + if ((size_t)n < ip_hlen + sizeof(sdhcp_udp_header_t)) continue; + + sdhcp_udp_header_t *udp = (sdhcp_udp_header_t *)(buf + ip_hlen); + if (ntohs(udp->src) != DHCP_SERVER_PORT || ntohs(udp->dst) != DHCP_CLIENT_PORT) continue; + + uint16_t udp_len = ntohs(udp->len); + if (udp_len < sizeof(sdhcp_udp_header_t)) continue; + + size_t dhcp_len = udp_len - sizeof(sdhcp_udp_header_t); + if (dhcp_len < offsetof(dhcp_packet_t, options)) continue; + if (dhcp_len > sizeof(*out)) continue; + if ((size_t)n < ip_hlen + sizeof(sdhcp_udp_header_t) + dhcp_len) continue; + + const dhcp_packet_t *d = (const dhcp_packet_t *)(buf + ip_hlen + sizeof(sdhcp_udp_header_t)); + + if (d->op != DHCP_OP_BOOTREPLY) continue; + if (d->htype != DHCP_HTYPE_ETHERNET || d->hlen != DHCP_HLEN_ETHERNET) continue; + if (ntohl(d->xid) != xid) continue; + if (memcmp(d->chaddr, mac, 6) != 0) continue; + + lease->yiaddr.s_addr = d->yiaddr; + + if (parse_options(d, dhcp_len, msg_type, lease) < 0) continue; + + memcpy(out, d, dhcp_len); + *out_len = dhcp_len; + return 0; + } +} + +int sdhcp_acquire(const char *ifname, int timeout_ms, int retries, sdhcp_lease_t *lease) +{ + if (!ifname || !lease || timeout_ms <= 0 || retries <= 0) { + errno = EINVAL; + return -1; + } + + memset(lease, 0, sizeof(*lease)); + + uint8_t mac[6]; + if (get_mac(ifname, mac) < 0) return -1; + + int ifindex; + int fd = open_sock(ifname, &ifindex); + if (fd < 0) return -1; + + uint32_t xid = make_xid(); + dhcp_packet_t tx, rx; + uint8_t buf[PKT_BUF_SIZE]; + size_t tx_len, rx_len; + uint8_t msg_type; + + for (int attempt = 0; attempt < retries; attempt++) { + size_t dhcp_len = build_discover(&tx, xid, mac); + if (build_ip_udp(buf, sizeof(buf), &tx, dhcp_len, &tx_len) < 0) break; + if (send_buf(fd, ifindex, buf, tx_len) < 0) continue; + + if (recv_dhcp(fd, xid, mac, timeout_ms, &rx, &rx_len, &msg_type, lease) < 0) { + continue; + } + if (msg_type != DHCPOFFER) continue; + if (!rx.yiaddr || !lease->server_identifier) continue; + + dhcp_len = build_request(&tx, xid, mac, rx.yiaddr, lease->server_identifier); + if (build_ip_udp(buf, sizeof(buf), &tx, dhcp_len, &tx_len) < 0) break; + if (send_buf(fd, ifindex, buf, tx_len) < 0) continue; + + if (recv_dhcp(fd, xid, mac, timeout_ms, &rx, &rx_len, &msg_type, lease) < 0) { + continue; + } + + if (msg_type == DHCPNAK) { + errno = EPROTO; + close(fd); + return -1; + } + + if (msg_type == DHCPACK) { + close(fd); + return 0; + } + } + + close(fd); + errno = ETIMEDOUT; + return -1; +} diff --git a/pipe/linux/dhcp/dhcp.h b/pipe/linux/dhcp/dhcp.h new file mode 100644 index 0000000..dc0b9e8 --- /dev/null +++ b/pipe/linux/dhcp/dhcp.h @@ -0,0 +1,24 @@ +#ifndef DHCP_H +#define DHCP_H + +#include +#include + +#define SDHCP_MAX_DNS 4 + +typedef struct { + struct in_addr yiaddr; + struct in_addr subnet_mask; + struct in_addr router; + struct in_addr dns[SDHCP_MAX_DNS]; + int dns_count; + uint32_t lease_time; /* host byte order */ + uint32_t server_identifier; /* network byte order */ +} sdhcp_lease_t; + +// Simple one-shot DHCP acquisition for connection to Wii U +// +// Return 0 on success, -1 on error with errno set +int sdhcp_acquire(const char *ifname, int timeout_ms, int retries, sdhcp_lease_t *lease); + +#endif diff --git a/pipe/linux/dhcp/dhcpc.c b/pipe/linux/dhcp/dhcpc.c deleted file mode 100644 index 9917b9b..0000000 --- a/pipe/linux/dhcp/dhcpc.c +++ /dev/null @@ -1,535 +0,0 @@ -/* dhcpc.c - * - * udhcp DHCP client - * - * Russ Dill July 2001 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dhcpd.h" -#include "dhcpc.h" -#include "options.h" -#include "clientpacket.h" -#include "clientsocket.h" -#include "script.h" -#include "socket.h" -#include "common.h" -#include "signalpipe.h" - -static int state; -static unsigned long requested_ip; /* = 0 */ -static unsigned long server_addr; -static unsigned long timeout; -static int packet_num; /* = 0 */ -static int fd = -1; - -#define LISTEN_NONE 0 -#define LISTEN_KERNEL 1 -#define LISTEN_RAW 2 -static int listen_mode; - -struct client_config_t client_config = { - /* Default options. */ - abort_if_no_lease: 0, - foreground: 0, - quit_after_lease: 0, - background_if_no_lease: 0, - interface: "eth0", - pidfile: NULL, - callback: NULL, - clientid: NULL, - hostname: NULL, - fqdn: NULL, - ifindex: 0, - arp: "\0\0\0\0\0\0", /* appease gcc-3.0 */ -}; - -#ifndef IN_BUSYBOX -static void __attribute__ ((noreturn)) show_usage(void) -{ - printf( -"Usage: udhcpc [OPTIONS]\n\n" -" -c, --clientid=CLIENTID Client identifier\n" -" -H, --hostname=HOSTNAME Client hostname\n" -" -h Alias for -H\n" -" -F, --fqdn=FQDN Client fully qualified domain name\n" -" -f, --foreground Do not fork after getting lease\n" -" -b, --background Fork to background if lease cannot be\n" -" immediately negotiated.\n" -" -i, --interface=INTERFACE Interface to use (default: eth0)\n" -" -n, --now Exit with failure if lease cannot be\n" -" immediately negotiated.\n" -" -p, --pidfile=file Store process ID of daemon in file\n" -" -q, --quit Quit after obtaining lease\n" -" -r, --request=IP IP address to request (default: none)\n" -" -s, --script=file Run file at dhcp events (default:\n" -" " DEFAULT_SCRIPT ")\n" -" -v, --version Display version\n" - ); - exit(0); -} -#else -#define show_usage bb_show_usage -extern void show_usage(void) __attribute__ ((noreturn)); -#endif - - -/* just a little helper */ -static void change_mode(int new_mode) -{ - DEBUG(LOG_INFO, "entering %s listen mode", - new_mode ? (new_mode == 1 ? "kernel" : "raw") : "none"); - if (fd >= 0) close(fd); - fd = -1; - listen_mode = new_mode; -} - - -/* perform a renew */ -static void perform_renew(void) -{ - LOG(LOG_INFO, "Performing a DHCP renew"); - switch (state) { - case BOUND: - change_mode(LISTEN_KERNEL); - case RENEWING: - case REBINDING: - state = RENEW_REQUESTED; - break; - case RENEW_REQUESTED: /* impatient are we? fine, square 1 */ - run_script(NULL, "deconfig"); - case REQUESTING: - case RELEASED: - change_mode(LISTEN_RAW); - state = INIT_SELECTING; - break; - case INIT_SELECTING: - break; - } - - /* start things over */ - packet_num = 0; - - /* Kill any timeouts because the user wants this to hurry along */ - timeout = 0; -} - - -/* perform a release */ -static void perform_release(void) -{ - char buffer[16]; - struct in_addr temp_addr; - - /* send release packet */ - if (state == BOUND || state == RENEWING || state == REBINDING) { - temp_addr.s_addr = server_addr; - sprintf(buffer, "%s", inet_ntoa(temp_addr)); - temp_addr.s_addr = requested_ip; - LOG(LOG_INFO, "Unicasting a release of %s to %s", - inet_ntoa(temp_addr), buffer); - send_release(server_addr, requested_ip); /* unicast */ - run_script(NULL, "deconfig"); - } - LOG(LOG_INFO, "Entering released state"); - - change_mode(LISTEN_NONE); - state = RELEASED; - timeout = 0x7fffffff; -} - - -static void client_background(void) -{ - background(client_config.pidfile); - client_config.foreground = 1; /* Do not fork again. */ - client_config.background_if_no_lease = 0; -} - - -int udhcpc_main() -{ - uint8_t *temp, *message; - unsigned long t1 = 0, t2 = 0, xid = 0; - unsigned long start = 0, lease; - fd_set rfds; - int retval; - struct timeval tv; - int c, len; - struct dhcpMessage packet; - struct in_addr temp_addr; - long now; - int max_fd; - int sig; - - static const struct option arg_options[] = { - {"clientid", required_argument, 0, 'c'}, - {"foreground", no_argument, 0, 'f'}, - {"background", no_argument, 0, 'b'}, - {"hostname", required_argument, 0, 'H'}, - {"hostname", required_argument, 0, 'h'}, - {"fqdn", required_argument, 0, 'F'}, - {"interface", required_argument, 0, 'i'}, - {"now", no_argument, 0, 'n'}, - {"pidfile", required_argument, 0, 'p'}, - {"quit", no_argument, 0, 'q'}, - {"request", required_argument, 0, 'r'}, - {"script", required_argument, 0, 's'}, - {"version", no_argument, 0, 'v'}, - {0, 0, 0, 0} - }; - - /* get options */ - // while (1) { - // int option_index = 0; - // c = getopt_long(argc, argv, "c:fbH:h:F:i:np:qr:s:v", arg_options, &option_index); - // if (c == -1) break; - - // switch (c) { - // case 'c': - // len = strlen(optarg) > 255 ? 255 : strlen(optarg); - // if (client_config.clientid) free(client_config.clientid); - // client_config.clientid = xmalloc(len + 2); - // client_config.clientid[OPT_CODE] = DHCP_CLIENT_ID; - // client_config.clientid[OPT_LEN] = len; - // client_config.clientid[OPT_DATA] = '\0'; - // strncpy(client_config.clientid + OPT_DATA, optarg, len); - // break; - // case 'f': - // client_config.foreground = 1; - // break; - // case 'b': - // client_config.background_if_no_lease = 1; - // break; - // case 'h': - // case 'H': - // len = strlen(optarg) > 255 ? 255 : strlen(optarg); - // if (client_config.hostname) free(client_config.hostname); - // client_config.hostname = xmalloc(len + 2); - // client_config.hostname[OPT_CODE] = DHCP_HOST_NAME; - // client_config.hostname[OPT_LEN] = len; - // strncpy(client_config.hostname + 2, optarg, len); - // break; - // case 'F': - // len = strlen(optarg) > 255 ? 255 : strlen(optarg); - // if (client_config.fqdn) free(client_config.fqdn); - // client_config.fqdn = xmalloc(len + 5); - // client_config.fqdn[OPT_CODE] = DHCP_FQDN; - // client_config.fqdn[OPT_LEN] = len + 3; - // /* Flags: 0000NEOS - // S: 1 => Client requests Server to update A RR in DNS as well as PTR - // O: 1 => Server indicates to client that DNS has been updated regardless - // E: 1 => Name data is DNS format, i.e. <4>host<6>domain<4>com<0> not "host.domain.com" - // N: 1 => Client requests Server to not update DNS - // */ - // client_config.fqdn[OPT_LEN + 1] = 0x1; - // client_config.fqdn[OPT_LEN + 2] = 0; - // client_config.fqdn[OPT_LEN + 3] = 0; - // strncpy(client_config.fqdn + 5, optarg, len); - // break; - // case 'i': - // client_config.interface = optarg; - // break; - // case 'n': - // client_config.abort_if_no_lease = 1; - // break; - // case 'p': - // client_config.pidfile = optarg; - // break; - // case 'q': - // client_config.quit_after_lease = 1; - // break; - // case 'r': - // requested_ip = inet_addr(optarg); - // break; - // case 's': - // client_config.script = optarg; - // break; - // case 'v': - // printf("udhcpcd, version %s\n\n", VERSION); - // return 0; - // break; - // default: - // show_usage(); - // } - // } - - /* Start the log, sanitize fd's, and write a pid file */ - start_log_and_pid("udhcpc", client_config.pidfile); - - if (read_interface(client_config.interface, &client_config.ifindex, - NULL, client_config.arp) < 0) - return 1; - - if (!client_config.clientid) { - client_config.clientid = xmalloc(6 + 3); - client_config.clientid[OPT_CODE] = DHCP_CLIENT_ID; - client_config.clientid[OPT_LEN] = 7; - client_config.clientid[OPT_DATA] = 1; - memcpy(client_config.clientid + 3, client_config.arp, 6); - } - - /* setup the signal pipe */ - udhcp_sp_setup(); - - state = INIT_SELECTING; - run_script(NULL, "deconfig"); - change_mode(LISTEN_RAW); - - timeout = 0; - - for (;;) { - - tv.tv_sec = timeout - uptime(); - tv.tv_usec = 0; - - if (listen_mode != LISTEN_NONE && fd < 0) { - if (listen_mode == LISTEN_KERNEL) - fd = listen_socket(INADDR_ANY, CLIENT_PORT, client_config.interface); - else - fd = raw_socket(client_config.ifindex); - if (fd < 0) { - LOG(LOG_ERR, "FATAL: couldn't listen on socket, %m"); - return 0; - } - } - max_fd = udhcp_sp_fd_set(&rfds, fd); - - if (tv.tv_sec > 0) { - DEBUG(LOG_INFO, "Waiting on select..."); - retval = select(max_fd + 1, &rfds, NULL, NULL, &tv); - } else retval = 0; /* If we already timed out, fall through */ - - now = uptime(); - if (retval == 0) { - /* timeout dropped to zero */ - switch (state) { - case INIT_SELECTING: - if (packet_num < 3) { - if (packet_num == 0) - xid = random_xid(); - - /* send discover packet */ - send_discover(xid, requested_ip); /* broadcast */ - - timeout = now + ((packet_num == 2) ? 4 : 2); - packet_num++; - } else { - run_script(NULL, "leasefail"); - if (client_config.background_if_no_lease) { - LOG(LOG_INFO, "No lease, forking to background."); - client_background(); - } else if (client_config.abort_if_no_lease) { - LOG(LOG_INFO, "No lease, failing."); - return 1; - } - /* wait to try again */ - packet_num = 0; - timeout = now + 60; - } - break; - case RENEW_REQUESTED: - case REQUESTING: - if (packet_num < 3) { - /* send request packet */ - if (state == RENEW_REQUESTED) - send_renew(xid, server_addr, requested_ip); /* unicast */ - else send_selecting(xid, server_addr, requested_ip); /* broadcast */ - - timeout = now + ((packet_num == 2) ? 10 : 2); - packet_num++; - } else { - /* timed out, go back to init state */ - if (state == RENEW_REQUESTED) run_script(NULL, "deconfig"); - state = INIT_SELECTING; - timeout = now; - packet_num = 0; - change_mode(LISTEN_RAW); - } - break; - case BOUND: - /* Lease is starting to run out, time to enter renewing state */ - state = RENEWING; - change_mode(LISTEN_KERNEL); - DEBUG(LOG_INFO, "Entering renew state"); - /* fall right through */ - case RENEWING: - /* Either set a new T1, or enter REBINDING state */ - if ((t2 - t1) <= (lease / 14400 + 1)) { - /* timed out, enter rebinding state */ - state = REBINDING; - timeout = now + (t2 - t1); - DEBUG(LOG_INFO, "Entering rebinding state"); - } else { - /* send a request packet */ - send_renew(xid, server_addr, requested_ip); /* unicast */ - - t1 = (t2 - t1) / 2 + t1; - timeout = t1 + start; - } - break; - case REBINDING: - /* Either set a new T2, or enter INIT state */ - if ((lease - t2) <= (lease / 14400 + 1)) { - /* timed out, enter init state */ - state = INIT_SELECTING; - LOG(LOG_INFO, "Lease lost, entering init state"); - run_script(NULL, "deconfig"); - timeout = now; - packet_num = 0; - change_mode(LISTEN_RAW); - } else { - /* send a request packet */ - send_renew(xid, 0, requested_ip); /* broadcast */ - - t2 = (lease - t2) / 2 + t2; - timeout = t2 + start; - } - break; - case RELEASED: - /* yah, I know, *you* say it would never happen */ - timeout = 0x7fffffff; - break; - } - } else if (retval > 0 && listen_mode != LISTEN_NONE && FD_ISSET(fd, &rfds)) { - /* a packet is ready, read it */ - - if (listen_mode == LISTEN_KERNEL) - len = get_packet(&packet, fd); - else len = get_raw_packet(&packet, fd); - - if (len == -1 && errno != EINTR) { - DEBUG(LOG_INFO, "error on read, %m, reopening socket"); - change_mode(listen_mode); /* just close and reopen */ - } - if (len < 0) continue; - - if (packet.xid != xid) { - DEBUG(LOG_INFO, "Ignoring XID %lx (our xid is %lx)", - (unsigned long) packet.xid, xid); - continue; - } - - if ((message = get_option(&packet, DHCP_MESSAGE_TYPE)) == NULL) { - DEBUG(LOG_ERR, "couldnt get option from packet -- ignoring"); - continue; - } - - switch (state) { - case INIT_SELECTING: - /* Must be a DHCPOFFER to one of our xid's */ - if (*message == DHCPOFFER) { - if ((temp = get_option(&packet, DHCP_SERVER_ID))) { - memcpy(&server_addr, temp, 4); - xid = packet.xid; - requested_ip = packet.yiaddr; - - /* enter requesting state */ - state = REQUESTING; - timeout = now; - packet_num = 0; - } else { - DEBUG(LOG_ERR, "No server ID in message"); - } - } - break; - case RENEW_REQUESTED: - case REQUESTING: - case RENEWING: - case REBINDING: - if (*message == DHCPACK) { - if (!(temp = get_option(&packet, DHCP_LEASE_TIME))) { - LOG(LOG_ERR, "No lease time with ACK, using 1 hour lease"); - lease = 60 * 60; - } else { - memcpy(&lease, temp, 4); - lease = ntohl(lease); - } - - /* enter bound state */ - t1 = lease / 2; - - /* little fixed point for n * .875 */ - t2 = (lease * 0x7) >> 3; - temp_addr.s_addr = packet.yiaddr; - LOG(LOG_INFO, "Lease of %s obtained, lease time %ld", - inet_ntoa(temp_addr), lease); - start = now; - timeout = t1 + start; - requested_ip = packet.yiaddr; - run_script(&packet, - ((state == RENEWING || state == REBINDING) ? "renew" : "bound")); - - state = BOUND; - change_mode(LISTEN_NONE); - if (client_config.quit_after_lease) - return 0; - if (!client_config.foreground) - client_background(); - - } else if (*message == DHCPNAK) { - /* return to init state */ - LOG(LOG_INFO, "Received DHCP NAK"); - run_script(&packet, "nak"); - if (state != REQUESTING) - run_script(NULL, "deconfig"); - state = INIT_SELECTING; - timeout = now; - requested_ip = 0; - packet_num = 0; - change_mode(LISTEN_RAW); - sleep(3); /* avoid excessive network traffic */ - } - break; - /* case BOUND, RELEASED: - ignore all packets */ - } - } else if (retval > 0 && (sig = udhcp_sp_read(&rfds))) { - switch (sig) { - case SIGUSR1: - perform_renew(); - break; - case SIGUSR2: - perform_release(); - break; - case SIGTERM: - LOG(LOG_INFO, "Received SIGTERM"); - return 0; - } - } else if (retval == -1 && errno == EINTR) { - /* a signal was caught */ - } else { - /* An error occured */ - DEBUG(LOG_ERR, "Error on select"); - } - - } - return 0; -} diff --git a/pipe/linux/dhcp/dhcpc.h b/pipe/linux/dhcp/dhcpc.h deleted file mode 100644 index c1a0fbe..0000000 --- a/pipe/linux/dhcp/dhcpc.h +++ /dev/null @@ -1,41 +0,0 @@ -/* dhcpc.h */ -#ifndef _DHCPC_H -#define _DHCPC_H - -#define DEFAULT_SCRIPT "/etc/udhcpc/default.script" - -/* allow libbb_udhcp.h to redefine DEFAULT_SCRIPT */ -#include "libbb_udhcp.h" - -#define INIT_SELECTING 0 -#define REQUESTING 1 -#define BOUND 2 -#define RENEWING 3 -#define REBINDING 4 -#define INIT_REBOOT 5 -#define RENEW_REQUESTED 6 -#define RELEASED 7 - -typedef void(*udhcpc_callback_t)(const char *name, char **envp, void *data); - -struct client_config_t { - char foreground; /* Do not fork */ - char quit_after_lease; /* Quit after obtaining lease */ - char abort_if_no_lease; /* Abort if no lease */ - char background_if_no_lease; /* Fork to background if no lease */ - char *interface; /* The name of the interface to use */ - char *pidfile; /* Optionally store the process ID */ - udhcpc_callback_t callback; /* User callback to run at dhcp events */ - void *callback_data; /* User callback to run at dhcp events */ - uint8_t *clientid; /* Optional client id to use */ - uint8_t *hostname; /* Optional hostname to use */ - uint8_t *fqdn; /* Optional fully qualified domain name to use */ - int ifindex; /* Index number of the interface to use */ - uint8_t arp[6]; /* Our arp address */ -}; - -extern struct client_config_t client_config; - -extern int udhcpc_main(); - -#endif diff --git a/pipe/linux/dhcp/dhcpd.h b/pipe/linux/dhcp/dhcpd.h deleted file mode 100644 index 65c8348..0000000 --- a/pipe/linux/dhcp/dhcpd.h +++ /dev/null @@ -1,141 +0,0 @@ -/* dhcpd.h */ -#ifndef _DHCPD_H -#define _DHCPD_H - -#include -#include - -#include "libbb_udhcp.h" -#include "leases.h" -#include "version.h" - -/************************************/ -/* Defaults _you_ may want to tweak */ -/************************************/ - -/* the period of time the client is allowed to use that address */ -#define LEASE_TIME (60*60*24*10) /* 10 days of seconds */ -#define LEASES_FILE "/var/lib/misc/udhcpd.leases" - -/* where to find the DHCP server configuration file */ -#define DHCPD_CONF_FILE "/etc/udhcpd.conf" - -/*****************************************************************/ -/* Do not modify below here unless you know what you are doing!! */ -/*****************************************************************/ - -/* DHCP protocol -- see RFC 2131 */ -#define SERVER_PORT 67 -#define CLIENT_PORT 68 - -#define DHCP_MAGIC 0x63825363 - -/* DHCP option codes (partial list) */ -#define DHCP_PADDING 0x00 -#define DHCP_SUBNET 0x01 -#define DHCP_TIME_OFFSET 0x02 -#define DHCP_ROUTER 0x03 -#define DHCP_TIME_SERVER 0x04 -#define DHCP_NAME_SERVER 0x05 -#define DHCP_DNS_SERVER 0x06 -#define DHCP_LOG_SERVER 0x07 -#define DHCP_COOKIE_SERVER 0x08 -#define DHCP_LPR_SERVER 0x09 -#define DHCP_HOST_NAME 0x0c -#define DHCP_BOOT_SIZE 0x0d -#define DHCP_DOMAIN_NAME 0x0f -#define DHCP_SWAP_SERVER 0x10 -#define DHCP_ROOT_PATH 0x11 -#define DHCP_IP_TTL 0x17 -#define DHCP_MTU 0x1a -#define DHCP_BROADCAST 0x1c -#define DHCP_NTP_SERVER 0x2a -#define DHCP_WINS_SERVER 0x2c -#define DHCP_REQUESTED_IP 0x32 -#define DHCP_LEASE_TIME 0x33 -#define DHCP_OPTION_OVER 0x34 -#define DHCP_MESSAGE_TYPE 0x35 -#define DHCP_SERVER_ID 0x36 -#define DHCP_PARAM_REQ 0x37 -#define DHCP_MESSAGE 0x38 -#define DHCP_MAX_SIZE 0x39 -#define DHCP_T1 0x3a -#define DHCP_T2 0x3b -#define DHCP_VENDOR 0x3c -#define DHCP_CLIENT_ID 0x3d -#define DHCP_FQDN 0x51 - -#define DHCP_END 0xFF - - -#define BOOTREQUEST 1 -#define BOOTREPLY 2 - -#define ETH_10MB 1 -#define ETH_10MB_LEN 6 - -#define DHCPDISCOVER 1 -#define DHCPOFFER 2 -#define DHCPREQUEST 3 -#define DHCPDECLINE 4 -#define DHCPACK 5 -#define DHCPNAK 6 -#define DHCPRELEASE 7 -#define DHCPINFORM 8 - -#define BROADCAST_FLAG 0x8000 - -#define OPTION_FIELD 0 -#define FILE_FIELD 1 -#define SNAME_FIELD 2 - -/* miscellaneous defines */ -#define MAC_BCAST_ADDR (uint8_t *) "\xff\xff\xff\xff\xff\xff" -#define OPT_CODE 0 -#define OPT_LEN 1 -#define OPT_DATA 2 - -struct option_set { - uint8_t *data; - struct option_set *next; -}; - -struct static_lease { - uint8_t *mac; - uint32_t *ip; - struct static_lease *next; -}; - -struct server_config_t { - uint32_t server; /* Our IP, in network order */ - uint32_t start; /* Start address of leases, network order */ - uint32_t end; /* End of leases, network order */ - struct option_set *options; /* List of DHCP options loaded from the config file */ - char *interface; /* The name of the interface to use */ - int ifindex; /* Index number of the interface to use */ - uint8_t arp[6]; /* Our arp address */ - unsigned long lease; /* lease time in seconds (host order) */ - unsigned long max_leases; /* maximum number of leases (including reserved address) */ - char remaining; /* should the lease file be interpreted as lease time remaining, or - * as the time the lease expires */ - unsigned long auto_time; /* how long should udhcpd wait before writing a config file. - * if this is zero, it will only write one on SIGUSR1 */ - unsigned long decline_time; /* how long an address is reserved if a client returns a - * decline message */ - unsigned long conflict_time; /* how long an arp conflict offender is leased for */ - unsigned long offer_time; /* how long an offered address is reserved */ - unsigned long min_lease; /* minimum lease a client can request*/ - char *lease_file; - char *pidfile; - char *notify_file; /* What to run whenever leases are written */ - uint32_t siaddr; /* next server bootp option */ - char *sname; /* bootp server name */ - char *boot_file; /* bootp boot file option */ - struct static_lease *static_leases; /* List of ip/mac pairs to assign static leases */ -}; - -extern struct server_config_t server_config; -extern struct dhcpOfferedAddr *leases; - - -#endif diff --git a/pipe/linux/dhcp/files.h b/pipe/linux/dhcp/files.h deleted file mode 100644 index 279738a..0000000 --- a/pipe/linux/dhcp/files.h +++ /dev/null @@ -1,17 +0,0 @@ -/* files.h */ -#ifndef _FILES_H -#define _FILES_H - -struct config_keyword { - const char *keyword; - int (* const handler)(const char *line, void *var); - void *var; - const char *def; -}; - - -int read_config(const char *file); -void write_leases(void); -void read_leases(const char *file); - -#endif diff --git a/pipe/linux/dhcp/leases.h b/pipe/linux/dhcp/leases.h deleted file mode 100644 index b13fa72..0000000 --- a/pipe/linux/dhcp/leases.h +++ /dev/null @@ -1,23 +0,0 @@ -/* leases.h */ -#ifndef _LEASES_H -#define _LEASES_H - - -struct dhcpOfferedAddr { - uint8_t chaddr[16]; - uint32_t yiaddr; /* network order */ - uint32_t expires; /* host order */ -}; - -extern uint8_t blank_chaddr[]; - -void clear_lease(uint8_t *chaddr, uint32_t yiaddr); -struct dhcpOfferedAddr *add_lease(uint8_t *chaddr, uint32_t yiaddr, unsigned long lease); -int lease_expired(struct dhcpOfferedAddr *lease); -struct dhcpOfferedAddr *oldest_expired_lease(void); -struct dhcpOfferedAddr *find_lease_by_chaddr(uint8_t *chaddr); -struct dhcpOfferedAddr *find_lease_by_yiaddr(uint32_t yiaddr); -uint32_t find_address(int check_expired); - - -#endif diff --git a/pipe/linux/dhcp/libbb_udhcp.h b/pipe/linux/dhcp/libbb_udhcp.h deleted file mode 100644 index 51e1571..0000000 --- a/pipe/linux/dhcp/libbb_udhcp.h +++ /dev/null @@ -1,54 +0,0 @@ -/* libbb_udhcp.h - busybox compatability wrapper */ - -/* bit of a hack, do this no matter what the order of the includes. - * (for busybox) */ - -#ifdef CONFIG_INSTALL_NO_USR -#undef DEFAULT_SCRIPT -#define DEFAULT_SCRIPT "/share/udhcpc/default.script" -#endif - -#ifndef _LIBBB_UDHCP_H -#define _LIBBB_UDHCP_H - -#ifdef IN_BUSYBOX -#include "busybox.h" - -#ifdef CONFIG_FEATURE_UDHCP_SYSLOG -#define UDHCP_SYSLOG -#endif - -#ifdef CONFIG_FEATURE_UDHCP_DEBUG -#define UDHCP_DEBUG -#endif - -#define COMBINED_BINARY -#include "version.h" - -#define xfopen bb_xfopen - -#else /* ! BB_VER */ - -#include -#include -#include - -#define TRUE 1 -#define FALSE 0 - -#define xmalloc malloc -#define xcalloc calloc - -static inline FILE *xfopen(const char *file, const char *mode) -{ - FILE *fp; - if (!(fp = fopen(file, mode))) { - perror("could not open input file"); - exit(0); - } - return fp; -} - -#endif /* BB_VER */ - -#endif /* _LIBBB_UDHCP_H */ diff --git a/pipe/linux/dhcp/options.c b/pipe/linux/dhcp/options.c deleted file mode 100644 index d9c32a8..0000000 --- a/pipe/linux/dhcp/options.c +++ /dev/null @@ -1,234 +0,0 @@ -/* - * options.c -- DHCP server option packet tools - * Rewrite by Russ Dill July 2001 - */ - -#include -#include - -#include "dhcpd.h" -#include "files.h" -#include "options.h" -#include "common.h" - - -/* supported options are easily added here */ -struct dhcp_option dhcp_options[] = { - /* name[10] flags code */ - {"subnet", OPTION_IP | OPTION_REQ, 0x01}, - {"timezone", OPTION_S32, 0x02}, - {"router", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x03}, - {"timesvr", OPTION_IP | OPTION_LIST, 0x04}, - {"namesvr", OPTION_IP | OPTION_LIST, 0x05}, - {"dns", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x06}, - {"logsvr", OPTION_IP | OPTION_LIST, 0x07}, - {"cookiesvr", OPTION_IP | OPTION_LIST, 0x08}, - {"lprsvr", OPTION_IP | OPTION_LIST, 0x09}, - {"hostname", OPTION_STRING | OPTION_REQ, 0x0c}, - {"bootsize", OPTION_U16, 0x0d}, - {"domain", OPTION_STRING | OPTION_REQ, 0x0f}, - {"swapsvr", OPTION_IP, 0x10}, - //We had the possibility for the client to ask for a rootpath - //closes http://bugs.debian.org/279110 (ericvb@debian.org) - //{"rootpath", OPTION_STRING, 0x11}, - {"rootpath", OPTION_STRING | OPTION_REQ, 0x11}, - {"ipttl", OPTION_U8, 0x17}, - {"mtu", OPTION_U16, 0x1a}, - {"broadcast", OPTION_IP | OPTION_REQ, 0x1c}, - {"nisdomain", OPTION_STRING | OPTION_REQ, 0x28}, - {"nissrv", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x29}, - {"ntpsrv", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x2a}, - {"wins", OPTION_IP | OPTION_LIST, 0x2c}, - {"requestip", OPTION_IP, 0x32}, - {"lease", OPTION_U32, 0x33}, - {"dhcptype", OPTION_U8, 0x35}, - {"serverid", OPTION_IP, 0x36}, - {"message", OPTION_STRING, 0x38}, - {"tftp", OPTION_STRING, 0x42}, - {"bootfile", OPTION_STRING, 0x43}, - {"wpad", OPTION_STRING, 0xfc}, - {"", 0x00, 0x00} -}; - -/* Lengths of the different option types */ -int option_lengths[] = { - [OPTION_IP] = 4, - [OPTION_IP_PAIR] = 8, - [OPTION_BOOLEAN] = 1, - [OPTION_STRING] = 1, - [OPTION_U8] = 1, - [OPTION_U16] = 2, - [OPTION_S16] = 2, - [OPTION_U32] = 4, - [OPTION_S32] = 4 -}; - - -/* get an option with bounds checking (warning, not aligned). */ -uint8_t *get_option(struct dhcpMessage *packet, int code) -{ - int i, length; - uint8_t *optionptr; - int over = 0, done = 0, curr = OPTION_FIELD; - - optionptr = packet->options; - i = 0; - length = 308; - while (!done) { - if (i >= length) { - LOG(LOG_WARNING, "bogus packet, option fields too long."); - return NULL; - } - if (optionptr[i + OPT_CODE] == code) { - if (i + 1 + optionptr[i + OPT_LEN] >= length) { - LOG(LOG_WARNING, "bogus packet, option fields too long."); - return NULL; - } - return optionptr + i + 2; - } - switch (optionptr[i + OPT_CODE]) { - case DHCP_PADDING: - i++; - break; - case DHCP_OPTION_OVER: - if (i + 1 + optionptr[i + OPT_LEN] >= length) { - LOG(LOG_WARNING, "bogus packet, option fields too long."); - return NULL; - } - over = optionptr[i + 3]; - i += optionptr[OPT_LEN] + 2; - break; - case DHCP_END: - if (curr == OPTION_FIELD && over & FILE_FIELD) { - optionptr = packet->file; - i = 0; - length = 128; - curr = FILE_FIELD; - } else if (curr == FILE_FIELD && over & SNAME_FIELD) { - optionptr = packet->sname; - i = 0; - length = 64; - curr = SNAME_FIELD; - } else done = 1; - break; - default: - i += optionptr[OPT_LEN + i] + 2; - } - } - return NULL; -} - - -/* return the position of the 'end' option (no bounds checking) */ -int end_option(uint8_t *optionptr) -{ - int i = 0; - - while (optionptr[i] != DHCP_END) { - if (optionptr[i] == DHCP_PADDING) i++; - else i += optionptr[i + OPT_LEN] + 2; - } - return i; -} - - -/* add an option string to the options (an option string contains an option code, - * length, then data) */ -int add_option_string(uint8_t *optionptr, uint8_t *string) -{ - int end = end_option(optionptr); - - /* end position + string length + option code/length + end option */ - if (end + string[OPT_LEN] + 2 + 1 >= 308) { - LOG(LOG_ERR, "Option 0x%02x did not fit into the packet!", string[OPT_CODE]); - return 0; - } - DEBUG(LOG_INFO, "adding option 0x%02x", string[OPT_CODE]); - memcpy(optionptr + end, string, string[OPT_LEN] + 2); - optionptr[end + string[OPT_LEN] + 2] = DHCP_END; - return string[OPT_LEN] + 2; -} - - -/* add a one to four byte option to a packet */ -int add_simple_option(uint8_t *optionptr, uint8_t code, uint32_t data) -{ - char length = 0; - int i; - uint8_t option[2 + 4]; - uint8_t *u8; - uint16_t *u16; - uint32_t *u32; - uint32_t aligned; - u8 = (uint8_t *) &aligned; - u16 = (uint16_t *) &aligned; - u32 = &aligned; - - for (i = 0; dhcp_options[i].code; i++) - if (dhcp_options[i].code == code) { - length = option_lengths[dhcp_options[i].flags & TYPE_MASK]; - } - - if (!length) { - DEBUG(LOG_ERR, "Could not add option 0x%02x", code); - return 0; - } - - option[OPT_CODE] = code; - option[OPT_LEN] = length; - - switch (length) { - case 1: *u8 = data; break; - case 2: *u16 = data; break; - case 4: *u32 = data; break; - } - memcpy(option + 2, &aligned, length); - return add_option_string(optionptr, option); -} - - -/* find option 'code' in opt_list */ -struct option_set *find_option(struct option_set *opt_list, char code) -{ - while (opt_list && opt_list->data[OPT_CODE] < code) - opt_list = opt_list->next; - - if (opt_list && opt_list->data[OPT_CODE] == code) return opt_list; - else return NULL; -} - - -/* add an option to the opt_list */ -void attach_option(struct option_set **opt_list, struct dhcp_option *option, char *buffer, int length) -{ - struct option_set *existing, *new, **curr; - - /* add it to an existing option */ - if ((existing = find_option(*opt_list, option->code))) { - DEBUG(LOG_INFO, "Attaching option %s to existing member of list", option->name); - if (option->flags & OPTION_LIST) { - if (existing->data[OPT_LEN] + length <= 255) { - existing->data = realloc(existing->data, - existing->data[OPT_LEN] + length + 2); - memcpy(existing->data + existing->data[OPT_LEN] + 2, buffer, length); - existing->data[OPT_LEN] += length; - } /* else, ignore the data, we could put this in a second option in the future */ - } /* else, ignore the new data */ - } else { - DEBUG(LOG_INFO, "Attaching option %s to list", option->name); - - /* make a new option */ - new = xmalloc(sizeof(struct option_set)); - new->data = xmalloc(length + 2); - new->data[OPT_CODE] = option->code; - new->data[OPT_LEN] = length; - memcpy(new->data + 2, buffer, length); - - curr = opt_list; - while (*curr && (*curr)->data[OPT_CODE] < option->code) - curr = &(*curr)->next; - - new->next = *curr; - *curr = new; - } -} diff --git a/pipe/linux/dhcp/options.h b/pipe/linux/dhcp/options.h deleted file mode 100644 index fbe54c8..0000000 --- a/pipe/linux/dhcp/options.h +++ /dev/null @@ -1,40 +0,0 @@ -/* options.h */ -#ifndef _OPTIONS_H -#define _OPTIONS_H - -#include "packet.h" - -#define TYPE_MASK 0x0F - -enum { - OPTION_IP=1, - OPTION_IP_PAIR, - OPTION_STRING, - OPTION_BOOLEAN, - OPTION_U8, - OPTION_U16, - OPTION_S16, - OPTION_U32, - OPTION_S32 -}; - -#define OPTION_REQ 0x10 /* have the client request this option */ -#define OPTION_LIST 0x20 /* There can be a list of 1 or more of these */ - -struct dhcp_option { - char name[10]; - char flags; - uint8_t code; -}; - -extern struct dhcp_option dhcp_options[]; -extern int option_lengths[]; - -uint8_t *get_option(struct dhcpMessage *packet, int code); -int end_option(uint8_t *optionptr); -int add_option_string(uint8_t *optionptr, uint8_t *string); -int add_simple_option(uint8_t *optionptr, uint8_t code, uint32_t data); -struct option_set *find_option(struct option_set *opt_list, char code); -void attach_option(struct option_set **opt_list, struct dhcp_option *option, char *buffer, int length); - -#endif diff --git a/pipe/linux/dhcp/packet.c b/pipe/linux/dhcp/packet.c deleted file mode 100644 index 7774291..0000000 --- a/pipe/linux/dhcp/packet.c +++ /dev/null @@ -1,211 +0,0 @@ -#include -#include -#include -#include -#include -#include -#if __GLIBC__ >=2 && __GLIBC_MINOR >= 1 -#include -#include -#else -#include -#include -#include -#endif -#include - -#include "packet.h" -#include "dhcpd.h" -#include "options.h" -#include "common.h" - - -void init_header(struct dhcpMessage *packet, char type) -{ - memset(packet, 0, sizeof(struct dhcpMessage)); - switch (type) { - case DHCPDISCOVER: - case DHCPREQUEST: - case DHCPRELEASE: - case DHCPINFORM: - packet->op = BOOTREQUEST; - break; - case DHCPOFFER: - case DHCPACK: - case DHCPNAK: - packet->op = BOOTREPLY; - } - packet->htype = ETH_10MB; - packet->hlen = ETH_10MB_LEN; - packet->cookie = htonl(DHCP_MAGIC); - packet->options[0] = DHCP_END; - add_simple_option(packet->options, DHCP_MESSAGE_TYPE, type); -} - - -/* read a packet from socket fd, return -1 on read error, -2 on packet error */ -int get_packet(struct dhcpMessage *packet, int fd) -{ - int bytes; - int i; - const char broken_vendors[][8] = { - "MSFT 98", - "" - }; - char unsigned *vendor; - - memset(packet, 0, sizeof(struct dhcpMessage)); - bytes = read(fd, packet, sizeof(struct dhcpMessage)); - if (bytes < 0) { - DEBUG(LOG_INFO, "couldn't read on listening socket, ignoring"); - return -1; - } - - if (ntohl(packet->cookie) != DHCP_MAGIC) { - LOG(LOG_ERR, "received bogus message, ignoring"); - return -2; - } - DEBUG(LOG_INFO, "Received a packet"); - - if (packet->op == BOOTREQUEST && (vendor = get_option(packet, DHCP_VENDOR))) { - for (i = 0; broken_vendors[i][0]; i++) { - if (vendor[OPT_LEN - 2] == (uint8_t) strlen(broken_vendors[i]) && - !strncmp(vendor, broken_vendors[i], vendor[OPT_LEN - 2])) { - DEBUG(LOG_INFO, "broken client (%s), forcing broadcast", - broken_vendors[i]); - packet->flags |= htons(BROADCAST_FLAG); - } - } - } - - - return bytes; -} - - -uint16_t checksum(void *addr, int count) -{ - /* Compute Internet Checksum for "count" bytes - * beginning at location "addr". - */ - register int32_t sum = 0; - uint16_t *source = (uint16_t *) addr; - - while (count > 1) { - /* This is the inner loop */ - sum += *source++; - count -= 2; - } - - /* Add left-over byte, if any */ - if (count > 0) { - /* Make sure that the left-over byte is added correctly both - * with little and big endian hosts */ - uint16_t tmp = 0; - *(uint8_t *) (&tmp) = * (uint8_t *) source; - sum += tmp; - } - /* Fold 32-bit sum to 16 bits */ - while (sum >> 16) - sum = (sum & 0xffff) + (sum >> 16); - - return ~sum; -} - - -/* Construct a ip/udp header for a packet, and specify the source and dest hardware address */ -int raw_packet(struct dhcpMessage *payload, uint32_t source_ip, int source_port, - uint32_t dest_ip, int dest_port, uint8_t *dest_arp, int ifindex) -{ - int fd; - int result; - struct sockaddr_ll dest; - struct udp_dhcp_packet packet; - - if ((fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) { - DEBUG(LOG_ERR, "socket call failed: %m"); - return -1; - } - - memset(&dest, 0, sizeof(dest)); - memset(&packet, 0, sizeof(packet)); - - dest.sll_family = AF_PACKET; - dest.sll_protocol = htons(ETH_P_IP); - dest.sll_ifindex = ifindex; - dest.sll_halen = 6; - memcpy(dest.sll_addr, dest_arp, 6); - if (bind(fd, (struct sockaddr *)&dest, sizeof(struct sockaddr_ll)) < 0) { - DEBUG(LOG_ERR, "bind call failed: %m"); - close(fd); - return -1; - } - - packet.ip.protocol = IPPROTO_UDP; - packet.ip.saddr = source_ip; - packet.ip.daddr = dest_ip; - packet.udp.source = htons(source_port); - packet.udp.dest = htons(dest_port); - packet.udp.len = htons(sizeof(packet.udp) + sizeof(struct dhcpMessage)); /* cheat on the psuedo-header */ - packet.ip.tot_len = packet.udp.len; - memcpy(&(packet.data), payload, sizeof(struct dhcpMessage)); - packet.udp.check = checksum(&packet, sizeof(struct udp_dhcp_packet)); - - packet.ip.tot_len = htons(sizeof(struct udp_dhcp_packet)); - packet.ip.ihl = sizeof(packet.ip) >> 2; - packet.ip.version = IPVERSION; - packet.ip.ttl = IPDEFTTL; - packet.ip.check = checksum(&(packet.ip), sizeof(packet.ip)); - - result = sendto(fd, &packet, sizeof(struct udp_dhcp_packet), 0, (struct sockaddr *) &dest, sizeof(dest)); - if (result <= 0) { - DEBUG(LOG_ERR, "write on socket failed: %m"); - } - close(fd); - return result; -} - - -/* Let the kernel do all the work for packet generation */ -int kernel_packet(struct dhcpMessage *payload, uint32_t source_ip, int source_port, - uint32_t dest_ip, int dest_port) -{ - int n = 1; - int fd, result; - struct sockaddr_in client; - - if ((fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) - return -1; - - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &n, sizeof(n)) == -1) - { - close(fd); - return -1; - } - - memset(&client, 0, sizeof(client)); - client.sin_family = AF_INET; - client.sin_port = htons(source_port); - client.sin_addr.s_addr = source_ip; - - if (bind(fd, (struct sockaddr *)&client, sizeof(struct sockaddr)) == -1) - { - close(fd); - return -1; - } - - memset(&client, 0, sizeof(client)); - client.sin_family = AF_INET; - client.sin_port = htons(dest_port); - client.sin_addr.s_addr = dest_ip; - - if (connect(fd, (struct sockaddr *)&client, sizeof(struct sockaddr)) == -1) - { - close(fd); - return -1; - } - - result = write(fd, payload, sizeof(struct dhcpMessage)); - close(fd); - return result; -} diff --git a/pipe/linux/dhcp/packet.h b/pipe/linux/dhcp/packet.h deleted file mode 100644 index f5859e8..0000000 --- a/pipe/linux/dhcp/packet.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef _PACKET_H -#define _PACKET_H - -#include -#include - -struct dhcpMessage { - uint8_t op; - uint8_t htype; - uint8_t hlen; - uint8_t hops; - uint32_t xid; - uint16_t secs; - uint16_t flags; - uint32_t ciaddr; - uint32_t yiaddr; - uint32_t siaddr; - uint32_t giaddr; - uint8_t chaddr[16]; - uint8_t sname[64]; - uint8_t file[128]; - uint32_t cookie; - uint8_t options[308]; /* 312 - cookie */ -}; - -struct udp_dhcp_packet { - struct iphdr ip; - struct udphdr udp; - struct dhcpMessage data; -}; - -void init_header(struct dhcpMessage *packet, char type); -int get_packet(struct dhcpMessage *packet, int fd); -uint16_t checksum(void *addr, int count); -int raw_packet(struct dhcpMessage *payload, uint32_t source_ip, int source_port, - uint32_t dest_ip, int dest_port, uint8_t *dest_arp, int ifindex); -int kernel_packet(struct dhcpMessage *payload, uint32_t source_ip, int source_port, - uint32_t dest_ip, int dest_port); - - -#endif diff --git a/pipe/linux/dhcp/pidfile.c b/pipe/linux/dhcp/pidfile.c deleted file mode 100644 index 2e0c753..0000000 --- a/pipe/linux/dhcp/pidfile.c +++ /dev/null @@ -1,75 +0,0 @@ -/* pidfile.c - * - * Functions to assist in the writing and removing of pidfiles. - * - * Russ Dill September 2001 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include -#include -#include -#include -#include -#include - -#include "pidfile.h" -#include "common.h" - -static char *saved_pidfile; - -static void pidfile_delete(void) -{ - if (saved_pidfile) unlink(saved_pidfile); -} - - -int pidfile_acquire(const char *pidfile) -{ - int pid_fd; - if (!pidfile) return -1; - - pid_fd = open(pidfile, O_CREAT | O_WRONLY, 0644); - if (pid_fd < 0) { - LOG(LOG_ERR, "Unable to open pidfile %s: %m\n", pidfile); - } else { - lockf(pid_fd, F_LOCK, 0); - if (!saved_pidfile) - atexit(pidfile_delete); - saved_pidfile = (char *) pidfile; - } - - return pid_fd; -} - - -void pidfile_write_release(int pid_fd) -{ - FILE *out; - - if (pid_fd < 0) return; - - if ((out = fdopen(pid_fd, "w")) != NULL) { - fprintf(out, "%d\n", getpid()); - fclose(out); - } - lockf(pid_fd, F_UNLCK, 0); - close(pid_fd); -} - - - - diff --git a/pipe/linux/dhcp/pidfile.h b/pipe/linux/dhcp/pidfile.h deleted file mode 100644 index ea97d1d..0000000 --- a/pipe/linux/dhcp/pidfile.h +++ /dev/null @@ -1,25 +0,0 @@ -/* pidfile.h - * - * Functions to assist in the writing and removing of pidfiles. - * - * Russ Dill September 2001 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - - -int pidfile_acquire(const char *pidfile); -void pidfile_write_release(int pid_fd); - diff --git a/pipe/linux/dhcp/script.c b/pipe/linux/dhcp/script.c deleted file mode 100644 index 590cd10..0000000 --- a/pipe/linux/dhcp/script.c +++ /dev/null @@ -1,242 +0,0 @@ -/* script.c - * - * Functions to call the DHCP client notification scripts - * - * Russ Dill July 2001 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "options.h" -#include "dhcpd.h" -#include "dhcpc.h" -#include "common.h" - -/* get a rough idea of how long an option will be (rounding up...) */ -static const int max_option_length[] = { - [OPTION_IP] = sizeof("255.255.255.255 "), - [OPTION_IP_PAIR] = sizeof("255.255.255.255 ") * 2, - [OPTION_STRING] = 1, - [OPTION_BOOLEAN] = sizeof("yes "), - [OPTION_U8] = sizeof("255 "), - [OPTION_U16] = sizeof("65535 "), - [OPTION_S16] = sizeof("-32768 "), - [OPTION_U32] = sizeof("4294967295 "), - [OPTION_S32] = sizeof("-2147483684 "), -}; - - -static inline int upper_length(int length, int opt_index) -{ - return max_option_length[opt_index] * - (length / option_lengths[opt_index]); -} - - -static int sprintip(char *dest, char *pre, uint8_t *ip) -{ - return sprintf(dest, "%s%d.%d.%d.%d", pre, ip[0], ip[1], ip[2], ip[3]); -} - - -/* really simple implementation, just count the bits */ -static int mton(struct in_addr *mask) -{ - int i; - unsigned long bits = ntohl(mask->s_addr); - /* too bad one can't check the carry bit, etc in c bit - * shifting */ - for (i = 0; i < 32 && !((bits >> i) & 1); i++); - return 32 - i; -} - - -/* Fill dest with the text of option 'option'. */ -static void fill_options(char *dest, uint8_t *option, struct dhcp_option *type_p) -{ - int type, optlen; - uint16_t val_u16; - int16_t val_s16; - uint32_t val_u32; - int32_t val_s32; - int len = option[OPT_LEN - 2]; - - dest += sprintf(dest, "%s=", type_p->name); - - type = type_p->flags & TYPE_MASK; - optlen = option_lengths[type]; - for(;;) { - switch (type) { - case OPTION_IP_PAIR: - dest += sprintip(dest, "", option); - *(dest++) = '/'; - option += 4; - optlen = 4; - case OPTION_IP: /* Works regardless of host byte order. */ - dest += sprintip(dest, "", option); - break; - case OPTION_BOOLEAN: - dest += sprintf(dest, *option ? "yes" : "no"); - break; - case OPTION_U8: - dest += sprintf(dest, "%u", *option); - break; - case OPTION_U16: - memcpy(&val_u16, option, 2); - dest += sprintf(dest, "%u", ntohs(val_u16)); - break; - case OPTION_S16: - memcpy(&val_s16, option, 2); - dest += sprintf(dest, "%d", ntohs(val_s16)); - break; - case OPTION_U32: - memcpy(&val_u32, option, 4); - dest += sprintf(dest, "%lu", (unsigned long) ntohl(val_u32)); - break; - case OPTION_S32: - memcpy(&val_s32, option, 4); - dest += sprintf(dest, "%ld", (long) ntohl(val_s32)); - break; - case OPTION_STRING: - memcpy(dest, option, len); - dest[len] = '\0'; - return; /* Short circuit this case */ - } - option += optlen; - len -= optlen; - if (len <= 0) break; - dest += sprintf(dest, " "); - } -} - - -/* put all the parameters into an environment */ -static char **fill_envp(struct dhcpMessage *packet) -{ - int num_options = 0; - int i, j; - char **envp; - uint8_t *temp; - struct in_addr subnet; - char over = 0; - - if (packet == NULL) - num_options = 0; - else { - for (i = 0; dhcp_options[i].code; i++) - if (get_option(packet, dhcp_options[i].code)) { - num_options++; - if (dhcp_options[i].code == DHCP_SUBNET) - num_options++; /* for mton */ - } - if (packet->siaddr) num_options++; - if ((temp = get_option(packet, DHCP_OPTION_OVER))) - over = *temp; - if (!(over & FILE_FIELD) && packet->file[0]) num_options++; - if (!(over & SNAME_FIELD) && packet->sname[0]) num_options++; - } - - envp = xcalloc(sizeof(char *), num_options + 5); - j = 0; - asprintf(&envp[j++], "interface=%s", client_config.interface); - asprintf(&envp[j++], "%s=%s", "PATH", - getenv("PATH") ? : "/bin:/usr/bin:/sbin:/usr/sbin"); - asprintf(&envp[j++], "%s=%s", "HOME", getenv("HOME") ? : "/"); - - if (packet == NULL) return envp; - - envp[j] = xmalloc(sizeof("ip=255.255.255.255")); - sprintip(envp[j++], "ip=", (uint8_t *) &packet->yiaddr); - - - for (i = 0; dhcp_options[i].code; i++) { - if (!(temp = get_option(packet, dhcp_options[i].code))) - continue; - envp[j] = xmalloc(upper_length(temp[OPT_LEN - 2], - dhcp_options[i].flags & TYPE_MASK) + strlen(dhcp_options[i].name) + 2); - fill_options(envp[j++], temp, &dhcp_options[i]); - - /* Fill in a subnet bits option for things like /24 */ - if (dhcp_options[i].code == DHCP_SUBNET) { - memcpy(&subnet, temp, 4); - asprintf(&envp[j++], "mask=%d", mton(&subnet)); - } - } - if (packet->siaddr) { - envp[j] = xmalloc(sizeof("siaddr=255.255.255.255")); - sprintip(envp[j++], "siaddr=", (uint8_t *) &packet->siaddr); - } - if (!(over & FILE_FIELD) && packet->file[0]) { - /* watch out for invalid packets */ - packet->file[sizeof(packet->file) - 1] = '\0'; - asprintf(&envp[j++], "boot_file=%s", packet->file); - } - if (!(over & SNAME_FIELD) && packet->sname[0]) { - /* watch out for invalid packets */ - packet->sname[sizeof(packet->sname) - 1] = '\0'; - asprintf(&envp[j++], "sname=%s", packet->sname); - } - return envp; -} - - -/* Call a script with a par file and env vars */ -void run_script(struct dhcpMessage *packet, const char *name) -{ - int pid; - char **envp, **curr; - - if (client_config.callback == NULL) - return; - - DEBUG(LOG_INFO, "callbacking'ing %p", client_config.callback); - - envp = fill_envp(packet); - /* call script */ - // pid = vfork(); - // if (pid) { - // waitpid(pid, NULL, 0); - // for (curr = envp; *curr; curr++) free(*curr); - // free(envp); - // return; - // } else if (pid == 0) { - // /* close fd's? */ - - // /* exec script */ - // execle(client_config.script, client_config.script, - // name, NULL, envp); - // LOG(LOG_ERR, "script %s failed: %m", client_config.script); - // exit(1); - // } - client_config.callback(name, envp, client_config.callback_data); - - // Free strings - char **e = envp; - while (*e) { - free(*e); - e++; - } - free(envp); -} diff --git a/pipe/linux/dhcp/script.h b/pipe/linux/dhcp/script.h deleted file mode 100644 index 87a20cc..0000000 --- a/pipe/linux/dhcp/script.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef _SCRIPT_H -#define _SCRIPT_H - -void run_script(struct dhcpMessage *packet, const char *name); - -#endif diff --git a/pipe/linux/dhcp/signalpipe.c b/pipe/linux/dhcp/signalpipe.c deleted file mode 100644 index 074c8a6..0000000 --- a/pipe/linux/dhcp/signalpipe.c +++ /dev/null @@ -1,78 +0,0 @@ -/* signalpipe.c - * - * Signal pipe infrastructure. A reliable way of delivering signals. - * - * Russ Dill December 2003 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include -#include -#include -#include -#include - - -#include "signalpipe.h" -#include "common.h" - -static int signal_pipe[2]; - -static void signal_handler(int sig) -{ - if (send(signal_pipe[1], &sig, sizeof(sig), MSG_DONTWAIT) < 0) - DEBUG(LOG_ERR, "Could not send signal: %m"); -} - - -/* Call this before doing anything else. Sets up the socket pair - * and installs the signal handler */ -void udhcp_sp_setup(void) -{ - socketpair(AF_UNIX, SOCK_STREAM, 0, signal_pipe); - signal(SIGUSR1, signal_handler); - signal(SIGUSR2, signal_handler); - signal(SIGTERM, signal_handler); -} - - -/* Quick little function to setup the rfds. Will return the - * max_fd for use with select. Limited in that you can only pass - * one extra fd */ -int udhcp_sp_fd_set(fd_set *rfds, int extra_fd) -{ - FD_ZERO(rfds); - FD_SET(signal_pipe[0], rfds); - if (extra_fd >= 0) FD_SET(extra_fd, rfds); - return signal_pipe[0] > extra_fd ? signal_pipe[0] : extra_fd; -} - - -/* Read a signal from the signal pipe. Returns 0 if there is - * no signal, -1 on error (and sets errno appropriately), and - * your signal on success */ -int udhcp_sp_read(fd_set *rfds) -{ - int sig; - - if (!FD_ISSET(signal_pipe[0], rfds)) - return 0; - - if (read(signal_pipe[0], &sig, sizeof(sig)) < 0) - return -1; - - return sig; -} diff --git a/pipe/linux/dhcp/signalpipe.h b/pipe/linux/dhcp/signalpipe.h deleted file mode 100644 index 70c1e57..0000000 --- a/pipe/linux/dhcp/signalpipe.h +++ /dev/null @@ -1,22 +0,0 @@ -/* signalpipe.h - * - * Russ Dill December 2003 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -void udhcp_sp_setup(void); -int udhcp_sp_fd_set(fd_set *rfds, int extra_fd); -int udhcp_sp_read(fd_set *rfds); diff --git a/pipe/linux/dhcp/socket.c b/pipe/linux/dhcp/socket.c deleted file mode 100644 index 7529250..0000000 --- a/pipe/linux/dhcp/socket.c +++ /dev/null @@ -1,137 +0,0 @@ -/* - * socket.c -- DHCP server client/server socket creation - * - * udhcp client/server - * Copyright (C) 1999 Matthew Ramsay - * Chris Trew - * - * Rewrite by Russ Dill July 2001 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if __GLIBC__ >=2 && __GLIBC_MINOR >= 1 -#include -#include -#else -#include -#include -#include -#endif - -#include "socket.h" -#include "common.h" - -int read_interface(char *interface, int *ifindex, uint32_t *addr, uint8_t *arp) -{ - int fd; - struct ifreq ifr; - struct sockaddr_in *our_ip; - - memset(&ifr, 0, sizeof(struct ifreq)); - if((fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) >= 0) { - ifr.ifr_addr.sa_family = AF_INET; - //closes http://bugs.debian.org/283582 - //strcpy(ifr.ifr_name, interface); - strncpy(ifr.ifr_name, interface, IFNAMSIZ-1); - - if (addr) { - if (ioctl(fd, SIOCGIFADDR, &ifr) == 0) { - our_ip = (struct sockaddr_in *) &ifr.ifr_addr; - *addr = our_ip->sin_addr.s_addr; - DEBUG(LOG_INFO, "%s (our ip) = %s", ifr.ifr_name, inet_ntoa(our_ip->sin_addr)); - } else { - LOG(LOG_ERR, "SIOCGIFADDR failed, is the interface up and configured?: %m"); - close(fd); - return -1; - } - } - - if (ioctl(fd, SIOCGIFINDEX, &ifr) == 0) { - DEBUG(LOG_INFO, "adapter index %d", ifr.ifr_ifindex); - *ifindex = ifr.ifr_ifindex; - } else { - LOG(LOG_ERR, "SIOCGIFINDEX failed!: %m"); - close(fd); - return -1; - } - if (ioctl(fd, SIOCGIFHWADDR, &ifr) == 0) { - memcpy(arp, ifr.ifr_hwaddr.sa_data, 6); - DEBUG(LOG_INFO, "adapter hardware address %02x:%02x:%02x:%02x:%02x:%02x", - arp[0], arp[1], arp[2], arp[3], arp[4], arp[5]); - } else { - LOG(LOG_ERR, "SIOCGIFHWADDR failed!: %m"); - close(fd); - return -1; - } - } else { - LOG(LOG_ERR, "socket failed!: %m"); - return -1; - } - close(fd); - return 0; -} - - -int listen_socket(uint32_t ip, int port, char *inf) -{ - struct ifreq interface; - int fd; - struct sockaddr_in addr; - int n = 1; - - DEBUG(LOG_INFO, "Opening listen socket on 0x%08x:%d %s", ip, port, inf); - if ((fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { - DEBUG(LOG_ERR, "socket call failed: %m"); - return -1; - } - - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - addr.sin_addr.s_addr = ip; - - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &n, sizeof(n)) == -1) { - close(fd); - return -1; - } - if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char *) &n, sizeof(n)) == -1) { - close(fd); - return -1; - } - - strncpy(interface.ifr_ifrn.ifrn_name, inf, IFNAMSIZ); - if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,(char *)&interface, sizeof(interface)) < 0) { - close(fd); - return -1; - } - - if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) == -1) { - close(fd); - return -1; - } - - return fd; -} diff --git a/pipe/linux/dhcp/socket.h b/pipe/linux/dhcp/socket.h deleted file mode 100644 index 66179d4..0000000 --- a/pipe/linux/dhcp/socket.h +++ /dev/null @@ -1,8 +0,0 @@ -/* socket.h */ -#ifndef _SOCKET_H -#define _SOCKET_H - -int read_interface(char *interface, int *ifindex, uint32_t *addr, uint8_t *arp); -int listen_socket(uint32_t ip, int port, char *inf); - -#endif diff --git a/pipe/linux/dhcp/version.h b/pipe/linux/dhcp/version.h deleted file mode 100644 index 3862539..0000000 --- a/pipe/linux/dhcp/version.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef _UDHCP_VERSION_H -#define _UDHCP_VERSION_H - -#define VERSION "0.9.9-pre" - -#endif diff --git a/pipe/linux/wpa.c b/pipe/linux/wpa.c index 367d1e8..a08b44b 100644 --- a/pipe/linux/wpa.c +++ b/pipe/linux/wpa.c @@ -1,8 +1,13 @@ #include "wpa.h" +#ifdef ANDROID +#include "android.h" +#endif + #include #include #include +#include #include #include #include @@ -19,14 +24,13 @@ #include #include #include -#include #include #include #include #include "../def.h" #include "../ports.h" -#include "dhcp/dhcpc.h" +#include "dhcp/dhcp.h" #include "util.h" #include "vanilla.h" #include "wpa.h" @@ -40,8 +44,6 @@ #include #include -static const char *wpa_ctrl_interface = "/var/run/wpa_supplicant_drc"; - static pthread_mutex_t running_mutex; static pthread_mutex_t main_loop_mutex; static pthread_mutex_t action_mutex; @@ -50,16 +52,10 @@ static int running = 0; static int main_loop = 0; static int relay_running = 0; -typedef union { - struct sockaddr_in in; - struct sockaddr_un un; -} sockaddr_u; - typedef struct { int from_socket; int to_socket; - sockaddr_u to_address; - size_t to_address_size; + struct sockaddr_in to_address; } relay_ports; struct sync_args { @@ -70,22 +66,97 @@ struct sync_args { unsigned char psk[32]; void *(*start_routine)(void *); struct wpa_ctrl *ctrl; + struct wpa_ctrl *monitor; int local; int skt; - sockaddr_u client; - size_t client_size; + struct sockaddr_in client; }; struct relay_info { const char *wireless_interface; - int local; - sockaddr_u client; - size_t client_size; + struct sockaddr_in client; in_port_t port; }; #define THREADRESULT(x) ((void *) (uintptr_t) (x)) +static void cleanup_runtime_directory(); + +static const char *get_wpa_runtime_directory() +{ +#ifdef ANDROID + static char runtime_directory[PATH_MAX]; + static int runtime_directory_init = 0; + + if (!runtime_directory_init) { + umask(0077); + + if (snprintf(runtime_directory, sizeof(runtime_directory), "/data/local/tmp/vanilla-pipe-XXXXXX") >= sizeof(runtime_directory) + || !mkdtemp(runtime_directory)) { + nlprint("Failed to create Android runtime directory: %i", errno); + return NULL; + } + + runtime_directory_init = 1; + } + + return runtime_directory; +#else + return "/tmp"; +#endif +} + +static const char *get_wpa_ctrl_interface() +{ +#ifdef ANDROID + static char wpa_ctrl_interface[PATH_MAX]; + static int wpa_ctrl_interface_init = 0; + + if (!wpa_ctrl_interface_init) { + umask(0077); + + if (snprintf(wpa_ctrl_interface, sizeof(wpa_ctrl_interface), "%s/wpa_supplicant_drc", get_wpa_runtime_directory()) >= sizeof(wpa_ctrl_interface)) { + nlprint("Android runtime path is too long"); + return NULL; + } + + if (mkdir(wpa_ctrl_interface, 0700) < 0) { + nlprint("Failed to create %s: %i", wpa_ctrl_interface, errno); + return NULL; + } + + if (atexit(cleanup_runtime_directory) != 0) { + nlprint("Failed to register Android runtime cleanup"); + return NULL; + } + + wpa_ctrl_interface_init = 1; + } + + return wpa_ctrl_interface; +#else + return "/var/run/wpa_supplicant_drc"; +#endif +} + +static void cleanup_runtime_directory() +{ + char path[PATH_MAX]; + int length; + + length = snprintf(path, sizeof(path), "%s/vanilla_wpa_key.conf", + get_wpa_runtime_directory()); + if (length >= 0 && length < (int) sizeof(path)) + unlink(path); + length = snprintf(path, sizeof(path), "%s/vanilla_wpa_connect.conf", + get_wpa_runtime_directory()); + if (length >= 0 && length < (int) sizeof(path)) + unlink(path); + + rmdir(get_wpa_ctrl_interface()); + rmdir(get_wpa_runtime_directory()); +} + static const char *ext_logfile = 0; void nlprint(const char *fmt, ...) { @@ -133,9 +204,10 @@ void vanilla_pipe_wpa_msg(char *msg, size_t len) nlprint("%.*s", len, msg); } -void wpa_ctrl_command(struct wpa_ctrl *ctrl, const char *cmd, char *buf, size_t *buf_len) +int wpa_ctrl_command(struct wpa_ctrl *ctrl, const char *cmd, char *buf, + size_t *buf_len) { - wpa_ctrl_request(ctrl, cmd, strlen(cmd), buf, buf_len, NULL /*vanilla_pipe_wpa_msg*/); + return wpa_ctrl_request(ctrl, cmd, strlen(cmd), buf, buf_len, NULL /* vanilla_pipe_wpa_msg */); } void quit_loop() @@ -201,12 +273,15 @@ void *read_stdin(void *arg) } read_size = 0; - } else if (c == EOF) { - break; } else { line[read_size] = c; read_size = (read_size + 1) % sizeof(line); } + } else if (s == 0) { + // Frontend closed its end of the pipe, shut down + quit_loop(); + pthread_mutex_lock(&main_loop_mutex); + break; } else if (s < 0) { perror("read()"); } @@ -232,7 +307,10 @@ ssize_t run_process_and_read_stdout(const char **args, char *read_buffer, size_t { // Create pipe so we can read from the forked child int pipefd[2]; - pipe(pipefd); + if (pipe(pipefd) < 0) { + nlprint("Failed to create subprocess pipe: %i", errno); + return -1; + } // Perform fork pid_t pid = fork(); @@ -242,6 +320,8 @@ ssize_t run_process_and_read_stdout(const char **args, char *read_buffer, size_t case -1: // Fork failed for some reason, report the errno nlprint("Failed to fork to run process: %i", errno); + close(pipefd[0]); + close(pipefd[1]); return -1; case 0: // We are the child process, go ahead and run exec @@ -255,23 +335,33 @@ ssize_t run_process_and_read_stdout(const char **args, char *read_buffer, size_t execvp(args[0], (char * const *) args); // Exit immediately if for some reason execvp failed - _exit(0); + _exit(127); default: // We are the parent process. Read from stdout until EOF. close(pipefd[1]); // Close write, don't need it - ssize_t read_len = 0; - char *read_buffer_now = read_buffer; - char * const read_buffer_end = read_buffer + read_buffer_len; - if (read_buffer && read_buffer_len) { - while (read_buffer_now != read_buffer_end) { - read_len = read(pipefd[0], read_buffer_now, read_buffer_end - read_buffer_now); - if (read_len <= 0) { - break; - } - read_buffer_now += read_len; + size_t stored_len = 0; + while (1) { + char discard_buffer[512]; + char *destination = discard_buffer; + size_t destination_len = sizeof(discard_buffer); + + if (read_buffer && stored_len < read_buffer_len) { + destination = read_buffer + stored_len; + destination_len = read_buffer_len - stored_len; } + + ssize_t read_len = read(pipefd[0], destination, destination_len); + if (read_len < 0 && errno == EINTR) { + continue; + } + if (read_len <= 0) { + break; + } + if (destination != discard_buffer) { + stored_len += read_len; + } } int status; @@ -283,7 +373,7 @@ ssize_t run_process_and_read_stdout(const char **args, char *read_buffer, size_t close(pipefd[0]); // Close read, we're done reading if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { - return read_buffer_now - read_buffer; + return stored_len; } else { nlprint("Subprocess failed with status: 0x%x", status); return -1; @@ -322,28 +412,61 @@ void *wpa_setup_environment(void *data) goto die; } + pthread_t wpa_thread; + int wpa_thread_started = 0; + struct wpa_supplicant *wpa_s = wpa_supplicant_add_iface(wpa, &interface, NULL); if (!wpa_s) { nlprint("FAILED TO ADD WPA IFACE"); goto die_and_kill; } - pthread_t wpa_thread; - pthread_create(&wpa_thread, NULL, start_wpa, wpa); + if (pthread_create(&wpa_thread, NULL, start_wpa, wpa) != 0) { + nlprint("FAILED TO START WPA SUPPLICANT THREAD"); + goto die_and_kill; + } + wpa_thread_started = 1; // Get control interface - char buf[128]; - snprintf(buf, sizeof(buf), "%s/%s", wpa_ctrl_interface, args->wireless_interface); + char buf[PATH_MAX]; + snprintf(buf, sizeof(buf), "%s/%s", get_wpa_ctrl_interface(), + args->wireless_interface); struct wpa_ctrl *ctrl; - while (!(ctrl = wpa_ctrl_open(buf))) { + while (1) { +#ifdef ANDROID + // wpa_ctrl_open creates a client socket in /tmp by default which doesn't + // exist on Android, so ensure it uses our runtime dir + ctrl = wpa_ctrl_open2(buf, get_wpa_runtime_directory()); +#else + ctrl = wpa_ctrl_open(buf); +#endif + if (ctrl) + break; + if (is_interrupted()) goto die_and_kill; - nlprint("WAITING FOR CTRL INTERFACE"); + nlprint("WAITING FOR CTRL INTERFACE: %i", errno); sleep(1); } - if (is_interrupted() || wpa_ctrl_attach(ctrl) < 0) { + struct wpa_ctrl *monitor; + while (1) { +#ifdef ANDROID + monitor = wpa_ctrl_open2(buf, get_wpa_runtime_directory()); +#else + monitor = wpa_ctrl_open(buf); +#endif + if (monitor) + break; + + if (is_interrupted()) + goto die_and_close_ctrl; + nlprint("WAITING FOR WPA MONITOR INTERFACE: %i", errno); + sleep(1); + } + + if (is_interrupted() || wpa_ctrl_attach(monitor) < 0) { nlprint("FAILED TO ATTACH TO WPA"); - goto die_and_close; + goto die_and_close_monitor; } // wpa_supplicant_run may have replaced our signals, so lets re-set them @@ -352,127 +475,122 @@ void *wpa_setup_environment(void *data) set_signals(); args->ctrl = ctrl; + args->monitor = monitor; ret = args->start_routine(args); die_and_detach: - wpa_ctrl_detach(ctrl); + wpa_ctrl_detach(monitor); -die_and_close: +die_and_close_monitor: + wpa_ctrl_close(monitor); + +die_and_close_ctrl: wpa_ctrl_close(ctrl); die_and_kill: - pthread_cancel(wpa_thread); - pthread_join(wpa_thread, NULL); + if (wpa_thread_started) { + wpa_supplicant_terminate_proc(wpa); + pthread_join(wpa_thread, NULL); + } wpa_supplicant_deinit(wpa); die: return ret; } -const char *get_dhcp_value(char **env, const char *key) +static int netmask_to_prefixlen(struct in_addr netmask) { - char buf[100]; - int len = snprintf(buf, sizeof(buf), "%s=", key); + uint32_t mask = ntohl(netmask.s_addr); + int prefixlen = 0; + int found_zero = 0; - while (*env) { - char *e = *env; - if (!memcmp(e, buf, len)) { - return e + len; + for (int bit = 31; bit >= 0; bit--) { + if (mask & (1u << bit)) { + if (found_zero) { + return -1; + } + prefixlen++; + } else { + found_zero = 1; } - env++; } - return 0; + return prefixlen; } -void dhcp_callback(const char *type, char **env, void *data) -{ - struct nl_sock *nl = (struct nl_sock *) data; - - if (!strcmp(type, "bound")) { - // Add address to interface - const char *ip = get_dhcp_value(env, "ip"); - // const char *subnet = get_dhcp_value(env, "subnet"); - // const char *router = get_dhcp_value(env, "router"); - // const char *serverid = get_dhcp_value(env, "serverid"); - const char *mask = get_dhcp_value(env, "mask"); - - // Create IP address object from DHCP data - struct nl_addr *ip_addr; - nl_addr_parse(ip, AF_INET, &ip_addr); - nl_addr_set_prefixlen(ip_addr, atoi(mask)); - - // Create route object - struct rtnl_addr *ra = rtnl_addr_alloc(); - rtnl_addr_set_ifindex(ra, if_nametoindex(get_dhcp_value(env, "interface"))); - rtnl_addr_set_local(ra, ip_addr); - - // Create build request - struct nl_msg *msg; - rtnl_addr_build_add_request(ra, 0, &msg); - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,18,00) - // Make this route the lowest possible priority so the system doesn't favor it over other connections - nla_put_u32(msg, IFA_RT_PRIORITY, UINT32_MAX); -#endif - - // Send request - nl_send_auto_complete(nl, msg); - - // Cleanup - nlmsg_free(msg); - rtnl_addr_put(ra); - nl_addr_put(ip_addr); - } else if (!strcmp(type, "deconfig")) { - // Remove address from interface - struct rtnl_addr *ra = rtnl_addr_alloc(); - rtnl_addr_set_ifindex(ra, if_nametoindex(get_dhcp_value(env, "interface"))); - rtnl_addr_set_family(ra, AF_INET); - rtnl_addr_delete(nl, ra, 0); - rtnl_addr_put(ra); - } - // nlprint("GOT DHCP EVENT: %s", type); - // while (*env) { - // char *e = *env; - // nlprint(" %s", e); - // env++; - // } -} - -int call_dhcp(const char *network_interface) +static int call_dhcp(const char *network_interface, struct nl_sock *nl) { int ret = VANILLA_ERR_GENERIC; - struct nl_sock *nl = nl_socket_alloc(); - if (!nl) { - nlprint("FAILED TO ALLOC NL_SOCK"); - goto exit; + if (!nl) + return ret; + + sdhcp_lease_t lease; + if (sdhcp_acquire(network_interface, 5000, 3, &lease) < 0) { + nlprint("DHCP ACQUISITION FAILED ON %s: %i", network_interface, errno); + return ret; } - int nlr = nl_connect(nl, NETLINK_ROUTE); - if (nlr < 0) { - nlprint("FAILED TO CONNECT NL: %i", nlr); - goto free_socket_and_exit; + int ifindex = if_nametoindex(network_interface); + if (ifindex == 0) + return ret; + + int prefixlen = netmask_to_prefixlen(lease.subnet_mask); + if (prefixlen < 0) + return ret; + + struct nl_addr *local = + nl_addr_build(AF_INET, &lease.yiaddr, sizeof(lease.yiaddr)); + if (!local) + return ret; + nl_addr_set_prefixlen(local, prefixlen); + + struct rtnl_addr *addr = rtnl_addr_alloc(); + if (!addr) { + nl_addr_put(local); + return ret; } - client_config.foreground = 1; - client_config.quit_after_lease = 1; - client_config.interface = (char *) network_interface; - client_config.callback = dhcp_callback; - client_config.callback_data = nl; - client_config.abort_if_no_lease = 1; + rtnl_addr_set_ifindex(addr, ifindex); + rtnl_addr_set_local(addr, local); - if (udhcpc_main() == 0) { - ret = VANILLA_SUCCESS; + struct nl_msg *msg = NULL; + if (rtnl_addr_build_add_request( + addr, NLM_F_CREATE | NLM_F_REPLACE, &msg) < 0) { + rtnl_addr_put(addr); + nl_addr_put(local); + return ret; } -close_socket_and_exit: - nl_close(nl); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,18,0) + // Make this route the lowest possible priority so the system doesn't favor it over other connections + nla_put_u32(msg, IFA_RT_PRIORITY, UINT32_MAX); +#endif -free_socket_and_exit: - nl_socket_free(nl); + int address_result = nl_send_auto_complete(nl, msg); + if (address_result >= 0) + address_result = nl_wait_for_ack(nl); -exit: - return ret; + nlmsg_free(msg); + rtnl_addr_put(addr); + if (address_result < 0) { + nlprint("FAILED TO CONFIGURE DHCP ADDRESS: %s", + nl_geterror(address_result)); + nl_addr_put(local); + return ret; + } + +#ifdef ANDROID + // Android prevents user apps from calling SO_BINDTODEVICE, so while we're + // root, we'll create a route that the user-side can use to effectively + // reach the Wii U + if (vanilla_android_install_console_routing(nl, ifindex, local) < 0) { + nl_addr_put(local); + return ret; + } +#endif + + nl_addr_put(local); + return VANILLA_SUCCESS; } int nl80211_set_power_save(struct nl_sock *nl, int nl80211_id, int ifindex, int ps_state) @@ -547,64 +665,44 @@ void *do_relay(void *data) continue; } - if (sendto(ports->to_socket, buf, read_size, 0, (const struct sockaddr *) &ports->to_address, ports->to_address_size) == -1) { - if (ports->to_address_size == sizeof(struct sockaddr_un)) { - nlprint("FAILED TO SENDTO \"%s\" (%i)", ports->to_address.un.sun_path, errno); - } else if (ports->to_address_size == sizeof(struct sockaddr_in)) { - char ip[20]; - inet_ntop(AF_INET, &ports->to_address.in.sin_addr, ip, sizeof(ip)); - nlprint("FAILED TO SENDTO %s:%u (%i)", ip, ports->to_address.in.sin_port, errno); - } else { - nlprint("FAILED TO SENDTO - INVALID SIZE: %zu", ports->to_address_size); - } + if (sendto(ports->to_socket, buf, read_size, 0, (const struct sockaddr *) &ports->to_address, sizeof(ports->to_address)) == -1) { + char ip[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &ports->to_address.sin_addr, ip, sizeof(ip)); + nlprint("FAILED TO SENDTO %s:%u (%i)", ip, ntohs(ports->to_address.sin_port), errno); } } return NULL; } -relay_ports create_ports(int from_socket, int to_socket, const sockaddr_u *to_addr, size_t to_addr_size) +relay_ports create_ports(int from_socket, int to_socket, + const struct sockaddr_in *to_addr) { relay_ports ports; ports.from_socket = from_socket; ports.to_socket = to_socket; ports.to_address = *to_addr; - ports.to_address_size = to_addr_size; return ports; } int open_socket(int local, in_port_t port) { - sockaddr_u sa; - size_t sa_size; - - int skt; - if (local) { - skt = socket(AF_UNIX, SOCK_DGRAM, 0); - sa.un.sun_family = AF_UNIX; - snprintf(sa.un.sun_path, sizeof(sa.un.sun_path) - 1, VANILLA_PIPE_LOCAL_SOCKET, port); - unlink(sa.un.sun_path); - sa_size = sizeof(struct sockaddr_un); - } else { - skt = socket(AF_INET, SOCK_DGRAM, 0); - sa.in.sin_family = AF_INET; - sa.in.sin_addr.s_addr = INADDR_ANY; - sa.in.sin_port = htons(port); - sa_size = sizeof(struct sockaddr_in); - } + int skt = socket(AF_INET, SOCK_DGRAM, 0); if (skt == -1) { return -1; } + struct sockaddr_in sa = {0}; + sa.sin_family = AF_INET; + sa.sin_addr.s_addr = local ? htonl(INADDR_LOOPBACK) : INADDR_ANY; + sa.sin_port = htons(port); struct timeval tv = {0}; tv.tv_usec = 250000; setsockopt(skt, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); - if (bind(skt, (const struct sockaddr *) &sa, sa_size) == -1) { + if (bind(skt, (const struct sockaddr *) &sa, sizeof(sa)) == -1) { nlprint("FAILED TO BIND PORT %u: %i", port, errno); close(skt); return -1; - } else { - // nlprint("BOUND PORT %u", port); } return skt; @@ -625,7 +723,7 @@ void *open_relay(void *data) setsockopt(from_console, SOL_SOCKET, SO_BINDTODEVICE, info->wireless_interface, strlen(info->wireless_interface)); // Open an incoming port from the frontend - int from_frontend = open_socket(info->local, port - 100); + int from_frontend = open_socket(0, port - 100); if (from_frontend == -1) { goto close_console_connection; } @@ -635,26 +733,16 @@ void *open_relay(void *data) console_addr.sin_addr.s_addr = inet_addr("192.168.1.10"); console_addr.sin_port = htons(port - 100); - sockaddr_u frontend_addr; - size_t frontend_addr_size; - if (info->local) { - memset(&frontend_addr.un, 0, sizeof(frontend_addr.un)); - frontend_addr.un.sun_family = AF_UNIX; - snprintf(frontend_addr.un.sun_path, sizeof(frontend_addr.un.sun_path), VANILLA_PIPE_LOCAL_SOCKET, port); - frontend_addr_size = sizeof(frontend_addr.un); - } else { - memset(&frontend_addr.in, 0, sizeof(frontend_addr.in)); - frontend_addr.in.sin_family = AF_INET; - frontend_addr.in.sin_addr = info->client.in.sin_addr; - frontend_addr.in.sin_port = htons(port); - frontend_addr_size = sizeof(frontend_addr.in); - } + struct sockaddr_in frontend_addr = info->client; + frontend_addr.sin_port = htons(port); // nlprint("ENTERING MAIN LOOP"); while (are_relays_running()) { nlprint("STARTED RELAYS"); - relay_ports console_to_frontend = create_ports(from_console, from_frontend, &frontend_addr, frontend_addr_size); - relay_ports frontend_to_console = create_ports(from_frontend, from_console, (sockaddr_u *) &console_addr, sizeof(struct sockaddr_in)); + relay_ports console_to_frontend = create_ports( + from_console, from_frontend, &frontend_addr); + relay_ports frontend_to_console = create_ports( + from_frontend, from_console, &console_addr); pthread_t a_thread, b_thread; pthread_create(&a_thread, NULL, do_relay, &console_to_frontend); @@ -685,13 +773,15 @@ int check_for_disconnection(struct sync_args *args) vanilla_pipe_command_t cmd; char buf[1024]; size_t buf_len = sizeof(buf); - if (wpa_ctrl_recv(args->ctrl, buf, &buf_len) == 0) { + if (wpa_ctrl_recv(args->monitor, buf, &buf_len) == 0) { if (!memcmp(buf, "<3>CTRL-EVENT-DISCONNECTED", 26)) { nlprint("Wii U disconnected, attempting to re-connect..."); // Let client know we lost connection cmd.control_code = VANILLA_PIPE_CC_DISCONNECTED; - sendto(args->skt, &cmd, sizeof(cmd.control_code), 0, (const struct sockaddr *) &args->client, args->client_size); + sendto(args->skt, &cmd, sizeof(cmd.control_code), 0, + (const struct sockaddr *) &args->client, + sizeof(args->client)); return 1; } @@ -708,9 +798,7 @@ void create_all_relays(struct sync_args *args) if (!args->local) { // Set common info for all vid_info.wireless_interface = aud_info.wireless_interface = msg_info.wireless_interface = cmd_info.wireless_interface = hid_info.wireless_interface = args->wireless_interface; - vid_info.local = aud_info.local = msg_info.local = cmd_info.local = hid_info.local = args->local; vid_info.client = aud_info.client = msg_info.client = cmd_info.client = hid_info.client = args->client; - vid_info.client_size = aud_info.client_size = msg_info.client_size = cmd_info.client_size = hid_info.client_size = args->client_size; vid_info.port = PORT_VID; aud_info.port = PORT_AUD; @@ -731,7 +819,7 @@ void create_all_relays(struct sync_args *args) // Notify client that we are connected vanilla_pipe_command_t cmd; cmd.control_code = VANILLA_PIPE_CC_CONNECTED; - sendto(args->skt, &cmd, sizeof(cmd.control_code), 0, (const struct sockaddr *) &args->client, args->client_size); + sendto(args->skt, &cmd, sizeof(cmd.control_code), 0, (const struct sockaddr *) &args->client, sizeof(args->client)); while (!is_interrupted()) { if (check_for_disconnection(args)) { @@ -763,6 +851,16 @@ void *thread_handler(void *data) pthread_mutex_unlock(&running_mutex); void *ret = args->start_routine(data); + int status = (int) (intptr_t) ret; + + if (status != VANILLA_SUCCESS && !is_interrupted()) { + vanilla_pipe_command_t cmd; + cmd.control_code = VANILLA_PIPE_CC_STATUS; + cmd.status.status = htonl(status); + if (sendto(args->skt, &cmd, sizeof(cmd.control_code) + sizeof(cmd.status), 0, (const struct sockaddr *) &args->client, sizeof(args->client)) < 0) { + nlprint("FAILED TO SEND ACTION ERROR %i: %i", status, errno); + } + } free(args); @@ -779,7 +877,7 @@ char wireless_connect_config_filename[1024] = {0}; size_t get_home_directory_file(const char *filename, char *buf, size_t buf_size) { - return snprintf(buf, buf_size, "/tmp/%s", filename); + return snprintf(buf, buf_size, "%s/%s", get_wpa_runtime_directory(), filename); } const char *get_wireless_connect_config_filename() @@ -854,7 +952,7 @@ int create_connect_config(const char *filename, unsigned char *bssid, unsigned c } static const char *template = - "ctrl_interface=/var/run/wpa_supplicant_drc\n" + "ctrl_interface=%s\n" "ap_scan=1\n" "scan_cur_freq=1\n" "\n" @@ -882,7 +980,7 @@ int create_connect_config(const char *filename, unsigned char *bssid, unsigned c bytes_to_str(bssid, sizeof(vanilla_bssid_t), ":", bssid_str); bytes_to_str(psk, sizeof(vanilla_psk_t), 0, psk_str); - fprintf(out_file, template, bssid_str, ssid_str, psk_str); + fprintf(out_file, template, get_wpa_ctrl_interface(), bssid_str, ssid_str, psk_str); fclose(out_file); return VANILLA_SUCCESS; @@ -892,7 +990,40 @@ ssize_t send_ping_to_client(struct sync_args *args) { vanilla_pipe_command_t cmd; cmd.control_code = VANILLA_PIPE_CC_PING; - return sendto(args->skt, &cmd, sizeof(cmd.control_code), 0, (const struct sockaddr *) &args->client, args->client_size); + return sendto(args->skt, &cmd, sizeof(cmd.control_code), 0, (const struct sockaddr *) &args->client, sizeof(args->client)); +} + +static int wait_for_scan_results_event(struct sync_args *args) +{ + static const int poll_interval_us = 100000; + static const int timeout_seconds = 15; + char event[1024]; + + for (int elapsed = 0; elapsed < timeout_seconds * 1000000; elapsed += poll_interval_us) { + if (is_interrupted()) { + return -1; + } + + int pending = wpa_ctrl_pending(args->monitor); + if (pending < 0) { + return -1; + } + if (pending > 0) { + size_t event_len = sizeof(event); + if (wpa_ctrl_recv(args->monitor, event, &event_len) < 0) { + return -1; + } + if (event_len >= strlen("<3>CTRL-EVENT-SCAN-RESULTS") + && !memcmp(event, "<3>CTRL-EVENT-SCAN-RESULTS", strlen("<3>CTRL-EVENT-SCAN-RESULTS"))) { + return 0; + } + continue; + } + + usleep(poll_interval_us); + } + + return -1; } void *sync_with_console_internal(void *data) @@ -921,10 +1052,18 @@ void *sync_with_console_internal(void *data) // nlprint("SCANNING"); actual_buf_len = buf_len; - wpa_ctrl_command(args->ctrl, "SCAN", buf, &actual_buf_len); + int command_status = + wpa_ctrl_command(args->ctrl, "SCAN", buf, &actual_buf_len); + + if (command_status < 0) { + nlprint("SCAN COMMAND TIMED OUT (%i), RETRYING", + command_status); + sleep(5); + continue; + } if (!memcmp(buf, "FAIL-BUSY", 9)) { - //nlprint("DEVICE BUSY, RETRYING"); + nlprint("SCAN BUSY, WAITING FOR SUPPLICANT"); sleep(5); } else if (!memcmp(buf, "OK", 2)) { break; @@ -934,17 +1073,32 @@ void *sync_with_console_internal(void *data) } } - //nlprint("WAITING FOR SCAN RESULTS"); + if (wait_for_scan_results_event(args) < 0) { + nlprint("TIMED OUT WAITING FOR SCAN COMPLETION, RETRYING"); + continue; + } + actual_buf_len = buf_len; - wpa_ctrl_command(args->ctrl, "SCAN_RESULTS", buf, &actual_buf_len); + if (wpa_ctrl_command(args->ctrl, "SCAN_RESULTS", buf, + &actual_buf_len) < 0) { + nlprint("FAILED TO READ SCAN RESULTS, RETRYING"); + sleep(2); + continue; + } nlprint("RECEIVED SCAN RESULTS"); + if (actual_buf_len < buf_len) { + buf[actual_buf_len] = '\0'; + } else { + buf[buf_len - 1] = '\0'; + } + const char *line = strtok(buf, "\n"); while (line) { if (is_interrupted()) goto exit_loop; if (strstr(line, "WiiU") && strstr(line, "_STA1")) { - nlprint("FOUND WII U, TESTING WPS PIN"); + nlprint("FOUND WII U, STARTING CONSOLE PAIRING"); // Make copy of bssid for later strncpy(bssid, line, sizeof(bssid)); @@ -954,9 +1108,12 @@ void *sync_with_console_internal(void *data) snprintf(wps_buf, sizeof(wps_buf), "WPS_PIN %.*s %04d5678", 17, bssid, args->code); size_t actual_buf_len = buf_len; - wpa_ctrl_command(args->ctrl, wps_buf, buf, &actual_buf_len); + if (wpa_ctrl_command(args->ctrl, wps_buf, buf, &actual_buf_len) < 0) { + nlprint("FAILED TO START CONSOLE PAIRING"); + break; + } - static const int max_wait = 20; + static const int max_wait = 60; int wait_count = 0; int cred_received = 0; @@ -968,11 +1125,29 @@ void *sync_with_console_internal(void *data) goto exit_loop; } - if (!wpa_ctrl_pending(args->ctrl)) { + int pending = wpa_ctrl_pending(args->monitor); + if (pending <= 0) { + if (pending < 0) { + nlprint("FAILED TO POLL PAIRING EVENTS"); + goto exit_loop; + } // If we haven't gotten any information yet, wait one second and try again wait_count++; if (wait_count == max_wait) { - nlprint("GIVING UP, RETURNING TO SCANNING"); + nlprint("PAIRING TIMED OUT, CANCELLING BEFORE RESCAN"); + + actual_buf_len = buf_len; + int cancel_status = wpa_ctrl_command( + args->ctrl, "WPS_CANCEL", buf, + &actual_buf_len); + if (cancel_status < 0) { + nlprint("SUPPLICANT CONTROL LOOP IS " + "UNRESPONSIVE (%i)", cancel_status); + goto exit_loop; + } + + nlprint("PAIRING CANCELLED, RETURNING TO SCANNING"); + sleep(1); break; } else { sleep(1); @@ -983,10 +1158,10 @@ void *sync_with_console_internal(void *data) } actual_buf_len = buf_len; - wpa_ctrl_recv(args->ctrl, buf, &actual_buf_len); + wpa_ctrl_recv(args->monitor, buf, &actual_buf_len); if (!strstr(buf, "CTRL-EVENT-BSS-ADDED") && !strstr(buf, "CTRL-EVENT-BSS-REMOVED")) { - nlprint("CRED RECV: %.*s", buf_len, buf); + nlprint("CRED RECV: %.*s", actual_buf_len, buf); } if (!memcmp("<3>WPS-CRED-RECEIVED", buf, 20)) { @@ -1003,7 +1178,11 @@ void *sync_with_console_internal(void *data) // Tell wpa_supplicant to save config (this seems to be the only way to retrieve the PSK) actual_buf_len = buf_len; nlprint("SAVING CONFIG", actual_buf_len, buf); - wpa_ctrl_command(args->ctrl, "SAVE_CONFIG", buf, &actual_buf_len); + if (wpa_ctrl_command(args->ctrl, "SAVE_CONFIG", buf, + &actual_buf_len) < 0) { + nlprint("FAILED TO SAVE PAIRING CONFIGURATION"); + goto exit_loop; + } // Retrieve BSSID and PSK from saved config FILE *in_file = fopen(get_wireless_authenticate_config_filename(), "r"); @@ -1023,7 +1202,11 @@ void *sync_with_console_internal(void *data) // Convert BSSID from string to bytes str_to_bytes(bssid, 1, cmd.connection.bssid.bssid, sizeof(cmd.connection.bssid.bssid)); - sendto(args->skt, &cmd, sizeof(cmd.control_code) + sizeof(cmd.connection), 0, (const struct sockaddr *) &args->client, args->client_size); + sendto(args->skt, &cmd, + sizeof(cmd.control_code) + + sizeof(cmd.connection), + 0, (const struct sockaddr *) &args->client, + sizeof(args->client)); ret = VANILLA_SUCCESS; } else { @@ -1033,6 +1216,8 @@ void *sync_with_console_internal(void *data) goto exit_loop; } + + break; } line = strtok(NULL, "\n"); } @@ -1048,7 +1233,7 @@ void *do_connect(void *data) while (!is_interrupted()) { while (1) { - while (!wpa_ctrl_pending(args->ctrl)) { + while (wpa_ctrl_pending(args->monitor) <= 0) { sleep(2); nlprint("WAITING FOR CONNECTION"); @@ -1057,7 +1242,7 @@ void *do_connect(void *data) char buf[1024]; size_t actual_buf_len = sizeof(buf); - wpa_ctrl_recv(args->ctrl, buf, &actual_buf_len); + wpa_ctrl_recv(args->monitor, buf, &actual_buf_len); if (!strstr(buf, "CTRL-EVENT-BSS-ADDED") && !strstr(buf, "CTRL-EVENT-BSS-REMOVED")) { nlprint("CONN RECV: %.*s", actual_buf_len, buf); @@ -1073,8 +1258,24 @@ void *do_connect(void *data) nlprint("CONNECTED TO CONSOLE"); // Use DHCP on interface - int r = call_dhcp(args->wireless_interface); + struct nl_sock *dhcp_socket = nl_socket_alloc(); + if (!dhcp_socket) { + nlprint("FAILED TO ALLOC DHCP NL_SOCK"); + return THREADRESULT(VANILLA_ERR_GENERIC); + } + + int nlr = nl_connect(dhcp_socket, NETLINK_ROUTE); + if (nlr < 0) { + nlprint("FAILED TO CONNECT DHCP NL_SOCK: %s", nl_geterror(nlr)); + nl_socket_free(dhcp_socket); + return THREADRESULT(VANILLA_ERR_GENERIC); + } + + int r = call_dhcp(args->wireless_interface, dhcp_socket); if (r != VANILLA_SUCCESS) { + nl_close(dhcp_socket); + nl_socket_free(dhcp_socket); + // For some reason, DHCP did not succeed. Determine if it's because // the Wi-Fi disconnected mid-handshake. if (check_for_disconnection(args)) { @@ -1091,6 +1292,12 @@ void *do_connect(void *data) } create_all_relays(args); + +#ifdef ANDROID + vanilla_android_remove_console_routing(dhcp_socket); +#endif + nl_close(dhcp_socket); + nl_socket_free(dhcp_socket); } return THREADRESULT(VANILLA_SUCCESS); @@ -1110,7 +1317,8 @@ void *vanilla_sync_with_console(void *data) return THREADRESULT(VANILLA_ERR_GENERIC); } - fprintf(config, "ctrl_interface=%s\nupdate_config=1\n", wpa_ctrl_interface); + fprintf(config, "ctrl_interface=%s\nupdate_config=1\n", + get_wpa_ctrl_interface()); fclose(config); args->start_routine = sync_with_console_internal; @@ -1199,23 +1407,30 @@ int install_polkit() void pipe_listen(int local, const char *wireless_interface, const char *log_file) { // Some setups require this +#ifndef ANDROID run_process_and_read_stdout((const char *[]) {"rfkill", "unblock", "wlan", NULL}, 0, 0); +#endif // Store reference to log file ext_logfile = log_file; - // Ensure local domain sockets can be written to by everyone - umask(0000); +#ifdef ANDROID + if (vanilla_android_acquire_wifi(wireless_interface) < 0) + return; +#endif int skt = open_socket(local, VANILLA_PIPE_CMD_SERVER_PORT); if (skt == -1) { nlprint("Failed to open server socket"); +#ifdef ANDROID + vanilla_android_release_wifi(); +#endif return; } vanilla_pipe_command_t cmd; - sockaddr_u addr; + struct sockaddr_in addr; pthread_mutex_init(&running_mutex, NULL); pthread_mutex_init(&action_mutex, NULL); @@ -1233,6 +1448,7 @@ void pipe_listen(int local, const char *wireless_interface, const char *log_file nlprint("READY"); // If this is the Steam Deck, we must switch the backend from `iwd` to `wpa_supplicant` +#ifndef ANDROID int sd_wifi_backend_changed = 0; { char sd_wifi_backend_buf[100] = {0}; @@ -1243,6 +1459,7 @@ void pipe_listen(int local, const char *wireless_interface, const char *log_file run_process_and_read_stdout((const char *[]) {"steamos-wifi-set-backend", "wpa_supplicant", NULL}, 0, 0); } } +#endif #ifdef USE_LIBNM // Check status of interface with NetworkManager @@ -1309,7 +1526,6 @@ void pipe_listen(int local, const char *wireless_interface, const char *log_file args->local = local; args->skt = skt; args->client = addr; - args->client_size = addr_size; if (cmd.control_code == VANILLA_PIPE_CC_SYNC) { args->code = ntohs(cmd.sync.code); @@ -1390,11 +1606,13 @@ die_and_close_nmcli: } #endif +#ifndef ANDROID if (sd_wifi_backend_changed) { // Restore iwd nlprint("STEAM DECK: SETTING WIFI BACKEND TO IWD"); run_process_and_read_stdout((const char *[]) {"steamos-wifi-set-backend", "iwd", NULL}, 0, 0); } +#endif // Restore previous Wi-Fi power saving state if (ps_nl) { @@ -1409,6 +1627,10 @@ die_and_close_nmcli: nl_close(ps_nl); nl_socket_free(ps_nl); } + +#ifdef ANDROID + vanilla_android_release_wifi(); +#endif } int vanilla_has_config() diff --git a/pipe/linux/wpa.h b/pipe/linux/wpa.h index a411aa6..b08953c 100644 --- a/pipe/linux/wpa.h +++ b/pipe/linux/wpa.h @@ -7,7 +7,8 @@ struct wpa_ctrl; -void wpa_ctrl_command(struct wpa_ctrl *ctrl, const char *cmd, char *buf, size_t *buf_len); +int wpa_ctrl_command(struct wpa_ctrl *ctrl, const char *cmd, char *buf, + size_t *buf_len); int start_wpa_supplicant(const char *wireless_interface, const char *config_file, pid_t *pid); int is_networkmanager_managing_device(const char *wireless_interface, int *is_managed); @@ -15,6 +16,8 @@ int disable_networkmanager_on_device(const char *wireless_interface); int enable_networkmanager_on_device(const char *wireless_interface); void nlprint(const char *fmt, ...); +ssize_t run_process_and_read_stdout(const char **args, char *read_buffer, + size_t read_buffer_len); void pipe_listen(int local, const char *wireless_interface, const char *log_file);