GCC 预定义宏: Endian

#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
// little endian
#elif (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
// big endian
#elif (__BYTE_ORDER__ == __ORDER_PDP_ENDIAN__)
// pdp endian, The bytes in 16-bit words are laid out in
// a little-endian fashion, whereas the 16-bit subwords of
// a 32-bit quantity are laid out in big-endian fashion. 
#endif

Over!

Run command ‘su’ in PHP

升级 VPS 后,由于 Ubuntu 的 upstart 与 OpenVZ 的兼容问题,导致 sshd 服务不自动启动了,在尝试了 vePortal 的 console 与 file manager 及提交技术支持后都不能解决问题之后。

只能靠自己了,大概的思路是在 PHP 中进行 su 命令以执行 sshd 服务,因为 WordPress 还活着,并且可以在后台直接编辑主题相关的 PHP 脚本。只要把准备好的代码片断插入到 header.php 中,并在浏览器中访问一下主页即可。

相关的代码逻辑
1. 使用 PHP 的 proc_open 打开一个进程,重定向 stdin, stdout, stderr, 这里会执行一个 python 程序。
2. 在这个 python 程序中打开一个 pty,并运行一个 sh。
3. 利用步骤 1 中重定向的 stdin pipe 向 python 程序发送 su 命令, python 会将来自 stdin 的命令数据写到入 ptmx,而这时 sh 的 stdin, stdout 及 stderr 是重定向到与 python 打开的 ptmx 配对的 pts 上的。也就是说 su 命令最终会转给 sh 进程处理。
4. sh 进程自然的执行了 su 命令,这时 su 进程的 stdin, stdout, stderr 也会被重定向到那个 pts 上。
5. 在 sleep 一段时间后(主要是等 su 真的跑起来了),再写入密码,数据流过程与步骤 3、4 一致。

相关的代码片断

<?php
  $descriptorspec = array(
    0 => array("pipe", "r"),  // stdin
    1 => array("pipe", "w"),  // stdout
    2 => array("pipe", "w")   // stderr
  );
  $process = proc_open("python -c 'import pty; pty.spawn(\"/bin/sh\")'", $descriptorspec, $pipes);
  if (is_resource($process)) {
    fwrite($pipes[0], "su -c 'service ssh start' root\n");
    fflush($pipes[0]);
    sleep(3);
    fwrite($pipes[0], "PASSWORD\n");
    fflush($pipes[0]);
    fclose($pipes[0]);
    fclose($pipes[1]);
    fclose($pipes[2]);
    proc_close($process);
  } 
?>

Over!

MIPS N32 编译 Firefox 链接 libxu.so 时被 Kill

在 Arch Linux (MIPS N32) 系统中编译 Firefox,链接 libxul.so 过程中 ld 进程被 system 给 kill 了,多次尝试都如此。检查发现是 OOM 触发了 system 将进程 kill 了。增加 swap 后仍然无效,原因是 32-bit 的虚拟地址空间不足了。。。

在这种杯具的情况下,可以启动 ld 参数 –no-keep-memory 临时解决此问题。

--no-keep-memory
ld normally optimizes for speed over memory usage by caching the symbol tables
 of input files in memory. This option tells ld to instead optimize for memory
 usage, by rereading the symbol tables as necessary. This may be required if ld
 runs out of memory space while linking a large executable.

如果还是不行,那就要再加上 -S 或 –strip-debug

-S
--strip-debug
    Omit debugger symbol information (but not all symbols) from the output file.

Over!

Kernel 不支持 timerfd 使用 timeout

VPS 内核版本较低,低到还没实现 timerfd。由于是 OpenVZ 的方案,基本上是无法更新 Kernel 的。有一个必要应用正好要使用 HevLib 库的 HevEventSourceTimeout,此 source 是基于 timerfd 的,临时的解决方法是使用 HevEventSourceSignal 监听 SIGALRM 信号, 使用 alarm 或 ualaram 设置定时器:

#include <stdio.h>
#include <signal.h>
#include <hev-lib.h>
 
static bool
signal_handler (void *data)
{
        printf ("timeout signal\n");
 
        alarm (1);
 
        return true;
}
 
