Skip to content

Latest commit

 

History

History
306 lines (214 loc) · 13.2 KB

影梭客户端原理剖析.md

File metadata and controls

306 lines (214 loc) · 13.2 KB

影梭客户端原理剖析

项目地址 https://github.com/PikachuHy/shadowsocks-deepin

整个项目都遵守GPLv3许可证协议,欢迎各位大神贡献代码,欢迎提issue。

为什么要做影梭客户端?

我在win上习惯了挂着ss-win,走局部代理(pac模式)。而deepin上的ss-qt5虽然能开代理,但是局部代理(pac模式)没有实现。所以自己就动手写了一个ss-client。界面,源码大量的借鉴了ss-win,ss-qt5以及deepin的许多开源项目。

我做ss-client的出发点是界面美观,操作简便,功能够用就好。

  • 界面美观

使用Qt开发,采用deepin的插件,软件的风格和界面的样式等契合deepin的整体效果。

  • 操作简单

当自己有ss账号,把ss-client点开,配好自己的ss账号,就能使用,自动处理pac的配置问题,GFWList的更新问题,开机自启问题,只需要简单的开关就能科学上网。

  • 功能够用就好

谷歌或者火狐浏览器,启动ss-client就能正常上google,youtube。

技术原理剖析

通过学习开源项目,了解更多软件开发方面的知识,同时提升自身的编码能力。在掌握相关的知识后,输出文档,记录下学习历程,既方便自己查阅,又能和他人交流。

ss代理的实现

借助开源项目shadowsocks/libQtShadowsocks,可以很方便的在拥有ss账号的情况下,启动sock5代理。

QSS::Profile profile;
QSS::Controller* controller = new Controller(true);
controller->setup(profile);
controller->start();

这么就能启动代理了。使用proxychains curl www.google.com就能在命令行上访问google的。

在上面的代码中,QSS::Profile就是ss账号,它是一个结构体,看源码

namespace QSS {

struct Profile {
    QString server;
    QString local_address;
    QString method;
    QString password;
    quint16 server_port;
    quint16 local_port;
    int timeout;

    /*
     * Set http_proxy to true then the local will serve as HTTP proxy server.
     * Because the HttpProxy is a second-level proxy, the actual process is to
     * use a random available port as SOCKS5 and then set HttpProxy listen on
     * the local port and forward traffics via SOCKS5 proxy.
     * It's false by default.
     */
    bool http_proxy;
    bool debug;//turn on debug output or not
    bool auth;

    Profile();

    /*
     * Construct Profile using ss:// URI
     * Please check https://shadowsocks.org/en/config/quick-guide.html for more
     * details. Undefined behaviour if uri is invalid.
     */
    Profile(QByteArray uri);

    // Encode profile as a ss:// URI
    QByteArray toURI();
};

}

和shadowsocks的配置文件是相对应的,具体请参考 shadowsocks Quick Guide

{
    "server":"my_server_ip",
    "server_port":8388,
    "local_port":1080,
    "password":"barfoo!",
    "timeout":600,
    "method":"chacha20-ietf-poly1305"
}

但是这样还不能直接用谷歌或火狐访问google,还需要在控制中心中配置(ss-qt5必须配才能用),而我做的事情就是干掉了这一步,ss-client帮忙做了。ss-client是如何做到的呢?答案是借助linuxdeepin/dde-control-center中的一个辅助类NetworkInter

三种代理方式的实现

这个辅助类是何方神圣呢?其实是通过DBus调用了dde-api。所谓dde-api,即

