顯示具有 client 標籤的文章。 顯示所有文章
顯示具有 client 標籤的文章。 顯示所有文章

2022年8月25日 星期四

C 開發筆記 - 使用 libwebsockets 製作簡易的 WebSocket Client

已經有用過 Golang 開發小工具,以及其他現成工具如 websocat 等,做 WebSocket Server 的測試。這次回過頭來再仔細看看 libwebsockets 這套,寫一點 C 語言。

程式架構不會太難,比較難的反而是一些小東西,像是跟 ws.ptt.cc 連線時,PTT 會做 Request Header(Origin欄位) 檢查,當作簡單的管控,然而 libwebsockets 則是要設定  LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN 才能不加料 XD

#include <include/libwebsockets/lws-context-vhost.h>

For backwards-compatibility reasons, by default lws prepends "http://" to the origin you give in the client connection info struct. If you give this flag when you create the context, only the string you give in the client connect info for .origin (if any) will be used directly.

大概在這種地方耗掉半小時以上。另外,還有做 wss 連線時,也須設定 LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT 等等,以及如果想要略過 wss 的 SSL 憑證檢查,有滿多設定項目可以試試:LCCSCF_USE_SSL | LCCSCF_ALLOW_SELFSIGNED | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK | LCCSCF_ALLOW_INSECURE;

其他則是依照 libwebsockets 的架構,要設計連線時的 callback 處理機制,算是體驗了一下。未來就多一個 C 語言工具可以做 WebSocket Server 連線測試。


常見的錯誤訊息:
  • E: SSL_new failed: error:00000063:lib(0)::reason(99)
  • W: [wsicli|0|WS/h1/default/ws.ptt.cc]: lws_client_ws_upgrade: got bad HTTP response '403'
  • E: lws_ssl_client_bio_create: Unable to get hostname
執行 logs:

% make
gcc -g -Wall main.c `pkg-config libwebsockets --libs --cflags`

% ./a.out   
Usage> ./a.out url
./a.out ws://localhost:8000/
./a.out wss://ws.ptt.cc/bbs app://cmd
./a.out wss://ws.ptt.cc/bbs https://term.ptt.cc