int
main (int argc, char *argv[])
{
        HevEventLoop *loop = NULL;
        HevEventSource *signal = NULL;
 
        loop = hev_event_loop_new ();
 
        signal = hev_event_source_signal_new (SIGALRM);
        hev_event_source_set_callback (signal, signal_handler, NULL, NULL);
        hev_event_loop_add_source (loop, signal);
        hev_event_source_unref (signal);
 
        alarm (1);
 
        hev_event_loop_run (loop);
 
        hev_event_loop_unref (loop);
 
        return 0;
}

Over!

Android 自动启动服务

最近在将 HevSocks5Client 移植到 Android 上了,在经过增加 signalfd 和 timerfd 相关的系统调用支持后,就可以直接使用 NDK 编译出 executable 了。直接的 native exectuable 在 Android 系统总还是不太方便用哦。还是做成一个 apk 吧,暂定只写一个 service 并开机自动启用,无 activity 的。

Java 中调用 native 程序我选择使用 JNI 方式,直接在 JNI_OnLoad 方法中调用 pthread_create 创建个线程跑原来的 main 就行啦。

...
#if defined(ANDROID)
#include <jni.h>
#include <pthread.h>
#endif
 
int
main (int argc, char *argv[])
{
    ...
}
 
#if defined(ANDROID)
static void *
thread_handler (void *data)
{
    main (0, NULL);
    return NULL;
}
 
jint
JNI_OnLoad (JavaVM *vm, void *reserved)
{
    pthread_t thread;
    pthread_create (&thread, NULL, thread_handler, NULL);
    return JNI_VERSION_1_4;
}
#endif

Android 服务
服务主要是加载 JNI 接口的 hev-socks5-client 库,使服务跑起来。

package hev.socks5;
 
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
 
public class MainService extends Service {
 
        static {
                System.loadLibrary("hev-socks5-client");
        }
 
        public IBinder onBind(Intent intent) {
                return null;
        }
}

BroadcastReceiver
ServiceReceiver 的功能就是监听系统上的 BOOT_COMPLETED 事件,用于实现自动启动服务。

package hev.socks5;
 
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
 
public class ServiceReceiver extends BroadcastReceiver {
 
        @Override
        public void onReceive(Context context, Intent intent) {
                if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
                        Intent i = new Intent(context, MainService.class);
                        context.startService(i);
                }
        }
}

AndroidManifest.xml
最后,要在 Manifest 中注册 Service 和 Receiver,增加上访问 Internet 和 Boot completed 的权限。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="hev.socks5"
          android:versionCode="1"
          android:versionName="1.0">
        <application android:label="@string/app_name" >
                <service android:name=".MainService">
                        <intent-filter>
                                <action android:name="hev.socks5.MainService" />
                        </intent-filter>
                </service>
                <receiver android:enabled="true" android:name=".ServiceReceiver">
                        <intent-filter>
                                <action android:name="android.intent.action.BOOT_COMPLETED" />
                        </intent-filter>
                </receiver>
        </application>
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
</manifest>

Tips
此方法仅在 Android 2.3.3 及之前版本有效,之后版本如果此应用在安装后从没运行过,Broadcast Receiver 将接收不到 boot completed 的 action,解决方法是使用命令手动启动一下 service。

am startservice hev.socks5/.MainService

Over!

变量运算溢出问题

最近犯了一个低级错误呀,记录一下这个教训吧,大概如下:

struct timespec {
    time_t tv_sec;
    long tv_nsec;
};
struct timespec ts;
uint64_t nsec = ts.tv_sec * 1000000000 + ts.tv_nsec;

也就是将 ts 换算成 nanoseconds,在 64-bit 系统上工作的没有问题,但 32-bit 系统上就杯具了。

这个地方可能会有两个问题:
1. 认为目标变量是 64-bit 就足够了,但乘法运算实际上是 (time_t) (tv.tv_sec * 1000000000),结果有可能溢出了。
2. 认为 time_t 类型在所有环境下都是足够的。(我就是认为它都是 64-bit 的)

Over!

移植 HevLib 到 Android 系统

打算将 HevSocks5Client 移植到 Android 系统,替换原来的 HevTLSTunnel 实现。先要移植 HevLib,原以为只要增加两个文件 Application.mk 和 Android.mk 就可以了。但编译到 hev-event-source-signal.c 的时候就出错了,报 sys/signalfd.h 文件找不到。于是 android-ndk 目录里搜索了一下,还真没有这个文件的。这才意识到有可能 Android 的用户态目前还没开放这些 API。

