整理目录

This commit is contained in:
2024-03-22 17:42:41 +08:00
parent 5a4efad893
commit fab9914f39
158 changed files with 158 additions and 84 deletions

View File

@ -0,0 +1,234 @@
---
title: swoole学习笔记(一)-swoole环境配置(树莓派安装)
---
> 打算开始学习 swoole 了(原来好像弄过,不过那次只是接触了一下,并未太过深入,这次重新来过 (° ー °〃)
> swoole 虽然能在 windows 上搭建,不过我觉得意义不大....需要安装 CygWin 这和在 linux 上有什么区别呢 ┑( ̄ Д  ̄)┍,刚好现在手上有一台空闲的树莓派 zero,试试在上面搭建
## 编译 php
> 之所以要编译安装是因为在 swoole 编译的时候需要用到 phpize,apt-get 安装的时候没发现有
现在这个上面什么东西都没有,先安装 php,我选最新的 php7.2.6,zero 配置是真的好低....解压和编译 cpu 都 100%了很慢....趁这个时间去干点别的吧
下载,解压源码,安装依赖
强烈建议使用国内镜像....不然可能一些依赖 lib 按照失败,导致编译错误
```
sudo -i
wget http://hk1.php.net/get/php-7.2.6.tar.gz/from/this/mirror
mv mirro php.tar.gz
tar -zxvf php.tar.gz
apt-get update
apt-get install libxml2* libbz2-dev libjpeg-dev libmcrypt-dev libssl-dev openssl libxslt1-dev libxslt1.1 libcurl4-gnutls-dev libpq-dev build-essential git make
```
编译配置,复制的网上的 lnmp 编译- -...去掉了和 Nginx 有关的编译项,我只需要编译出 php 就行,不需要 Nginx 那些环境,当然如果你之前已经有了这些,这一部分就可以跳过了
```
cd php-7.2.6
./configure \
--prefix=/usr/local/php \
--exec-prefix=/usr/local/php \
--bindir=/usr/local/php/bin \
--sbindir=/usr/local/php/sbin \
--includedir=/usr/local/php/include \
--libdir=/usr/local/php/lib/php \
--mandir=/usr/local/php/php/man \
--with-config-file-path=/usr/local/php/etc \
--with-mysql-sock=/var/lib/mysql/mysql.sock \
--with-mcrypt=/usr/include \
--with-mhash \
--with-openssl \
--with-mysql=shared,mysqlnd \
--with-mysqli=shared,mysqlnd \
--with-pdo-mysql=shared,mysqlnd \
--with-gd \
--with-iconv \
--with-zlib \
--enable-zip \
--enable-inline-optimization \
--disable-debug \
--disable-rpath \
--enable-shared \
--enable-xml \
--enable-bcmath \
--enable-shmop \
--enable-sysvsem \
--enable-mbregex \
--enable-mbstring \
--enable-ftp \
--enable-gd-native-ttf \
--enable-pcntl \
--enable-sockets \
--with-xmlrpc \
--enable-soap \
--without-pear \
--with-gettext \
--enable-session \
--with-curl \
--with-freetype-dir \
--enable-opcache \
--enable-redis \
--enable-fpm \
--enable-fastcgi \
--disable-fileinfo
```
![](img/01-swoole%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180619143040-300x207.png)
CPU 100% 有点怕,树莓派 zero 性能确实是弱...编译好慢....解决了编译配置的问题后就开始编译,我是真的睡了一觉(第二天)才起来 make install
```
make && make install
```
设置一下 php.ini 文件
```
cp php.ini-production /usr/local/php/etc/php.ini
//我输入php -v之后发现没反应,但是php确实是成功了,在/usr/local/php/bin里面./php -v也有反应,想到可能是没有链接到/usr/bin 目录里,用ln命令链接一下
ln -s /usr/local/php/bin/php /usr/bin/php
//链接phpize
ln -s /usr/local/php/bin/phpize /usr/bin/phpize
```
成功之后,老套路
```
php -v
```
![](img/01-swoole%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180620085032-300x87.png)
成功,终于可以下一步了,进入 swoole 编译配置
## swoole 编译
从 git 上下载源码[https://github.com/swoole/swoole-src/releases](https://github.com/swoole/swoole-src/releases),开始编译
```
wget https://github.com/swoole/swoole-src/archive/v4.0.0.zip
unzip v4.0.0.zip
mv swoole-src-4.0.0/ swoole
cd swoole
phpize
```
这里我提示了一个错误...
Cannot find autoconf. Please check your autoconf installation and the
$PHP_AUTOCONF environment variable. Then, rerun this script.
解决办法:
```
apt-get install m4 autoconf
```
phpize 成功之后继续运行编译配置和开始编译(但愿这次不用那么久了...)
开启一些需要的:[编译配置项](https://wiki.swoole.com/wiki/page/437.html)
```
./configure --with-php-config=/usr/local/php/bin/php-config --enable-sockets --enable-swoole-debug --enable-openssl --enable-mysqlnd --enable-coroutine
make && make install
```
![](img/01-swoole%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180620095954-300x207.png)
然后需要在 php.ini 中配置下
```
vi /usr/local/php/etc/php.ini
//添加
extension=swoole.so
```
然后`php -m`
![](img/01-swoole%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180620113804-300x207.png)
有这一项就代表成啦~
## 测试
> 安装编译都完成之后,当然来试试是不是真的能用了
复制官方的例子,嘿嘿嘿~
```php
<?php
//创建websocket服务器对象监听0.0.0.0:9502端口
$ws = new swoole_websocket_server("0.0.0.0", 9502);
//监听WebSocket连接打开事件
$ws->on('open', function ($ws, $request) {
var_dump($request->fd, $request->get, $request->server);
$ws->push($request->fd, "hello, welcome\n");
});
//监听WebSocket消息事件
$ws->on('message', function ($ws, $frame) {
echo "Message: {$frame->data}\n";
$ws->push($frame->fd, "server: {$frame->data}");
});
//监听WebSocket连接关闭事件
$ws->on('close', function ($ws, $fd) {
echo "client-{$fd} is closed\n";
});
$ws->start();
```
`php swoole.php`
web:
```html
<script>
var ws = new WebSocket("ws://localhost:9502");
ws.onopen = function () {
ws.send("send data");
};
ws.onmessage = function (evt) {
var received_msg = evt.data;
console.log(received_msg);
};
ws.onclose = function () {
console.log("连接关闭");
};
</script>
```
成了~
![](img/01-swoole%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180620114807-300x207.png)
---
## 问题解决
### redis 扩展安装
> 弄完后...并没有用,然后重新编译一次成了....= =,不过还是记着
在 swoole 编译完成后,又遇到了一个问题....
```
php: symbol lookup error: /usr/local/php/lib/php/extensions/no-debug-non-zts-20170718/swoole.so: undefined symbol: swoole_redis_coro_init
```
查资料后发现可能是需要给 php 安装 redis 扩展....[redis 源码下载](https://pecl.php.net/package/redis)
```
wget https://pecl.php.net/get/redis-4.0.2.tgz
tar -zxvf redis-4.0.2.tgz
cd redis-4.0.2
phpize
./configure --with-php-config=/usr/local/php/bin/php-config
make && make install
```
然后在 php.ini 中加上`extension = redis.so`就好了,注意这个配置一定要放在 swoole 的配置的前面,因为这些扩展都是按照顺序加载的
---
**历时一天,终于搞定了 编译真的是漫长的过程=\_=**

View File

@ -0,0 +1,134 @@
---
title: swoole学习笔记(二)-开发环境配置
---
> swoole 可以跑了,然后开始弄开发环境
> 后面的 xdebug,在协程中 tm 不能用!...有挺多问题的,不推荐配置了,写 log 吧
## 代码自动上传
我的开发环境一般是 windows,phpstorm,然而我的树莓派和 swoole 的环境又不在一起,这时候就可以用 phpstorm 的一个功能,可以自动同步代码
File->setting->Deployment
添加一个,选择 sftp,然后输入 pi 的信息
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180620131748-300x204.png)
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180620132024-1-300x204.png)
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180620132024-2-300x204.png)
添加好服务器后,再设置 options
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180620133255-300x204.png)
自动上传就配置好了,当你保存的时候就会自动上传到服务器
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180620133409-300x96.png)
## 代码自动提示
> 对于我这种百度型程序员,自动提示是必不可少的
### swoole-ide-helper
https://github.com/eaglewu/swoole-ide-helper
这是一个**Swoole 在 IDE 下自动识别类、函数、宏,自动补全函数名**
#### 安装方法
##### phpstrom
将项目 clone 或者直接下载下来,解压
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180622124636-300x216.png)
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180622124713-300x227.png)
##### composer
要是你的项目中使用了 composer,你可以直接
```bash
composer require --dev "eaglewu/swoole-ide-helper:dev-master"
```
## 远程调试配置
> 虽然可以通过 echo 之类的来调试,但是断点调试也是必不可少的
### XDebug 安装
[https://github.com/xdebug/xdebug](https://github.com/xdebug/xdebug)
```bash
wget https://github.com/xdebug/xdebug/archive/2.6.0.tar.gz
tar -zxvf 2.6.0.tar.gz
cd xdebug-2.6.0/
./configure --with-php-config=/usr/local/php/bin/php-config
make && make install
```
然后在 php.ini 中配置
```toml
zend_extension=xdebug.so
[xdebug]
xdebug.remote_enable=true
xdebug.remote_host=127.0.0.1
xdebug.remote_port=9000
xdebug.remote_handler=dbgp
```
保存之后`php -m`,出现 xdebug 就算安装成功了
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180622161650-300x203.png)
### phpstorm 配置
setting 中 php 配置,设置一下远程 cli
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180622161818-300x204.png)
在之前已经配置了远程自动同步的话,这里是会有服务器可以选择的
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180622162052-300x107.png)
点击 OK 之后,phpstorm 会自动获取远程的 php 信息,如下
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180622162146-300x246.png)
之后选择我们刚刚添加的(我重命名了 pi_zero php7.2)
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180622162303-300x204.png)
然后下方的 path mappings,也需要设置(我这里默认设置好了),对本地与远程的目录进行映射
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180622162622-300x108.png)
xdebug 的端口 9000,一开始就是这样的,如果你改了的话,这里注意也改一下
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180622162413-300x107.png)
这些配置好之后就可以开始配置调试选项了
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180622162413-1-300x203.png)
配置启动文件
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180622162829-300x254.png)
之后就可以开始调试了,在我们的源码下下断点,然后点击调试按钮,成功~!
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180622163913-300x219.png)
当收到信息/连接的时候:
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/TIM%E6%88%AA%E5%9B%BE20180622164021-300x238.png)
非常舒服,嘿嘿嘿
![](img/02-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.assets/1LE84QO9K@A1N9.jpg)