% ./a.out wss://ws.ptt.cc/bbs https://term.ptt.cc
[2022/08/25 20:45:29:8530] N: lws_create_context: LWS: 4.3.2-no_hash, NET CLI SRV H1 H2 WS ConMon IPV6-on
[2022/08/25 20:45:29:8539] N: __lws_lc_tag:  ++ [wsi|0|pipe] (1)
[2022/08/25 20:45:29:8964] N: __lws_lc_tag:  ++ [vh|0|default||-1] (1)
wsCallback - LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS
[INFO] libwebsockets version: 4.3.2
[INFO] lws_parse_uri - Server: ws.ptt.cc:443
[INFO] lws_parse_uri - URLScheme: wss
[INFO] lws_parse_uri - URLPath: bbs
[INFO] clientConnectInfo.addres: ws.ptt.cc
[INFO] clientConnectInfo.port: 443
[INFO] clientConnectInfo.path: /bbs
[INFO] clientConnectInfo.host: ws.ptt.cc
[INFO] clientConnectInfo.origin: https://term.ptt.cc
[INFO] clientConnectInfo.ssl_connection: 109
[2022/08/25 20:45:29:9789] N: __lws_lc_tag:  ++ [wsicli|0|WS/h1/default/ws.ptt.cc] (1)
call lws_client_connect_via_info done, webSocket is NULL(NO)
noop
[2022/08/25 20:45:30:0229] N: lws_gate_accepts: on = 0
[2022/08/25 20:45:30:0575] N: lws_gate_accepts: on = 0
wsCallback - LWS_CALLBACK_CLIENT_ESTABLISHED
wsCallback - LWS_CALLBACK_CLIENT_WRITEABLE
wsCallback - LWS_CALLBACK_CLIENT_RECEIVE
RECEIVER: HTTP/1.1 200 OK

     ??      PTT                ?P   ??        ?P  ??      ???i?i?i?i?i?i??
             140.112.172.11 ?P     ???i???i??            ???i?i?i?i?i
  ?z?w?{     ?????~?{        ???d?i?i???i??   ?P   ???i?i?i?i?i??  ?P
  ?x?V?|?{   ptt.cc            ???i?i?i?i?i???i??    ???i?i?i?i?i
  ?x?V  ?x                   ???i?i?i???i?i?i????  ???i?i?i?i?i??  ??  ?P
?w?}    ?x?z?w?w?{  ?P       ????      ?i?i?i?i?????i?i?i?i    ?P
        ?|?t  ?V?x      ?P           ???i?i?i?i???i?i?i??            ?P
                ?x?z?w?w?w?{         ?i?i?i?i?i?h?h?g?g?f?f?e?e?d?c?b
            ?z?w?r?}?V?V  ?| ???i?i?i?i?i?h?h?g?g?f?f?e?e?d?d?c?c?b
            ?x?V?V           ???i?i
wsCallback - LWS_CALLBACK_CLIENT_RECEIVE
RECEIVER:       ?i?h?h?g?g?f?f?e?e?d?c?b

wsCallback - LWS_CALLBACK_CLIENT_RECEIVE
RECEIVER:            ?f?d?d?e?g?f?e         ?e?d?g??     ???âg?d?e       ????            
               ?g?h  ?h         ?e?d         ?b      ???c?g?d                   
?п?J?N???A?ΥH guest ???[?A?ΥH new ???U:               
^C   ?b?c?e?f?e?g?h?g?f?d    ?n  ??   ??  ???n?l?? ??  ???? ?l?k  ?h            

2018年4月3日 星期二

[PHP] 微信開放平台開發筆記 - WeChat Web 應用與 OAuth2 client

今年才感到 Wechat app 崛起,得知用戶已破10億,甚至微信支付交易筆數早已超車支付寶1.5倍,已乃覺三十里 Orz 想要用境外身份申請,才發現很多都辦不成,像是境外要用公司身份才行,甚至騰訊公開平台的管理者都還得綁有中國銀行卡(金融卡)才搞得定 <囧> 多虧對岸同事的幫忙,可以小試身手一下。

騰訊公開平台的 OAuth2 文件: https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN

需要的留意的是騰訊公開平台的 Web 應用只允許填入一個 hostname/domain 作為 callback handler ,所以,也有人想得很快,透過在統一地方處理完畢後,在拋去其他地方,如:https://github.com/HADB/GetWeixinCode

在此利用一樣的架構,不過是在 backend 處理 XD 主要是服務常分成 production/alpha/dev site,就把它統一建構在 Production site ,但 Dev site 要登入時,跳去 production site 處理後,再把資料回拋到 dev site(當然也可以申請多個 WeChat Web 應用,每個 site 擁有自己的 Web app)

整個過程都算順利,唯一不太順的地方是 OAuth state 這個參數的使用,原先埋了要跳去其他地方的 callback url 或其他資訊,但測了幾輪發現 state 內若有一些敏感資訊(&)時,回來都會出錯,於是就多加個 base64_encode 來解了(也可以考慮把必要的資料儲存在 cookie)

發動處:

<?php

$init_url = 'https://open.weixin.qq.com/connect/qrconnect?'. http_build_query( array(
        'scope' => 'snsapi_login,snsapi_userinfo',
        'response_type' => 'code',
        'redirect_uri' => 'https://auth.my-domain/wechat/callback',
        'state' => base64_encode( http_build_query(array(
                'give_me_code' => 1,
                'callback' => 'https://service.my-domain/wechat/handle-code',
        )) ),
        'appid' => $weixin_app_id,
));


負責處理 WeChat OAuth2 回傳資料(code):https://auth.my-domain/wechat/callback

<?php

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.weixin.qq.com/sns/oauth2/access_token');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query( array(
'appid' => $weixin_app_id,
'secret' => $weixin_app_secret,
'code' => $code,
'grant_type' => 'authorization_code',
)));
$ret = @json_decode(curl_exec($ch), true);
             
$state = $_GET['state'];
$state_param = array();
if (!empty($state)) {
$state = @base64_decode($state);
parse_str($state, $state_param);
}

isset($state_param['callback']) && (
preg_match('#http[s]://(.*?)\.my-domain/#i', $state_param['callback'], $match)
) ) {
if (!empty($_GET['code'])) {
if (isset($state_param['give_me_code']) && $state_param['give_me_code'] == '1')
header('Location: '.$state_param['callback'].'?'.http_build_query(array('code' => $_GET['code'])) );
// ...
} else {
header('Location: '.$state_param['callback'].'?'.http_build_query(array('error' => 'no code')) );
}
return;
}


