ppp/pppd/pptp插件编写手册/指南
[| 2012/07/01 15:27]
最近研究了一下pptp vpn的插件编写。刚开始的时候以为身份认证等信息是pptpd服务进程做的,后来看了代码后发现并非如此,是在pppd那里做的,也就是ppp软件包。
下载了ppp源码看了下,当前版本是2.4.5,项目居然在samba域名下,不知道这个跟samba有啥联系。
在代码的pppd/plugins里面有一些插件,比如radius的,看了下源码。大致了解了下思路:
一个插件即是一个标准的动态链接库。pppd使用dlopen()库调用来加载。pppd使用plugin选项来加载插件,只有一个参数:插件文件名。如果指定的文件名没有斜线,pppd会到/usr/lib/pppd/目前下面查找该文件,这里面version是pppd的版本,比如2.4.5。也可以设置全路径,在使用自己编写的插件时比较方便。
插件可以做的事:
使用add_options(),传入option_t 。最后一项是NULL,跟lighttpd配置是一样的。这个作用应该是时ppp支持相应配置。
指定hook。pppd会在处理过程中调用各个钩子。插件可以任意设置自己的过程到这些hook。
插件可以用add_notifier消息回调。
#include "pppd.h"
#include "chap-new.h"
char pppd_version[] = VERSION;
static int pppd_chap_check(void) {
return 1;
}
static int pppd_chap_verify(char *user, char *ourname, int id, struct chap_digest_type *digest, unsigned char *challenge, unsigned char *response, char *message, int message_space) {
if(digest->verify_response(id, user, "a", strlen("a"), challenge, response, message, message_space)) {
return 1;
} else {
return 0;
}
}
static int check_address_allowed(int addr) {
return 1;
}
void plugin_init(void) {
chap_check_hook = pppd_chap_check;
chap_verify_hook = pppd_chap_verify;
allowed_address_hook = check_address_allowed;
}
这是一个简单的插件源码,如果用户密码是a就通过。可以进行扩充就可以让pppd支持多种不同的权限认证,并且可以支持各种计数操作。该插件支持chap认证,包括mschap,mschap-v2,chap方式,至于pap,由于安全性不行,不支持也无所谓。
这里需要注意allowed_address_hook 需要实现,表示同意使用分配的该ip,没有实现时会在注册网络时失败。
char pppd_version[] = VERSION;
表示支持的ppp版本。
plugin_init,就是插件被载入时执行的操作,执行hook注册等等操作。
chap_check_hook 表示该插件是否可以进行chap认证。
chap_verify_hook 是身份认证的过程,一般使用digest->verify_response来进行校验。
以下是一篇资料里贴出的函数及消息列表:
插件钩子函数列表
int (*idle_time_hook)(struct ppp_idle *idlep);
idle_time_hook在这个连接第一次开始的时候被调用(比如,第一个网络协议开始的时候),那之后被定时调用。在第一次调用的时候,idlep参数是NULL,返回值则是pppd检查连接活跃前的秒数,或者0表示没有超时。
在后来的调用中,idlep指令一个指向最包被发送、接收秒数的结构体。如果返回的值大于0,pppd会在再次检查之前等一些的秒数。如果小于等于0,就是说该连接在不活跃的情况下应该被终结。
int (*holdoff_hook)(void);
当尝试拌连接失败或是连接被终结的时候,holdoff_hook被调用,persist或是demand选项被使用。它返回PPTP重新建立连接应该等待的秒数(0表示立即)。
int (*chap_check_hook)(void);
int (*chap_passwd_hook)(char *user, char *passwd);
int (*chap_auth_hook)(char *user, u_char *remmd, int remmd_len, chap_state *cstate);
这些hook被设计用来在插件里面替换常规的CHAP密码处理过程(比如外部服务器的认证)。
chap_check_hook被调用来检查对端是否有必要向我们认证其自身。如果返回1,pppd将询问询问其自身,否则返回0(如果该认证被要求了,在网络协议协商之前pppd会退出或是终结当前连接)。如果返回-1,pppd将查找chap-secrets文件使用常规方式处理。
chap_passwd_hook决定pppd应该使用什么密码来使用CHAP来向对端认证自身。user字符串使用’user’选项或者’name’选项、主机名进行初始化,有必须可以进行修改。这个钩子只有当ppdp是客户客户端而非服务的时候被调用。passwd可以容纳最多MAXSECRETLEN 字节。如果钩子返回0,PPPD使用 *passwd,如果返回 -1,pppd认证失败。
chap_auth_hook 钩子确定由对应提供的CHAP挑战响应是否有效。user指向一个包含对端提供用户名的非NULL结束的字符串。remmd向向对端提供的响应,由remmd_len表示其长度 。cstate是PPTP维护的内部CHAP状态结构体。chap_auth_hook应该返回CHAP_SUCCESS 或者CHAP_FAILURE。
int (*null_auth_hook)(struct wordlist **paddrs, struct wordlist **popts);
这个钩子允许插件确定当请求的认证被对端拒绝的时候应该采取的策略。 如果返回0,连接被终结;1,连接被允许处理,这种情况下 *paddrs和*popts可像pap_auth_hook一样被设置,以指定被允许的IP地址列表和任意扩展属性。如果返回-1,pppd查找pap-secret文件按常规处理。
void (*ip_choose_hook)(u_int32_t *addrp);
该钩子在IPCP协商开始的时候调用。它使得插件有机会设置对端的IP地址。地方应该保存在*addrp。如果*addrp里什么也没有存储的庆,pppd使用常规方式决定对端的地址。
int (*allowed_address_hook)(u_int32_t addr)
这个钩子确认对端是否可以使用指定的IP地址。如果钩子返回1,地址可以接受,返回0为拒绝。如果返回-1,将使用常规方式查找适当的选项和secrets文件来决定。
void (*snoop_recv_hook)(unsigned char *p, int len)
void (*snoop_send_hook)(unsigned char *p, int len)
这些钩子在接受或是发送数据包的时候被调用。数据包在p里同,长度由 len表式。使得插件可以检查pppd的会话。这些钩子在实现L2TP的时候将会起到很大的作用。
可注册的消息列表
插件可以使用notifier注册自身,通过申明一个下面形式的过程:
void my_notify_proc(void *opaque, int arg);
然后使用适当的notifier,以下面形式调用来注册这个过程。
add_notifier(&interesting_notifier, my_notify_proc, opaque);
add_notifier 中的’opaque’参数每次调用notifier的时候传递给my_notify_proc 。传递的’arg’参数决定于notifier一个nofify过程可以使用下面的方式从当前的notifier列表中被移除。
remove_notifier(&interesting_notifier, my_notify_proc, opaque);
下面是目前pppd实现的notifier列表。
pidchange 由其父进程在pppd已经forked并且子进程在继续pppd’s处理时调用,比如pppd从其控制终端中分离出来的时候。参数就是这个子进程的pid。
phasechange 当pppd从一个阶段操作转移到另一个的时候调用。参数是新阶段的编号。
exitnotify 在pppd退出前调用。参数是pppd退出的状态。(比如exit()的参数)。
sigreceived 在收到信号的时候调用,存在于signal handle里面。参数是signal的编号。
ip_up_notifier 在IPCP发生的时候调用。
ip_down_notifier 在IPCP关闭的时候调用。
auth_up_notifier 在对端认证自身成功的时候调用。
link_down_notifier 在连接关闭的时候调用。
参考资料:http://liaoweiqiang.info/?p=411
下载了ppp源码看了下,当前版本是2.4.5,项目居然在samba域名下,不知道这个跟samba有啥联系。
在代码的pppd/plugins里面有一些插件,比如radius的,看了下源码。大致了解了下思路:
一个插件即是一个标准的动态链接库。pppd使用dlopen()库调用来加载。pppd使用plugin选项来加载插件,只有一个参数:插件文件名。如果指定的文件名没有斜线,pppd会到/usr/lib/pppd/
插件可以做的事:
使用add_options(),传入option_t 。最后一项是NULL,跟lighttpd配置是一样的。这个作用应该是时ppp支持相应配置。
指定hook。pppd会在处理过程中调用各个钩子。插件可以任意设置自己的过程到这些hook。
插件可以用add_notifier消息回调。
引用
#include "pppd.h"
#include "chap-new.h"
char pppd_version[] = VERSION;
static int pppd_chap_check(void) {
return 1;
}
static int pppd_chap_verify(char *user, char *ourname, int id, struct chap_digest_type *digest, unsigned char *challenge, unsigned char *response, char *message, int message_space) {
if(digest->verify_response(id, user, "a", strlen("a"), challenge, response, message, message_space)) {
return 1;
} else {
return 0;
}
}
static int check_address_allowed(int addr) {
return 1;
}
void plugin_init(void) {
chap_check_hook = pppd_chap_check;
chap_verify_hook = pppd_chap_verify;
allowed_address_hook = check_address_allowed;
}
这是一个简单的插件源码,如果用户密码是a就通过。可以进行扩充就可以让pppd支持多种不同的权限认证,并且可以支持各种计数操作。该插件支持chap认证,包括mschap,mschap-v2,chap方式,至于pap,由于安全性不行,不支持也无所谓。
这里需要注意allowed_address_hook 需要实现,表示同意使用分配的该ip,没有实现时会在注册网络时失败。
char pppd_version[] = VERSION;
表示支持的ppp版本。
plugin_init,就是插件被载入时执行的操作,执行hook注册等等操作。
chap_check_hook 表示该插件是否可以进行chap认证。
chap_verify_hook 是身份认证的过程,一般使用digest->verify_response来进行校验。
以下是一篇资料里贴出的函数及消息列表:
插件钩子函数列表
int (*idle_time_hook)(struct ppp_idle *idlep);
idle_time_hook在这个连接第一次开始的时候被调用(比如,第一个网络协议开始的时候),那之后被定时调用。在第一次调用的时候,idlep参数是NULL,返回值则是pppd检查连接活跃前的秒数,或者0表示没有超时。
在后来的调用中,idlep指令一个指向最包被发送、接收秒数的结构体。如果返回的值大于0,pppd会在再次检查之前等一些的秒数。如果小于等于0,就是说该连接在不活跃的情况下应该被终结。
int (*holdoff_hook)(void);
当尝试拌连接失败或是连接被终结的时候,holdoff_hook被调用,persist或是demand选项被使用。它返回PPTP重新建立连接应该等待的秒数(0表示立即)。
int (*chap_check_hook)(void);
int (*chap_passwd_hook)(char *user, char *passwd);
int (*chap_auth_hook)(char *user, u_char *remmd, int remmd_len, chap_state *cstate);
这些hook被设计用来在插件里面替换常规的CHAP密码处理过程(比如外部服务器的认证)。
chap_check_hook被调用来检查对端是否有必要向我们认证其自身。如果返回1,pppd将询问询问其自身,否则返回0(如果该认证被要求了,在网络协议协商之前pppd会退出或是终结当前连接)。如果返回-1,pppd将查找chap-secrets文件使用常规方式处理。
chap_passwd_hook决定pppd应该使用什么密码来使用CHAP来向对端认证自身。user字符串使用’user’选项或者’name’选项、主机名进行初始化,有必须可以进行修改。这个钩子只有当ppdp是客户客户端而非服务的时候被调用。passwd可以容纳最多MAXSECRETLEN 字节。如果钩子返回0,PPPD使用 *passwd,如果返回 -1,pppd认证失败。
chap_auth_hook 钩子确定由对应提供的CHAP挑战响应是否有效。user指向一个包含对端提供用户名的非NULL结束的字符串。remmd向向对端提供的响应,由remmd_len表示其长度 。cstate是PPTP维护的内部CHAP状态结构体。chap_auth_hook应该返回CHAP_SUCCESS 或者CHAP_FAILURE。
int (*null_auth_hook)(struct wordlist **paddrs, struct wordlist **popts);
这个钩子允许插件确定当请求的认证被对端拒绝的时候应该采取的策略。 如果返回0,连接被终结;1,连接被允许处理,这种情况下 *paddrs和*popts可像pap_auth_hook一样被设置,以指定被允许的IP地址列表和任意扩展属性。如果返回-1,pppd查找pap-secret文件按常规处理。
void (*ip_choose_hook)(u_int32_t *addrp);
该钩子在IPCP协商开始的时候调用。它使得插件有机会设置对端的IP地址。地方应该保存在*addrp。如果*addrp里什么也没有存储的庆,pppd使用常规方式决定对端的地址。
int (*allowed_address_hook)(u_int32_t addr)
这个钩子确认对端是否可以使用指定的IP地址。如果钩子返回1,地址可以接受,返回0为拒绝。如果返回-1,将使用常规方式查找适当的选项和secrets文件来决定。
void (*snoop_recv_hook)(unsigned char *p, int len)
void (*snoop_send_hook)(unsigned char *p, int len)
这些钩子在接受或是发送数据包的时候被调用。数据包在p里同,长度由 len表式。使得插件可以检查pppd的会话。这些钩子在实现L2TP的时候将会起到很大的作用。
可注册的消息列表
插件可以使用notifier注册自身,通过申明一个下面形式的过程:
void my_notify_proc(void *opaque, int arg);
然后使用适当的notifier,以下面形式调用来注册这个过程。
add_notifier(&interesting_notifier, my_notify_proc, opaque);
add_notifier 中的’opaque’参数每次调用notifier的时候传递给my_notify_proc 。传递的’arg’参数决定于notifier一个nofify过程可以使用下面的方式从当前的notifier列表中被移除。
remove_notifier(&interesting_notifier, my_notify_proc, opaque);
下面是目前pppd实现的notifier列表。
pidchange 由其父进程在pppd已经forked并且子进程在继续pppd’s处理时调用,比如pppd从其控制终端中分离出来的时候。参数就是这个子进程的pid。
phasechange 当pppd从一个阶段操作转移到另一个的时候调用。参数是新阶段的编号。
exitnotify 在pppd退出前调用。参数是pppd退出的状态。(比如exit()的参数)。
sigreceived 在收到信号的时候调用,存在于signal handle里面。参数是signal的编号。
ip_up_notifier 在IPCP发生的时候调用。
ip_down_notifier 在IPCP关闭的时候调用。
auth_up_notifier 在对端认证自身成功的时候调用。
link_down_notifier 在连接关闭的时候调用。
参考资料:http://liaoweiqiang.info/?p=411
百度开放云(BAE)支持绑定域名
[| 2012/06/30 12:12]
前几天百度开放云支持绑定自有域名了,在应用列表里有很明显的绑定域名链接,点击后会提示将要绑定的域名cname到应用主域名上,一般是*.duapp.com,然后点击确定即可绑定成功。
需要注意的一点是,一定要在域名设置生效后再点击绑定按钮,否则由于dns缓存问题,可能需要等待最长24小时才能成功。
修改域名指向的过程也比较简单,进入dns管理页面(一般域名注册商提供),添加cname记录(如该条记录已存在且不是cname记录,则修改为cname记录),目标设为应用主域名,点确认即可。尽量不要在设置成功前访问或nslookup该域名,这样会使域名生效时间延后,无法绑定后立即体验效果。
需要注意的一点是,一定要在域名设置生效后再点击绑定按钮,否则由于dns缓存问题,可能需要等待最长24小时才能成功。
修改域名指向的过程也比较简单,进入dns管理页面(一般域名注册商提供),添加cname记录(如该条记录已存在且不是cname记录,则修改为cname记录),目标设为应用主域名,点确认即可。尽量不要在设置成功前访问或nslookup该域名,这样会使域名生效时间延后,无法绑定后立即体验效果。
cpanel账号分享管理面板:cp-share
[| 2012/06/19 23:57]
买过一些空间,有时候有的朋友要放个小博客,于是就手动开通一些账号和绑定域名给他们,但总是不太方便,很麻烦,于是就有搞一个面板来实现这个功能。
现在使用最多的主机面板就是cpanel,而cpanel恰好有很多api。
于是搞了一个面板,可以将一个普通的cpanel用户账号多人共享,互不干扰。这样方便了主机合租和账号分享。朋友们共享主机时不再需要购买多个主机或reseller账号了。
不过面板的账户间共享资源,所以隔离性上还是不如reseller账号的。所以建议还是朋友或熟悉的人之间使用。
项目地址:http://code.google.com/p/cp-share/
本文地址:http://www.snooda.com/read/307
第一版实现了域名,数据库,ftp账号的管理,功能和界面都比较简陋。不过已经可以满足基本的需求了。后续慢慢完善。
附截图一张:
现在使用最多的主机面板就是cpanel,而cpanel恰好有很多api。
于是搞了一个面板,可以将一个普通的cpanel用户账号多人共享,互不干扰。这样方便了主机合租和账号分享。朋友们共享主机时不再需要购买多个主机或reseller账号了。
不过面板的账户间共享资源,所以隔离性上还是不如reseller账号的。所以建议还是朋友或熟悉的人之间使用。
项目地址:http://code.google.com/p/cp-share/
本文地址:http://www.snooda.com/read/307
第一版实现了域名,数据库,ftp账号的管理,功能和界面都比较简陋。不过已经可以满足基本的需求了。后续慢慢完善。
附截图一张:
lighttpd打印日志出core的问题
[| 2012/06/15 17:17]
有一个问题,是在一个环境上的lighttpd一打日志就出core,很奇怪,看堆栈信息是出在mod_accesslog里,今天看了下,发现原来是试图打印%i导致的。
lighttpd支持在日志中打印请求头中的字段,方法是%{key}i,这样就能在请求头中的key字段打印到日志里,打印referer等东西的时候比较方便。
但如果直接写%i的话,由于没有指定key,导致NULL指针,lighttpd没有校验导致出core。
恰好%I是打印请求长度,大写I还是比较容易误按成小写的。所以有了这个问题
lighttpd支持在日志中打印请求头中的字段,方法是%{key}i,这样就能在请求头中的key字段打印到日志里,打印referer等东西的时候比较方便。
但如果直接写%i的话,由于没有指定key,导致NULL指针,lighttpd没有校验导致出core。
恰好%I是打印请求长度,大写I还是比较容易误按成小写的。所以有了这个问题
redis的性能初测
[| 2012/06/12 12:13]
最近准备用用redis,于是做了下性能测试,在一台虚拟机上跑了下,发现add命令大概1s可以执行3-5w条,使用redis官方推荐的php客户端:Predis ,使用pipe功能批量插入1w条数据的话大概要0.3-0.5s,感觉速度有些慢,仔细研究了下,发现时php库在生成那个1w条的redis命令是非常慢,一个请求要耗费掉1s的cpu时间,实在是性能杀手,考虑后续使用c实现。
lighttpd/nginx与后端fastcgi长连接的问题
[| 2012/06/05 16:45]
今天有同学希望将webserver与后端fcgi换成长连接以提高性能,于是在lighttpd里直接配置proxy-core.max-keep-alive-requests即可,为了做测试,后端只启动了一个fcgi进程。然后发现了奇怪的问题,每隔几个请求就会有一个非常非常慢的连接,需要等待10s左右。
后来决定开strace看看,由于lighttpd配了多进程,不好搞,于是改成单worker,结果问题解决了。
这样问题就豁然开朗了,原来多个请求落到了不同的lighttpd工作进程上,而唯一的一个fcgi进程被处理第一个请求的worker起长连接占用了,结果导致其他lighttpd无法连接到后端。
这是一个启用长连接后比较隐蔽的坑,可能会导致各个webserver的worker进程间负载不一致。需要小心了。
后来决定开strace看看,由于lighttpd配了多进程,不好搞,于是改成单worker,结果问题解决了。
这样问题就豁然开朗了,原来多个请求落到了不同的lighttpd工作进程上,而唯一的一个fcgi进程被处理第一个请求的worker起长连接占用了,结果导致其他lighttpd无法连接到后端。
这是一个启用长连接后比较隐蔽的坑,可能会导致各个webserver的worker进程间负载不一致。需要小心了。
mysql5.5禁用innodb引擎方法
[| 2012/05/30 13:44]
今天发现有一个备份的mysql数据文件夹异常变大,一查发现是多了三个文件:ibdata1 ib_logfile0 ib_logfile1,前者18m,后两个各5m,原来是迁移的时候从mysql5.0迁移到了5.5,而5.5关闭innodb启动不起来,于是我就开启了innodb,由于innodb会默认增加这几个数据文件和日志文件,导致变大。尝试设置数据文件的大小,结果告诉我最小10m,还是太大,于是探索关闭innodb的方法。
看日志发现说由于mysql程序升级了,需要运行mysql_upgrade升级一下mysql里面的数据库,这个比较简单,和mysql命令用法是一样的,运行一遍就ok了。然后发现还是无法关闭innodb,很奇怪,查了下发现原来mysql5.5默认使用innodb了,所以无法简单的关闭掉,还要设置一下默认使用的引擎为myisam才可以,在my.cnf里加上如下两句:
default-storage-engine=MYISAM
innodb=OFF
重启mysql,然后删掉那三个讨厌的文件即可。
看日志发现说由于mysql程序升级了,需要运行mysql_upgrade升级一下mysql里面的数据库,这个比较简单,和mysql命令用法是一样的,运行一遍就ok了。然后发现还是无法关闭innodb,很奇怪,查了下发现原来mysql5.5默认使用innodb了,所以无法简单的关闭掉,还要设置一下默认使用的引擎为myisam才可以,在my.cnf里加上如下两句:
default-storage-engine=MYISAM
innodb=OFF
重启mysql,然后删掉那三个讨厌的文件即可。
malloc、calloc、memset与free等内存操作的速度
[| 2012/05/30 00:55]
一直对内存操作的速度没有数值概念,只泛泛的知道memset影响效率,反复分配释放内存影响效率,具体速度如何,从来没试过,今天试验了一下。
写了个程序,分配一个指针数组,挨个分配内存,然后挨个释放。使用的是一台2核16g内存的虚拟机。gcc版本3.4.2
先是分配了1w个100字节块,发现5ms以内即可完成。
然后扩大到500字节块,速度没什么变化。
然后分配100w个字节块,平均需要270ms左右。
字节块扩大的2k左右,大概5s完成。
扩大到5k左右,大概12s左右。
以上数据均为malloc+memset数据和calloc数据,两者不相上下。
只malloc不memset,大概能缩短20%左右,可见memset对速度还是有一定影响的,不过对于性能要求不是那么严苛的程序,设置一下提高程序稳定性也是值得的。避免某变量忘记初始化出现野值的问题。
写了个程序,分配一个指针数组,挨个分配内存,然后挨个释放。使用的是一台2核16g内存的虚拟机。gcc版本3.4.2
先是分配了1w个100字节块,发现5ms以内即可完成。
然后扩大到500字节块,速度没什么变化。
然后分配100w个字节块,平均需要270ms左右。
字节块扩大的2k左右,大概5s完成。
扩大到5k左右,大概12s左右。
以上数据均为malloc+memset数据和calloc数据,两者不相上下。
只malloc不memset,大概能缩短20%左右,可见memset对速度还是有一定影响的,不过对于性能要求不是那么严苛的程序,设置一下提高程序稳定性也是值得的。避免某变量忘记初始化出现野值的问题。
php和mysql性能优化-开启缓存和慢查询日志
[| 2012/05/27 17:55]
wp的速度慢是出了名的,主要是因为php的执行速度比较慢,页面间require的层数比较多,除了关闭不必要的插件外,在php端做字节码缓存也有不错的效果,开启字节码缓存很简单,相关的php扩展也有很多个,在这里选了apc扩展,直接apt-get install php-apc然后重启php即可。经试验发现wp的执行时间缩短了一半以上,效果相当的不错。
然后发现mysql信息里提示慢查询的数量比较多,可惜之前没有开启记录,在my.cnf里加上如下三条即可。
log_slow_queries = /var/log/mysql/mysql-slow.log
long_query_time = 2
log-queries-not-using-indexes
记录执行时间超过2秒的和没有用到索引的查询日志到mysql-slow.log中,这样就可以对sql语句进行调优了。
然后发现mysql信息里提示慢查询的数量比较多,可惜之前没有开启记录,在my.cnf里加上如下三条即可。
log_slow_queries = /var/log/mysql/mysql-slow.log
long_query_time = 2
log-queries-not-using-indexes
记录执行时间超过2秒的和没有用到索引的查询日志到mysql-slow.log中,这样就可以对sql语句进行调优了。
wordpress在nginx中的rewrite重写规则
[| 2012/05/27 12:02]
对于wordpress开启静态化url时如何在nginx下配置重写rewrite规则,网上一大抄的一个方式是说这样配置:
if (!-e $request_filename)
{
rewrite ^(.+)$ /index.php?q=$1 last;
}
看起来没什么错误,但实际使用中总发现静态文件也被rewrite走了,去nginx官网发现有了新参数:
在location /段里加上一句:try_files $uri $uri/ /index.php?q=$uri&$args;
ok了,轻松愉快。
if (!-e $request_filename)
{
rewrite ^(.+)$ /index.php?q=$1 last;
}
看起来没什么错误,但实际使用中总发现静态文件也被rewrite走了,去nginx官网发现有了新参数:
在location /段里加上一句:try_files $uri $uri/ /index.php?q=$uri&$args;
ok了,轻松愉快。