如果仅仅是用户态没有导出问题应该不太,关键内核要实现了这些功能。还好我的系统中存在 /proc/config.gz 文件,解压查询确认 SIGNALFD、TIMERFD 及 EVENTFD 都是支持。下面的事情就好办了,就是增加几个系统调用的实现问题了。进一步确认了间接系统调用 syscall 函数是实现了的,连汇编都不用写了。

diff --git a/src/hev-event-source-signal.c b/src/hev-event-source-signal.c
index daf3e96..01f0088 100644
--- a/src/hev-event-source-signal.c
+++ b/src/hev-event-source-signal.c
@@ -11,7 +11,12 @@
 #include <unistd.h>
 #include <signal.h>
 #include <sys/epoll.h>
+#if defined(ANDROID)
+#include <fcntl.h>
+#include <asm/unistd.h>
+#else /* GENERIC */
 #include <sys/signalfd.h>
+#endif
 
 #include "hev-event-source-signal.h"
 
@@ -25,6 +30,31 @@ struct _HevEventSourceSignal
 	int signal_fd;
 };
 
+#if defined(ANDROID)
+#define SFD_NONBLOCK	O_NONBLOCK
+
+struct signalfd_siginfo
+{
+  uint32_t ssi_signo;
+  int32_t ssi_errno;
+  int32_t ssi_code;
+  uint32_t ssi_pid;
+  uint32_t ssi_uid;
+  int32_t ssi_fd;
+  uint32_t ssi_tid;
+  uint32_t ssi_band;
+  uint32_t ssi_overrun;
+  uint32_t ssi_trapno;
+  int32_t ssi_status;
+  int32_t ssi_int;
+  uint64_t ssi_ptr;
+  uint64_t ssi_utime;
+  uint64_t ssi_stime;
+  uint64_t ssi_addr;
+  uint8_t __pad[48];
+};
+#endif
+
 static HevEventSourceFuncs hev_event_source_signal_funcs =
 {
 	.prepare = NULL,
@@ -33,6 +63,14 @@ static HevEventSourceFuncs hev_event_source_signal_funcs =
 	.finalize = hev_event_source_signal_finalize,
 };
 
+#if defined(ANDROID)
+static int
+signalfd (int fd, const sigset_t *mask, int flags)
+{
+	return syscall (__NR_signalfd4, fd, mask, _NSIG / 8, flags);
+}
+#endif
+
 HevEventSource *
 hev_event_source_signal_new (int signal)
 {
diff --git a/src/hev-event-source-timeout.c b/src/hev-event-source-timeout.c
index 57a9b00..cacd196 100644
--- a/src/hev-event-source-timeout.c
+++ b/src/hev-event-source-timeout.c
@@ -11,10 +11,20 @@
 #include <errno.h>
 #include <unistd.h>
 #include <sys/epoll.h>
+#if defined(ANDROID)
+#include <fcntl.h>
+#include <asm/unistd.h>
+#else /* GENERIC */
 #include <sys/timerfd.h>
+#endif
 
 #include "hev-event-source-timeout.h"
 
+#if defined(ANDROID)
+#define TFD_NONBLOCK	O_NONBLOCK
+#define TFD_TIMER_ABSTIME 1
+#endif
+
 static bool hev_event_source_timeout_prepare (HevEventSource *source);
 static bool hev_event_source_timeout_check (HevEventSource *source, HevEventSourceFD *fd);
 static void hev_event_source_timeout_finalize (HevEventSource *source);
@@ -35,6 +45,22 @@ static HevEventSourceFuncs hev_event_source_timeout_funcs =
 	.finalize = hev_event_source_timeout_finalize,
 };
 
+#if defined(ANDROID)
+static int
+timerfd_create (int clockid, int flags)
+{
+	return syscall (__NR_timerfd_create, clockid, flags);
+}
+
+static int
+timerfd_settime (int fd, int flags,
+			const struct itimerspec *new_value,
+			struct itimerspec *old_value)
+{
+	return syscall (__NR_timerfd_settime, fd, flags, new_value, old_value);
+}
+#endif
+
 HevEventSource *
 hev_event_source_timeout_new (unsigned int interval)
 {

Over!

重新实现了 Socsk5

可能你会想实现那么多的 Socsk5 能吃吗?其实我只是想研究一下 I/O 模型,拿它来练习一下,如果效果不错也可以拿来用用。本次的实现没有使用 GLib,或许我真的想再也不用 GLib 啦?应该是这样的吧,至少我已经开始在实现我需要的东西。HevLib 就是我的一个初级替代品吧,目前仅仅包含最基本的数据结构、EventSource、EventLoop 等等。更多的数据结构与算法还有类型和对象系统就在以后有需要的时候再实现,一步一步来吧。

重写的 Socks5 Proxy 就是使用了 HevLib 的事件循环机制(Epoll,优先级排序),目前是单线程异步事件驱动的,感觉异步事件驱动实现起来还是有点麻烦的,和内核的进程调度一样,要上下文切换,实际来说 Session 中已经存储了上下文了,要做说也就是流程控制,我是简单的使用了一个状态变量控制的(可能不是太好,有好的方法可以告诉我哦)。

另外,在 DNS 解析上遇到一点麻烦、gethostbyname 虽然工作的没问题,显然这个阻塞的方法并不适合我现在的模型,又看了一下 getaddrinfo_a 和 c-ares 这个异步 DNS 查询库,结果发现想套到当前的事件循环机制中都有一些麻烦。后来就直接基于 UDP 实现了一个简单的异步的标准A记录查询器,简单又好用。

现在服务已经在 VPS 上部署了,RAM 的使用由原来的 300+M 直接下降到 80+M,功能还和原来一样的。;)

代码仓库:https://github.com/heiher/hev-socks5-proxy

Over!

静态链接 HevSCGIHandler

在原来 HevSCGIServer 的设计中 HevSCGIHandler 接口的实现的整合形式为共享库,HevSCGIServerLibrary 会读取 modules.conf 配置文件,解析出 Handlers 配置,应用于 TaskDispatcher 中。最近由于一个特殊的需求,HevSCGIServerLibrary 和 HevSCGIHandler 都需要静态链接到主程序中,这在原来的设计中上不支持的。

分析发现 HevSCGIHandler 要做的就是定义一个类,主要是要实现了 HevSCGIHandler 接口,模块还实现一个导出函数 hev_scgi_handler_module_get_handler_type 注册与导出 GType。模块被 Library 加载的过程就是通过查找模块的 hev_scgi_handler_module_get_handler_type 函数,调用并得到 GType,然后实例化进增加到 TaskDispatcher。

在此基础上需要对 Library 和 Handler 实现做一些简单的修改就可以支持静态链接 Handler 实现了,首先对于 Library 要做的就是在实例化之前调用 Handler 实现的注册方法向 GType system 注册 GType,然后 Library 根据 modules.conf 中的 TypeName 从 GType system 找到 GType 再进行实例化,最后加入 TaskDispatcher;而对于 Handler 实现来说,就是要简单修改 G_DEFINE_TYPE_DYNAMIC… 为 G_DEFINE_TYPE,在 Makefile 中通过开关宏分离 static 与 shared 的实现。

diff --git a/src/hev-scgi-server.c b/src/hev-scgi-server.c
index ab2d9bb..99f0a40 100644
--- a/src/hev-scgi-server.c
+++ b/src/hev-scgi-server.c
@@ -20,6 +20,9 @@
 #include "hev-scgi-handler-module.h"
 #include "hev-scgi-handler-default.h"
 
+#include <hev-scgi-handler-cgi.h>
+#include <hev-scgi-handler-filebox.h>
+
 enum
 {
        PROP_0,
@@ -148,7 +151,8 @@ static void hev_scgi_server_constructed(GObject * obj)
          g_critical("%s:%d[%s]", __FILE__, __LINE__, __FUNCTION__);
 
        /* Register static handler types at here*/
-
+       HEV_REGISTER_TYPE_SCGI_HANDLER_CGI();
+       HEV_REGISTER_TYPE_SCGI_HANDLER_FILEBOX();
 
        module_dir_path = hev_scgi_config_get_module_dir_path(
                                HEV_SCGI_CONFIG(priv->scgi_config));
# modules.conf
 
[Module0]
Alias=HevSCGIHandlerFilebox
Pattern=^/fb(.*)$
TypeName=HevSCGIHandlerFilebox
 
BaseURI=/fb/
CleanInterval=600
FilePoolPath=/tmp
FileMetaPath=/tmp
FileTempPath=/tmp
 
[Module1]
Alias=HevSCGIHandlerCGI
Pattern=^/cgi((/|\\?)(.*))?$
TypeName=HevSCGIHandlerCGI
 
CGIBinPath=/tmp/test.cgi
WorkDir=/tmp

最后,只要在主程序中将 library 与 handler 实现全部静态链接就可以了。