處理 code:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.weixin.qq.com/sns/oauth2/access_token');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query( array(
'appid' => $weixin_app_id,
'secret' => $weixin_app_secret,
'code' => $ret_code,
'grant_type' => 'authorization_code',
)));

$ret = @json_decode(curl_exec($ch), true);

if (isset($ret['access_token']) && isset($ret['refresh_token']) && isset($ret['openid']) && isset($ret['unionid'])) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.weixin.qq.com/sns/userinfo');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query( array(
'access_token' => $ret['access_token'],
'openid' => $ret['openid'],
)));
$profile_ret = @json_decode(curl_exec($ch), true);
}

// $profile_ret['nickname']
// $profile_ret['headimgurl']


原先在 callback handler 上,順便做完 code 檢驗,但後來覺得這樣要傳出去的資料太多(且感到不安),所以還是統一把 code 拋出去,缺點是其他服務就必須都紀錄 WeChat Web 應用的資料(app_id/secret_key)等等,但可以避免受到 DNS 攻擊而把用戶的 access_token 給了出去。

2017年6月14日 星期三

AWS 筆記 - AWS ELB 與 Nginx 之 DOS 惡意攻擊者的處理方式

這個緣由是這樣的,有一台 Server 被狂打,但架構是:

Remote client <-> AWS ELB <-> Web server (Nginx) <-> Application

當有用戶非常好心地發大量 request 來檢查機器漏洞,若 Nginx 預設都沒多做設定,那 access.log 都會只記錄到 AWS ELB IP ,也就是不能把 access.log 紀錄的 IP 拿來 ban ,會變成 ban 掉 ELB。

而 AWS 提供的 Security group 預設是從 Allow 的角度,若要特意 ban 掉某個 IP 會有點難搞,例如要改寫防火牆規則,因此,最後就改成從實體 server 去阻擋遠端 client request 了。

步驟一:先讓 Nginx 可以記錄到真實的 remote ip

/etc/nginx/nginx.conf

http {
    # ...
    real_ip_header X-Forwarded-For;
    set_real_ip_from 0.0.0.0/0;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    # ...
}


如此一來,access.log 的 remote_addr 就可以不是 AWS ELB IP 了。

步驟二:用 Nginx 去 deny ip

/etc/nginx/conf.d/your-service.conf

server {
  # ...
  deny RemoteIP;
  # ...
}


這樣設定後,在用 sudo nginx -t 檢查語法後,就可以啟用了

在此不用 iptables 去阻擋的主因是封包進來的 IP 都是 AWS ELB ,所以才改從 Nginx/App 這層取阻擋。缺點就是 access.log 還是一直肥,好處是可以觀察 access.log 看看對方是不是放棄攻擊了?XD

未來若碰到 DDOS 就...

2017年5月18日 星期四

[macOS] 透過 MacPorts 安裝 MySQL Client

每次裝完都很感到疑惑,為何當下沒 mysql 指令可以用,然後又跑去 linux server 把玩 :p 其實是自己沒做設定罷了:

$ sudo port install mysql56
$ mysql
-bash: mysql: command not found
$ sudo port select mysql mysql56
$ mysql --version
mysql  Ver 14.14 Distrib 5.6.34, for osx10.11 (x86_64) using  EditLine wrapper


收工

2016年6月23日 星期四

[PHP] 使用 Google api client 操作 Google Spreadsheet API (Google Docs - Excel)

最近在整理資料,由於使用情境尚未明朗,想了一下就把資料塞到 Excel 來呈現,要排序、輸出跟轉 PDF 檔都 ok ,再加上有定期更新的需求,就來找一下 Google Spreadsheet API 來用用:https://developers.google.com/sheets/ ,程式擺在:

https://github.com/changyy/codeigniter-library-google-spreadsheet

由於同事比較熟 PHP ,就繼續挑 PHP 路線了 :P 沒想到 PHP 相關的 library 滿少的,大概都推薦 github.com/asimlqt/php-google-spreadsheet-client 這套了,但他採用的 API 所實作出來的有些侷限,但已經可以滿足八成的需求了!

最後為了達成自己想幹的事,在 k 完文件跟摸索後,還是自己下海包一份當作筆記(實在是官方文件都沒寫很清楚,只能自己追細節),包括的功能:

  • 支援單獨使用,以及和 PHP CodeIgniter 合用
  • 使用 composer.json 維護相依性:只有使用 google/apiclient: https://github.com/google/google-api-php-client
  • 使用  Google Spreadsheet API v4
  • 取得 Spreadsheet 清單 (使用 v3 API)
  • 建立 Spreadsheet
  • 修改 Spreadsheet name
  • 建立 Worksheet
  • 刪除 Worksheet
  • 可對 Worksheet 凍結視窗(frozenRow/frozenColumn)
  • 更新 Cells 資料
  • 搭配 Web server (如 CodeIgniter Controller 架構),可以完成 Google OAuth 流程,取得 Access Token 跟 Refresh Token
  • 搭配 Google OAuth offline 權限,可以自動 renew access_token

整體上,若不需要建立 Spreadsheet 跟 frozenRowColumn 的功能,建議直接用 asimlqt/php-google-spreadsheet-client 即可,我只先完成自己需要的部分。

2015年10月15日 星期四

使用 Firefox 瀏覽器測試 DNS Round Robin 的方式

關於 DNS round robin 得設置可以把 ttl=0 來方便測試,單純測試 DNS record 有沒有正常顯示,可以單純不斷用 ping 查看 ip address 即可:

$ ping test.domain.name

接著想驗證 server site 有沒有 session 相關的問題,單純用 wget/curl 也可以啦,但如果有一些圖片資料是跟 session 有關的(例如驗證碼,產生的來源資料一樣,但輸出每次都不一樣),這時又頭痛了一下 XD 原先一直想用 Chrome browser,但最後反而跑去用 Firefox! 測試 DNS round robin 是否正常!

首先要驗證 web service 跟 dns round robin 的方式,必須先把 http client 關掉 keep-alive 機制,以 firefox 來說,就是在網址列輸入 about:config 後,搜尋 keep 關鍵字就能找到 network.http.keep-alive.timeout ,把它設成 0 即可,但千萬不要把 network.tcp.keepalive.enabled 設成 false ,那會導致上述的所有設定都無效 XD

接著問題就降低成 DNS round robin 的事了,嗯,沒有最佳解,因為 DNS round robin 本來就沒有每一次 requests 都配置不同的 IP,所以為了提升測試效率,只好自行去寫死 /etc/hosts 等類似的位置了 XD 雖然小蠢,但還是堪用啦。總之,最重要的是要先把 http client 的 keep-alive 關掉才能測試。

2014年4月14日 星期一

[Linux] 安裝 Glances - An eye on your system @ Ubuntu 12.04


Glances - An eye on your system

$ sudo apt-get install python-pip build-essential python-dev
$ sudo pip install Glances
$ glances


如此就可以看到美美的系統監控了 :D

其中 glances 來支援 server/client 模式,並支援 password 管控,可以把它當作可以遠端看 server 的狀態吧

Server:
$ glances -s -B 0.0.0.0 -p 12345 -P helloworld
Glances server is running on 0.0.0.0:12345


Client
$ glances -c ServerIP -p 12345 -P helloworld

2014年3月27日 星期四

[Linux] MySQL Server & Client characterset 設定 @ Ubuntu 12.04

這...太久沒設定了,記得 n 年前也都會碰到 Orz

首先先查詢現況:

mysql> status;
...
Server characterset:    latin1
Db     characterset:    latin1
Client characterset:    utf8
Conn.  characterset:    utf8


接著設定:

$ ls -la /etc/mysql/
total 24
drwxr-xr-x  3 root root 4096 Mar 27 08:58 .
drwxr-xr-x 91 root root 4096 Mar 26 22:41 ..
drwxr-xr-x  2 root root 4096 Mar 27 09:03 conf.d
-rw-------  1 root root  333 Mar 25 16:00 debian.cnf
-rwxr-xr-x  1 root root 1220 Jan 22 05:48 debian-start
-rw-r--r--  1 root root 3638 Mar 27 08:56 my.cnf
$ sudo touch /etc/mysql/conf.d/charset.cnf
[client]
default-character-set=utf8

[mysqld]
character-set-server=utf8
collation-server=utf8_unicode_ci

$ sudo service mysql restart


再查詢一次:

mysql> status;
...
Server characterset:    utf8
Db     characterset:    utf8
Client characterset:    utf8
Conn.  characterset:    utf8


參考資料:

2014年2月11日 星期二

[Linux] VPN L2TP client + DHCP server + NAT iptables @ Ubuntu 12.04 desktop

VPN client + DHCP server + NAt

試了一個小型的網路架構,原本打定把 AP 刷成 DD-WRT 後,即可將 WAN 設定成 VPN 走法,可惜 AP 實在不穩,就改拉一台 PC 來用。此例用一台 Ubuntu 12.04 Desktop 當作 VPN client,此外,為了讓其他 clients 不需要處理 VPN 的連線,因此,讓這台 Ubuntu desktop 又充當 DHCP server,藉以再接一台 AP 出來,供眾人使用,眾人只需要連上 AP 後,就可以走指定的 VPN 通道。

為此,先介紹 Ubuntu 12.04 這檯機器,需要兩張網卡,分別為 NIC 1 跟 NIC 2 ,以下則用 eth0 跟 eth1 區分,而 VPN 走的網卡是 ppp0 (可以連通 VPN 後,用 ifconfig 查看)。其中 eth0 的網路是自動取得 IP 的。

設定 eth1 網卡:

$ sudo vim /etc/network/interfaces
auto eth1
iface eth1 inet static
address 192.168.168.1
netmask 255.255.255.0
network 192.168.168.0

broadcast 192.168.168.255


架設 DHCP server:

$ sudo apt-get install isc-dhcp-server
$ sudo vim /etc/default/isc-dhcp-server
INTERFACES="eth1"
$ sudo vim /etc/dhcp/dhcpd.conf
option domain-name "example.org";
option domain-name-servers 8.8.8.8;
subnet 192.168.168.0 netmask 255.255.255.0 {
  range dynamic-bootp 192.168.168.50 192.168.168.100;
  option routers 192.168.168.1;
}
$ sudo service isc-dhcp-server restart


設定 NAT 與 iptables:

$ echo "1" > /proc/sys/net/ipv4/ip_forward
$ sudo vim /etc/rc.local

echo "1" | tee /proc/sys/net/ipv4/ip_forward

OIF="eth0"
VIF="ppp0"
IIF="eth1"
INNET="192.168.168.0/24"

iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X

iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

iptables -t nat -A POSTROUTING -o $OIF -s $INNET -j MASQUERADE
iptables -t nat -A POSTROUTING -o $VIF -j MASQUERADE


如此一來,即可收工。