View File

@ -0,0 +1,445 @@
---
title: swoole学习笔记(三)-UDP radius协议实现
---
> 又开新坑-swoole 作为 radius 服务器,huanlphp 写业务 [php-radius](https://github.com/CodFrm/php-radius "php-radius") 主要还是为了学习 swoole 和实验我的框架,所以这里记录一下 radius 协议的结构和使用(原来用 python 实现过一次,容易崩溃还写得垃圾),文章中只写了 auth,没有写 account 的记录,openvpn 需要 auth 和 account 才能实现连接成功,可以看我完整的实现[python 实现](https://github.com/CodFrm/stuShare/blob/master/radius/main.py)
## 协议&工具
- [rfc2865 radius 身份认证](https://tools.ietf.org/html/rfc2865)
- [rfc2866 radius 计费](https://tools.ietf.org/html/rfc2866)
测试工具我用的 NTRadPing:[http://www.winsite.com/internet/server-tools/ntradping/](http://www.winsite.com/internet/server-tools/ntradping/)
![](img/03-UDP-radius%E5%8D%8F%E8%AE%AE%E5%AE%9E%E7%8E%B0.assets/TIM%E6%88%AA%E5%9B%BE20180622220548-300x188.png)
## 结构
### pack fromat
> 关于计费和验证的格式都是一样的
验证:[https://tools.ietf.org/html/rfc2865#page-13](https://tools.ietf.org/html/rfc2865#page-13)
计费:[https://tools.ietf.org/html/rfc2866#page-5](https://tools.ietf.org/html/rfc2866#page-5)
```
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Code | Identifier | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Authenticator |
| |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Attributes ...
+-+-+-+-+-+-+-+-+-+-+-+-+-
```
看得有些隐晦,我将它转换成 c 的 struct 结构吧
char 1 个字节,8bit short 两个字节,16bit
```c
struct radius_format{
char code;//包类型,请求类型根据这个判断
char identifier;//鉴定码,服务器和客户端这个值需要一样
short lenght; //本次数据包的总长度(20-4096)
char authenticator[16];//数据hash,用来校验数据是否正确,一个数据的md5码
};
```
在响应的时候 authenticator 的计算方式为:MD5(Code+ID+Length+RequestAuth+Attributes+**Secret**) 在计算中的**RequestAuth**为请求的时候的 authenticator,**Secret**记笔记,是双方的一个 key
后面的 Attributes 长度不一定(length-20),里面包含着 **用户/NAS** 的一些信息,比如账号,密码,ip,之类的,格式看后面,这里也可以用来扩展自己的协议,带上自己的一些奇奇怪怪的值- -
#### code 意义
主要就是前面 5 个了
```
RADIUS Codes (decimal) are assigned as follows:
1 Access-Request //验证请求,一般由客户端使用
2 Access-Accept //验证通过,一般由服务端处理返回
3 Access-Reject //验证拒绝,一般由服务端处理返回
4 Accounting-Request //计费请求,客户端发起
5 Accounting-Response //计费返回,服务器发起
11 Access-Challeng //服务端主动请求再次验证,客户端要求必须返回
12 Status-Server (experimental)//服务器状态???没看到说明
13 Status-Client (experimental)//同上
255 Reserved//保留
```
### Attributes
[https://tools.ietf.org/html/rfc2865#page-22](https://tools.ietf.org/html/rfc2865#page-22)
```
0 1 2
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
| Type | Length | Value ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
```
```c
struct radius_attr{
char type;//类型,有不少...
char length;//type+length+value的长度
}
```
value 是不固定的(长度:1-253),根据 length 来判定(就是 length-2 咯)
#### type
> 偷懒了,不写意义了
验证属性:[https://tools.ietf.org/html/rfc2865#page-24](https://tools.ietf.org/html/rfc2865#page-24)
计费属性:[https://tools.ietf.org/html/rfc2866#page-11](https://tools.ietf.org/html/rfc2866#page-11)
```
This specification concerns the following values:
1 User-Name //登录用户名
2 User-Password //登录密码
3 CHAP-Password
4 NAS-IP-Address //NAS的IP地址
5 NAS-Port //NAS的端口
6 Service-Type
7 Framed-Protocol
8 Framed-IP-Address
9 Framed-IP-Netmask
10 Framed-Routing
11 Filter-Id
12 Framed-MTU
13 Framed-Compression
14 Login-IP-Host
15 Login-Service
16 Login-TCP-Port
17 (unassigned)
18 Reply-Message
19 Callback-Number
20 Callback-Id
21 (unassigned)
22 Framed-Route
23 Framed-IPX-Network
24 State
25 Class
26 Vendor-Specific
27 Session-Timeout
28 Idle-Timeout
29 Termination-Action
30 Called-Station-Id
31 Calling-Station-Id
32 NAS-Identifier
33 Proxy-State
34 Login-LAT-Service
35 Login-LAT-Node
36 Login-LAT-Group
37 Framed-AppleTalk-Link
38 Framed-AppleTalk-Network
39 Framed-AppleTalk-Zone
40-59 (reserved for accounting)//计费的,包含流量信息和连接时间什么的
60 CHAP-Challenge
61 NAS-Port-Type
62 Port-Limit
63 Login-LAT-Port
```
## 实现
> 利用 NTR 工具发送测试数据,使用 php-swoole 来接收和处理
### 解包
php 中使用 unpack 来解包,还是很方便的
```php
public function onPacket(swoole_server $serv, string $data, array $clientInfo) {
//解包
$radius = unpack('ccode/cidentifier/nlength/a16authenticator', $data);
print_r($radius);
echo bin2hex($radius['authenticator']) . "\n";
echo bin2hex($data);
}
```
![](img/03-UDP-radius%E5%8D%8F%E8%AE%AE%E5%AE%9E%E7%8E%B0.assets/TIM%E6%88%AA%E5%9B%BE20180623221807-300x101.png)
后面还需要处理 attr 属性,这是我解码的代码
```php
<?php
class radius {
/**
* @var swoole_server
*/
public $server;
/**
* 密钥
* @var string
*/
public $secret_key;
public static $ATTR_TYPE = [1 => 'User-Name',
2 => 'User-Password', 3 => 'CHAP-Password', 4 => 'NAS-IP-Address', 5 => 'NAS-Port', 6 => 'Service-Type',
7 => 'Framed-Protocol', 8 => 'Framed-IP-Address', 9 => 'Framed-IP-Netmask', 10 => 'Framed-Routing',
11 => 'Filter-Id', 12 => 'Framed-MTU', 13 => 'Framed-Compression', 14 => 'Login-IP-Host', 15 => 'Login-Service',
16 => 'Login-TCP-Port', 17 => '(unassigned)', 18 => 'Reply-Message', 19 => 'Callback-Number',
20 => 'Callback-Id', 21 => '(unassigned)', 22 => 'Framed-Route', 23 => 'Framed-IPX-Network', 24 => 'State',
25 => 'Class', 26 => 'Vendor-Specific', 27 => 'Session-Timeout', 28 => 'Idle-Timeout', 29 => 'Termination-Action',
30 => 'Called-Station-Id', 31 => 'Calling-Station-Id', 32 => 'NAS-Identifier', 33 => 'Proxy-State',
34 => 'Login-LAT-Service', 35 => 'Login-LAT-Node', 36 => 'Login-LAT-Group', 37 => 'Framed-AppleTalk-Link',
38 => 'Framed-AppleTalk-Network', 39 => 'Framed-AppleTalk-Zone',
60 => 'CHAP-Challenge', 61 => 'NAS-Port-Type', 62 => 'Port-Limit', 63 => 'Login-LAT-Port'];
/**
* 收到udp数据包
* @param swoole_server $serv
* @param string $data
* @param array $clientInfo
*/
public function onPacket(swoole_server $serv, string $data, array $clientInfo) {
$attr = [];
$struct = $this->unpack($data, $attr);
print_r($struct);
print_r($attr);
}
/**
* 解码radius数据包
* @param string $bin
* @param array $attr
* @return array|bool
*/
public function unpack(string $bin, array &$attr): array {
//一个正常的radius封包长度是绝对大于等于20的
if (strlen($bin) < 20) {
return [];
}
//解包
$radius = unpack('ccode/cidentifier/nlength/a16authenticator', $bin);
//获取后面的属性长度,并且对数据包进行验证
if (strlen($bin) != $radius['length']) {
return [];
}
$attr_len = $radius['length'] - 20;
//处理得到后面的Attributes,并且解包
$attr = $this->unpack_attr(substr($bin, 20, $attr_len));
if ($attr == []) {
return [];
}
return $radius;
}
/**
* 处理Attributes
* @param string $bin
* @return array
*/
public function unpack_attr(string $bin): array {
$attr = [];
$offset = 0;
$len = strlen($bin);
while ($offset < $len) {
$attr_type = ord($bin[$offset]);//属性类型
$attr_len = ord($bin[$offset + 1]);//属性长度
$attr[static::$ATTR_TYPE[$attr_type]] = substr($bin, $offset + 2, $attr_len - 2);//属性值
//跳到下一个
$offset += $attr_len;
}
//判断offset和$len是否相等,不相等认为无效,抛弃这个封包
if ($offset != $len) {
return [];
}
return $attr;
}
/**
* 运行服务器
* @param int $authPort
* @param int $accountPort
*/
public function run(string $secret_key, int $authPort = 1812, int $accountPort = 1813) {
$server = new swoole_server("0.0.0.0", $authPort, SWOOLE_PROCESS, SWOOLE_SOCK_UDP);
$server->on('Packet', array($this, 'onPacket'));
$server->start();
$this->server = $server;
$this->secret_key = $secret_key;
}
}
$server = new radius();
$server->run('test123');
```
![](img/03-UDP-radius%E5%8D%8F%E8%AE%AE%E5%AE%9E%E7%8E%B0.assets/TIM%E6%88%AA%E5%9B%BE20180624151246-300x156.png)
### 密码验证
> 密码有两种的加密方式 `User-Password(PAP加密)`和`CHAP-Password(CHAP加密)`
### User-Password
[https://tools.ietf.org/html/rfc2865#page-27](https://tools.ietf.org/html/rfc2865#page-27)
> 在传输时,密码是隐藏的。首先在密码的末尾用空值填充 16 个字节的倍数。单向 MD5 哈希是通过由共享密钥组成的八位字节流来计算的,后面是请求身份验证器。该值与密码的前 16 个八位位组段进行异或并放置在用户密码属性的字符串字段的前 16 个八位位组中。
>
> 如果密码长度超过 16 个字符,则第二个单向 MD5 散列值将通过由共享密钥组成的八位字节流计算,然后是第一个异或结果。该散列与密码的第二个 16 位八位字节进行异或,并置于用户密码属性的字符串字段的第二个 16 位字节中。
>
> 调用**共享密钥 S**和伪随机 128 位请求认证器**RA** 。 将密码分成 16 个八位字节块 p1p2 等。 最后一个填充为零,最终为 16 个八位字节的边界。 调用密文块 c1c2我们需要中间值 b1b2 等。
```
b1 = MD5(S + RA) c(1) = p1 xor b1
b2 = MD5(S + c(1)) c(2) = p2 xor b2
. .
. .
. .
bi = MD5(S + c(i-1)) c(i) = pi xor bi
The String will contain c(1)+c(2)+...+c(i) where + denotes
concatenation.
```
上面其实是 google 翻译来的- -...把我理解的说一下
当密码位数小于 16 位的时候,先计算出**md5(密钥(secret_key)+Authenticator)**的 md5,然后再与我们接收到的**User-Password**进行位运算,遇到`\x0`结尾字符串结束
如果超过了 16 位而且还没有遇到`\x0`那么继续和我们的**User-Password**进行位运算,但是这回的用于位运算的 md5 为**md5(密钥(secret_key)+前 16 个字符)**,这里有个坑....文档说的是加密的过程,我们需要实现的是解密,所以前 16 个字符串不是我们解密后的 16 个字符,而是加密后的 16 个字符,=\_=害我拿原文(解密后的)一直在算,不对
实现代码:
```php
public function decode_user_passwd($bin, $Authenticator) {
$passwd = '';
$S = $this->secret_key;
$len = strlen($bin);
//b1 = MD5(S + RA)
$hash_b = md5($S . $Authenticator, true);
for ($offset = 0; $offset < $len; $offset += 16) {
//每次拿16字符进行解码
for ($i = 0; $i < 16; $i++) {
$pi = ord($bin[$offset + $i]);
$bi = ord($hash_b[$i]);
//c(i) = pi xor bi
$chr = chr($pi ^ $bi);
if ($chr == "\x0") {
//文本标志\x0结尾
return $passwd;
}
$passwd .= $chr;
}
//判断一下是不是已经结束了,然后返回
if ($len == $offset + 16) {
return $passwd;
}
//bi = MD5(S + c(i-1))
$hash_b = md5($S . substr($bin, $offset, 16), true);
}
//都循环完了,还没看见结束,返回空
return '';
}
```
### CHAP-Password
> 从上面的代码来看`User-Password`是不安全的,只要知道了**secret_key**,密码明文完全可以解密出来,CHAP 密码原文是无法解密出来的
>
> RADIUS 服务器根据用户名查找密码,使用 MD5 在 CHAP ID 八位位组,密码和 CHAP 质询(如果存在 CHAP-Challenge 属性,否则从请求身份验证器)加密质询,以及 将该结果与 CHAP 密码进行比较。 如果它们匹配,则服务器发回一个访问接受,否则它将发回一个访问拒绝。
>
> 此属性指示由 PPP 挑战握手认证协议CHAP用户响应挑战而提供的响应值。 它仅用于 Access-Request 数据包。
>
> 如果存在于数据包中,则在 CHAP-Challenge 属性60中找到 CHAP 质询值,否则在请求验证器字段中找到。
[https://tools.ietf.org/html/rfc2865#page-8](https://tools.ietf.org/html/rfc2865#page-8)
[https://tools.ietf.org/html/rfc2865#page-28](https://tools.ietf.org/html/rfc2865#page-28)
CHAP-Password 的结构是这样的,中间还有一个 CHAP Ident,后面 string 是一个 16 个字节的字符串,一听就觉得是 md5
```
A summary of the CHAP-Password Attribute format is shown below. The
fields are transmitted from left to right.
0 1 2
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
| Type | Length | CHAP Ident | String ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
```
感觉 CHAP 比 PAP 好写多了,直接验证**md5(chapid+passwd+CHAP-Challenge(没有则用 Authenticator))**和 String 是否相等就好了
```php
//调用
echo "isPass:" . (
$this->verify_chap_passwd($attr['CHAP-Password'],
'qwe123',
$attr['CHAP-Challenge'] ?? $struct['authenticator']
) ? 'true' : 'false');
public function verify_chap_passwd(string $bin, string $pwd, string $chap): bool {
if (strlen($bin) != 17) return false;
$chapid = $bin[0];
$string = substr($bin, 1);
return md5($chapid . $pwd . $chap, true) == $string;
}
```
### 封包
> 处理完后,总还得人家一个回信吧
[https://tools.ietf.org/html/rfc2865#page-19](https://tools.ietf.org/html/rfc2865#page-19)
都是一样的,只是吧解包变成封包一个逆向操作,值得注意的是**Authenticator**,在接收的时候这个是随机的,发送的时候,我们需要带进去计算**MD5(Code+ID+Length+RequestAuth+Attributes+Secret)**
```php
/**
* 封包
* @param int $code
* @param int $identifier
* @param string $reqAuthenticator
* @param array $attr
* @return string
*/
public function pack(int $code, int $identifier, string $reqAuthenticator, array $attr = []): string {
$attr_bin = '';
foreach ($attr as $key => $value) {
$attr_bin .= $this->pack_attr($key, $value);
}
$len = 20 + strlen($attr_bin);
//MD5(Code+ID+Length+RequestAuth+Attributes+Secret)
$send = pack('ccna16',
$code, $identifier, $len,
md5(chr($code) . chr($identifier) . pack('n', $len) .
$reqAuthenticator . $attr_bin . $this->secret_key, true)
) . $attr_bin;
//这里实际使用的时候有错误,因为NTR工具没有校验Response Authenticator...现在已经修改了
return $send;
}
/**
* 封包属性
* @param $code
* @param string $data
* @return string
*/
public function pack_attr($code, string $data): string {
return pack('cc', $code, 2 + strlen($data)) . $data;
}
```
![](img/03-UDP-radius%E5%8D%8F%E8%AE%AE%E5%AE%9E%E7%8E%B0.assets/TIM%E6%88%AA%E5%9B%BE20180624194116-300x147.png)
> 完整代码中的封包有错误,请看上面的注释,在代码中修改了
完成,完整代码: [start.php](./img/03-UDP-radius协议实现.assets/start.zip)

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB