frp/pkg/ssh/vclient.go
0x7fff 8b432e179d feat: ssh client implement (#3671)
* feat: frps support ssh

* fix: comments

* fix: update pkg

* fix: remove useless change

---------

Co-authored-by: int7 <int7@gmail.com>
2023-11-22 14:35:37 +08:00

186 lines
3.9 KiB
Go

package ssh
import (
"context"
"fmt"
"net"
"sync/atomic"
"time"
"golang.org/x/crypto/ssh"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
plugin "github.com/fatedier/frp/pkg/plugin/server"
"github.com/fatedier/frp/pkg/util/log"
frp_net "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/pkg/util/util"
"github.com/fatedier/frp/pkg/util/xlog"
"github.com/fatedier/frp/server/controller"
"github.com/fatedier/frp/server/proxy"
)
// VirtualService is a client VirtualService run in frps
type VirtualService struct {
clientCfg v1.ClientCommonConfig
pxyCfg v1.ProxyConfigurer
serverCfg v1.ServerConfig
sshSvc *Service
// uniq id got from frps, attach it in loginMsg
runID string
loginMsg *msg.Login
// All resource managers and controllers
rc *controller.ResourceController
exit uint32 // 0 means not exit
// SSHService context
ctx context.Context
// call cancel to stop SSHService
cancel context.CancelFunc
replyCh chan interface{}
pxy proxy.Proxy
}
func NewVirtualService(
ctx context.Context,
clientCfg v1.ClientCommonConfig,
serverCfg v1.ServerConfig,
logMsg msg.Login,
rc *controller.ResourceController,
pxyCfg v1.ProxyConfigurer,
sshSvc *Service,
replyCh chan interface{},
) (svr *VirtualService, err error) {
svr = &VirtualService{
clientCfg: clientCfg,
serverCfg: serverCfg,
rc: rc,
loginMsg: &logMsg,
sshSvc: sshSvc,
pxyCfg: pxyCfg,
ctx: ctx,
exit: 0,
replyCh: replyCh,
}
svr.runID, err = util.RandID()
if err != nil {
return nil, err
}
go svr.loopCheck()
return
}
func (svr *VirtualService) Run(ctx context.Context) (err error) {
ctx, cancel := context.WithCancel(ctx)
svr.ctx = xlog.NewContext(ctx, xlog.New())
svr.cancel = cancel
remoteAddr, err := svr.RegisterProxy(&msg.NewProxy{
ProxyName: svr.pxyCfg.(*v1.TCPProxyConfig).Name,
ProxyType: svr.pxyCfg.(*v1.TCPProxyConfig).Type,
RemotePort: svr.pxyCfg.(*v1.TCPProxyConfig).RemotePort,
})
if err != nil {
return err
}
log.Info("run a reverse proxy on port: %v", remoteAddr)
return nil
}
func (svr *VirtualService) Close() {
svr.GracefulClose(time.Duration(0))
}
func (svr *VirtualService) GracefulClose(d time.Duration) {
atomic.StoreUint32(&svr.exit, 1)
svr.pxy.Close()
if svr.cancel != nil {
svr.cancel()
}
svr.replyCh <- &VProxyError{}
}
func (svr *VirtualService) loopCheck() {
<-svr.sshSvc.Exit()
svr.pxy.Close()
log.Info("virtual client service close")
}
func (svr *VirtualService) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
var pxyConf v1.ProxyConfigurer
pxyConf, err = config.NewProxyConfigurerFromMsg(pxyMsg, &svr.serverCfg)
if err != nil {
return
}
// User info
userInfo := plugin.UserInfo{
User: svr.loginMsg.User,
Metas: svr.loginMsg.Metas,
RunID: svr.runID,
}
svr.pxy, err = proxy.NewProxy(svr.ctx, &proxy.Options{
LoginMsg: svr.loginMsg,
UserInfo: userInfo,
Configurer: pxyConf,
ResourceController: svr.rc,
GetWorkConnFn: svr.GetWorkConn,
PoolCount: 10,
ServerCfg: &svr.serverCfg,
})
if err != nil {
return remoteAddr, err
}
remoteAddr, err = svr.pxy.Run()
if err != nil {
log.Warn("proxy run error: %v", err)
return
}
defer func() {
if err != nil {
log.Warn("proxy close")
svr.pxy.Close()
}
}()
return
}
func (svr *VirtualService) GetWorkConn() (workConn net.Conn, err error) {
// tell ssh client open a new stream for work
payload := forwardedTCPPayload{
Addr: svr.serverCfg.BindAddr, // TODO refine
Port: uint32(svr.pxyCfg.(*v1.TCPProxyConfig).RemotePort),
}
channel, reqs, err := svr.sshSvc.SSHConn().OpenChannel(ChannelTypeServerOpenChannel, ssh.Marshal(payload))
if err != nil {
return nil, fmt.Errorf("open ssh channel error: %v", err)
}
go ssh.DiscardRequests(reqs)
workConn = frp_net.WrapReadWriteCloserToConn(channel, svr.sshSvc.tcpConn)
return workConn, nil
}