dde-daemon dbus API: 这一部分主要是由dde-session-daemon和dde-system-daemon提供的DBus接口给深度控制中心前端界面使用的,外部应用程序也可以直接使用这部分API来快速开发,而不用自己研究和编写与系统底层软硬件打交到的代码,简单的说几个功能,感兴趣的朋友可以直接查看深度控制中心的界面代码来玩(https://github.com/linuxdeepin/dde-control-center):

  • 查询当前系统有几个屏幕,哪些屏幕是主屏,分辨率是多少?
  • 查询当前系统的语言、亮度、音量等设置
  • 查询当前系统的网络链接状态:连接的是无线还是有线,有没有开启VPN?
  • 查询当前系统的日期时间、时区、键盘鼠标等外设的状态 只要控制中心界面显示的所有硬件状态,都可以通过dde-api提供的DBus接口服务查询到,而这些DBus API后面的源代码都是深度操作系统研发人员经过非常多的时间打磨好的,不用自己痛苦的去裸写底层库(network-manager、pluseaudio、bluez、upower、udisk等)代码,大大节约了应用开发者编写高级功能的时间和投入成本。

——摘自深度桌面操作系统架构设计

源码摘录,全部请查看manager_proxy.go

func (m *Manager) SetProxyMethod(proxyMode string) (err error) {
	logger.Info("SetProxyMethod", proxyMode)
	err = checkProxyMethod(proxyMode)
	if err != nil {
		return
	}

	// ignore if proxyModeNone already set
	currentMethod, _ := m.GetProxyMethod()
	if proxyMode == proxyModeNone && currentMethod == proxyModeNone {
		return
	}

	ok := proxySettings.SetString(gkeyProxyMode, proxyMode)
	if !ok {
		err = fmt.Errorf("set proxy method through gsettings failed")
		return
	}
	switch proxyMode {
	case proxyModeNone:
		notifyProxyDisabled()
	default:
		notifyProxyEnabled()
	}
	return
}

再往下走deepin给了一个注释,原来是这样实现的。

The Deepin proxy gsettings schemas use the same path with org.gnome.system.proxy which is /system/proxy. So in fact they control the same values, and we don't need to synchronize them at all.

大致意思是设置方式同gnome。稍微提一下的是GSetings,因为我以前没见过。

GSettings 是应用程序设置的高级 API,“dconf”的前端。“dconf”是管理用户设置,基于键值的配置系统。它是红帽企业版 Linux 7 使用的“GSettings”的后端。“dconf”管理了一系列不同的设置,包括“GDM”、应用程序,以及代理设置。

对应到深度控制中心就是

图

在这三个选项之间切换。在本客户端中对应的选项就是这三个Action。

图

手动代理即全局模式,本客户端自动设置好socks5代理,主机和端口号随ss账号定。

自动代理即pac模式,只有通过pac文件设置了的网址才走代理,其他正常连接。

为了方便使用,本客户端提供了从GFWList上更新本地pac文件的功能。gfwlist转pac的实现参考

https://github.com/JinnLynn/genpac

开机自启动的实现

开机自启动有一个比较简单的做法就是,直接把desktop文件扔到~/.config/autostart文件夹下。但是,这样会和deepin启动器的样式有冲突,也不利于检测是否开机自启。deepin启动器关闭开机自启动,在~/.config/autostart文件夹下还是存在desktop文件。所以,我又去找了dde-api。

以下是配置DBus接口的xml文件。

     <interface name="com.deepin.StartManager">
          <method name="AddAutostart">
               <arg type="s" direction="in"></arg>
               <arg type="b" direction="out"></arg>
          </method>
          <method name="AutostartList">
               <arg type="as" direction="out"></arg>
          </method>
          <method name="IsAutostart">
               <arg type="s" direction="in"></arg>
               <arg type="b" direction="out"></arg>
          </method>
          <method name="Launch">
               <arg type="s" direction="in"></arg>
               <arg type="b" direction="out"></arg>
          </method>
          <method name="LaunchWithTimestamp">
               <arg type="s" direction="in"></arg>
               <arg type="u" direction="in"></arg>
               <arg type="b" direction="out"></arg>
          </method>
          <method name="RemoveAutostart">
               <arg type="s" direction="in"></arg>
               <arg type="b" direction="out"></arg>
          </method>
          <signal name="AutostartChanged">
               <arg type="s"></arg>
               <arg type="s"></arg>
          </signal>
     </interface>

这样,我就能和deepin启动器同步了。

打开文件管理器直接定位到某个文件的实现

在pac的小项里,有几个用户可以自己编辑的地方,我按照ss-win的做法,直接打开文件管理器并定位到改的文件。这个是调用deepin的一个工具类——DDesktopServices

DDesktopServices::showFileItem(path);

其他的似乎都只需要用一些Qt的基本知识,或者Google一下能慢慢做出来。

项目小结

最开始的时候,我是把所有的实现都写在一个文件夹里。当实现的功能越来越多,查找起来很不方便。后来,我做了一个像gradle的目录结构,但是CLion对于嵌套多层CMakeLists.txt的解析好像有点问题,最后目录结果如下

.
├── debian.bak
├── images
├── Resources
├── src
│   ├── common
│   ├── dao
│   ├── dbusinterface
│   ├── model
│   ├── service
│   │   └── impl
│   ├── util
│   └── widget
└── translations

比较有意思的是,由于我打deb包每次都需要手动输入不少东西,一直没弄懂ss-qt5怎么就dpkg-buildpackage -uc -us -b直接搞定了呢?(见ss-qt5安装指南) 所以,我自己动手写了一个脚本,把之前重复的动作,一个一个的通过shell执行一遍。脚本源码

然后是github的好基友——travis持续集成,由于依赖问题,没有办法使用。想想觉得可气。

哦!有几个需要说明的是,这个客户端比起ss-win来,还有一些功能没有实现

  • 允许来自局域网的连接

在deepin上,我还不会开wifi,所以没有办法测试,只能延后了。

  • 根据统计选择服务器

在ss-win上,有三种选择自动服务器的策略:负载均衡,高可用,根据统计。我实现了前两种,最后一种,那些统计参数的意义实在难懂,就放弃了。

  • 正向代理

“允许来自局域网的连接”以及“正向代理”的设置问题 说是翻公司的墙用的。我目前还没有用过,不是很懂,也暂时放弃。后面有需要补上。

另外一点是shadowsocks-windows实现了AEAD,由于我的底层库libQtShadowsocks没有实现,所以我也没法实现。见Support SIP004 (AEAD Ciphers)

可以说,这个项目还是很有收获的,重新熟悉了Qt,对Qt的Model/View有了更深的认识。比较遗憾的是dropbox目前还是不能通过pac模式登录上去(全局模式可以),真不知道还有什么地方需要添加到pac文件中。

特别感谢以下项目

参考文章

做准备的学习记录

(Deepin 15.4) Qt5.9的下载及安装——开始Qt之旅

说说QtQuick提供的类型

从MVC到Model-View-Delegate

Qt宏Q_OBJECT展开记录

ss学习记录

附:

界面图

图

图

图

图

图

图