FRP源码阅读1-客户端

当前时间为2024-5-01,FRP版本为0.57.0。想着网上能不能找到一个Rust写好的内网穿透工具,结果找的两个ratholewmproxy都无法做到像FRP一样,直接通过服务端,进入客户端内网访问任意内网资源。还是需要自己重写,所以现在来读FRP源码了。

目录结构

核心如下,Client是FRP核心的源码,然后cmd是main函数入口,简单处理后进入Client完成核心行为。

img

CMD

main

没有什么好看的,直接进入sub.Execute方法。

img

root

在Execute方法调用了rootCmd.Execute(),跟进去之后是一个库。

img

没看过Go,但是猜测是一个调用。直接看rootCmd的内容,在rootCmd中,进行参数判断,然后核心进到了runClient。

img

在runClient中,首先解析cfg文件,然后调用startService方法。先跟一下config文件是怎么解析的,随便看一下数据结构。

img

LoadClientConfig

先来看一下返回的三个数据结构:ClientCommonConfig、ProxyConfiuterer、VisitorConfigurer。

ClientCommonConfig

img

  1. Auth

类型为AuthClientConfig。里面有Method、AdditionScopes、Token、OIDC。四个字段组成。这里我只关注token、和Mehotd里面有token这个方法。这个字段应该就是ini里面的Token定义。

image-20240501224935189

image-20240501224953053

  1. User没懂,看意思应该是为了区别不同的Proxy。

  2. ServerAddr不用说,Client连接Server的位置

  3. ServerPort,一样的。

  4. NatHoleSTUNServer ,NAT相关不看

  5. DNSServer , 为client指定Dns服务器

  6. LoginFailExit, 连接Server失败后是否直接退出

  7. Start,

  8. Log

  9. WebServer

  10. Transport

  11. UDPpacketSize

  12. Metadatas

  13. IncludeConfigFile

ProxyConfigurer和VisitorConfigurer由两个Interface组成,不懂也懒得看了。

LoadClientConfig也懒得读了,直接看后面startService

startService

image-20240501230422146

主要干了两件事,NewServicesvr.Run。kcp和quic不用也不看了。

NewService

image-20240501230712625

设置DefaultOption,进去看看。第一个如果Options.common不存在,调用Complete函数。

image-20240501230811653

设置默认IP,端口等等。

image-20240501230831721

如果ConnectorCreatro不存在,新建一个NewConnector,跟进去看了一下,挺复杂。后面用到了再说

image-20240501231121776

设置DefaultOptions后,就是webServer。这个不用看,关键是创建了一个Service结构体。

image-20240501231401797

Service结构体:

  1. ctx暂时没看懂

  2. authSetter之前说的Token认证

  3. Webserver不用看

  4. common就是之前的ClientCommonConfig

  5. configFilePath

  6. proxyCfgs,之前没看以后用到了再看,不知道SOcks_5在不在里面。

  7. VisitorCgf,之前也没看用到了再看。

  8. clientSpec,没看

  9. connectorCreator,仔细看了一个这个东西是个接口

  10. HandleWorkConnCb,这个也是一个接口

run

New了一个Service之后,就开始Run了。进入了Client目录,离开CMD目录

Client

run

取一个Cancel,然后设置Dns服务器。取Cancel没看懂,好像是取消Service的。

image-20240501232513238

之后是第一次登录尝试。

image-20240501232626660

loopLoginUntilSuccess

进入srv.login函数

image-20240501232930686

在Login函数中首先尝试连接对方端口

image-20240501234130884

之后Set一个LoginMsg,安全设备一般识别到的特征好像就是这个地方来的。包含了Arch、OS、PoolCount、User、Timestamp等。

image-20240501234223389

之后调用SetLogin,将LoginMsg设置。这里跟进去看

image-20240501234503395

SetLogin是一个接口,Oidc和、Toekn两个类实现 了

image-20240501234531971

image-20240501234814579

跟进Token,调用了GetAuthKey函数,这里有意思的是不仅传入了Token而且传入了之前设置的Loginmsg.timeStam作为参数。

image-20240501235039507

跟进去看,拼接TokenTimeStamp作为Key。有这个必要吗,时间撮感觉没什么用,而且还暴露了特征。

image-20240501235027016

获得key之后,使用WriteMsg将信息发给Server。之后读取Server回复

将Read的Msg转换为了lginRespMsg结构体,由VersionRunIDError组成。

image-20240501235502688

如果一切成功最后拿到的是一个RunId。之后都是使用这个RunId进行认证的吗?

image-20240501235611482

lgin函数结束,如果设置了认证失败立即停止就无了。但是如果没有,好像就继续使用Porxy继续,这里感觉不重要就不读了。

image-20240502000000790

keepControllerWorking之后就是等待svr.ctx.Done执行完,执行完就stop然后退出了。关键就在于KeepControllerWorking.

image-20240502001816911

keepControllerWorking

login结束后,是一个KeepControllerWorking函数。

image-20240502000245494

keepControllerWorking里面是等待ctl执行完,之后就是一个防止网络问题导致中断的再次连接了。

image-20240502002018356

Stop

Keep中的核心之后再看,这里看看最后的svr.stop的内容

image-20240502002228100

继续跟进一个GraceFulClose函数

image-20240502002242193

关闭pmvmSession

image-20240502002305167

三个非别是关闭`proxyManagerVisitorManager、和Session,这次这三个都没有认真看,之后在说。

总结

首先CMD处理Config文件解析命令行、然后通过run函数中LoopLogin尝试登录,之后使用keepController进行主要工作,然后使用Stop清除。