mirror of
https://github.com/kubenetworks/kubevpn.git
synced 2025-09-26 19:31:17 +08:00
feat: add ssh to linux server
This commit is contained in:
2
Makefile
2
Makefile
@@ -23,10 +23,10 @@ LDFLAGS=--ldflags "\
|
||||
-X ${BASE}/pkg/config.Image=${IMAGE} \
|
||||
-X ${BASE}/pkg/config.Version=${VERSION} \
|
||||
-X ${BASE}/pkg/config.GitCommit=${GIT_COMMIT} \
|
||||
-X ${BASE}/pkg/config.GitHubOAuthToken=${GitHubOAuthToken} \
|
||||
-X ${FOLDER}/cmds.BuildTime=${BUILD_TIME} \
|
||||
-X ${FOLDER}/cmds.Branch=${BRANCH} \
|
||||
-X ${FOLDER}/cmds.OsArch=${OS_ARCH} \
|
||||
-X ${FOLDER}/cmds.GitHubOAuthToken=${GitHubOAuthToken} \
|
||||
"
|
||||
|
||||
GO111MODULE=on
|
||||
|
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
[![GitHub Workflow][1]](https://github.com/KubeNetworks/kubevpn/actions)
|
||||
[![Go Version][2]](https://github.com/KubeNetworks/kubevpn/blob/master/go.mod)
|
||||
|
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
[![GitHub Workflow][1]](https://github.com/KubeNetworks/kubevpn/actions)
|
||||
[![Go Version][2]](https://github.com/KubeNetworks/kubevpn/blob/master/go.mod)
|
||||
|
@@ -71,6 +71,7 @@ func NewKubeVPNCommand() *cobra.Command {
|
||||
CmdConfig(factory),
|
||||
CmdCp(factory),
|
||||
CmdSSH(factory),
|
||||
CmdSSHDaemon(factory),
|
||||
CmdLogs(factory),
|
||||
CmdReset(factory),
|
||||
CmdQuit(factory),
|
||||
|
@@ -1,14 +1,16 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/websocket"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
|
||||
"github.com/wencaiwulue/kubevpn/pkg/handler"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/daemon"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/util"
|
||||
)
|
||||
|
||||
@@ -18,10 +20,9 @@ import (
|
||||
func CmdSSH(_ cmdutil.Factory) *cobra.Command {
|
||||
var sshConf = &util.SshConfig{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "ssh",
|
||||
Hidden: true,
|
||||
Short: "Ssh to jump server",
|
||||
Long: `Ssh to jump server`,
|
||||
Use: "ssh",
|
||||
Short: "Ssh to jump server",
|
||||
Long: `Ssh to jump server`,
|
||||
Example: templates.Examples(i18n.T(`
|
||||
# Jump to server behind of bastion host or ssh jump host
|
||||
kubevpn ssh --ssh-addr 192.168.1.100:22 --ssh-username root --ssh-keyfile ~/.ssh/ssh.pem
|
||||
@@ -32,14 +33,26 @@ func CmdSSH(_ cmdutil.Factory) *cobra.Command {
|
||||
└──────┘ └──────┘ └──────┘ └──────┘ └────────┘
|
||||
kubevpn ssh --ssh-alias <alias>
|
||||
`)),
|
||||
PreRun: func(*cobra.Command, []string) {
|
||||
if !util.IsAdmin() {
|
||||
util.RunWithElevated()
|
||||
os.Exit(0)
|
||||
}
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return daemon.StartupDaemon(cmd.Context())
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := handler.SSH(cmd.Context(), sshConf)
|
||||
config, err := websocket.NewConfig("ws://test/ws", "http://test")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Header.Set("ssh-addr", sshConf.Addr)
|
||||
config.Header.Set("ssh-username", sshConf.User)
|
||||
config.Header.Set("ssh-password", sshConf.Password)
|
||||
config.Header.Set("ssh-keyfile", sshConf.Keyfile)
|
||||
config.Header.Set("ssh-alias", sshConf.ConfigAlias)
|
||||
client := daemon.GetTCPClient(true)
|
||||
conn, err := websocket.NewClient(config, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go io.Copy(conn, os.Stdin)
|
||||
_, err = io.Copy(os.Stdout, conn)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
50
cmd/kubevpn/cmds/sshdaemon.go
Normal file
50
cmd/kubevpn/cmds/sshdaemon.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
|
||||
"github.com/wencaiwulue/kubevpn/pkg/daemon"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/daemon/rpc"
|
||||
)
|
||||
|
||||
// CmdSSHDaemon
|
||||
// 设置本地的IP是223.254.0.1/32 ,记得一定是掩码 32位,
|
||||
// 这样别的路由不会走到这里来
|
||||
func CmdSSHDaemon(_ cmdutil.Factory) *cobra.Command {
|
||||
var clientIP string
|
||||
cmd := &cobra.Command{
|
||||
Use: "ssh-daemon",
|
||||
Hidden: true,
|
||||
Short: "Ssh daemon server",
|
||||
Long: `Ssh daemon server`,
|
||||
Example: templates.Examples(i18n.T(`
|
||||
# SSH daemon server
|
||||
kubevpn ssh-daemon --client-ip 223.254.0.123/32
|
||||
`)),
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := daemon.StartupDaemon(cmd.Context())
|
||||
return err
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
client, err := daemon.GetClient(true).SshStart(
|
||||
cmd.Context(),
|
||||
&rpc.SshStartRequest{
|
||||
ClientIP: clientIP,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(os.Stdout, client.ServerIP)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVar(&clientIP, "client-ip", "", "Client cidr")
|
||||
return cmd
|
||||
}
|
@@ -14,12 +14,6 @@ import (
|
||||
"github.com/wencaiwulue/kubevpn/pkg/upgrade"
|
||||
)
|
||||
|
||||
// GitHubOAuthToken
|
||||
// --ldflags -X
|
||||
var (
|
||||
GitHubOAuthToken = ""
|
||||
)
|
||||
|
||||
func CmdUpgrade(_ cmdutil.Factory) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "upgrade",
|
||||
@@ -27,8 +21,8 @@ func CmdUpgrade(_ cmdutil.Factory) *cobra.Command {
|
||||
Long: `Upgrade KubeVPN version, automatically download latest KubeVPN from GitHub`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var client = http.DefaultClient
|
||||
if GitHubOAuthToken != "" {
|
||||
client = oauth2.NewClient(cmd.Context(), oauth2.StaticTokenSource(&oauth2.Token{AccessToken: GitHubOAuthToken, TokenType: "Bearer"}))
|
||||
if config.GitHubOAuthToken != "" {
|
||||
client = oauth2.NewClient(cmd.Context(), oauth2.StaticTokenSource(&oauth2.Token{AccessToken: config.GitHubOAuthToken, TokenType: "Bearer"}))
|
||||
}
|
||||
err := upgrade.Main(cmd.Context(), config.Version, config.GitCommit, client)
|
||||
if err != nil {
|
||||
|
@@ -101,6 +101,9 @@ var (
|
||||
Version = "latest"
|
||||
GitCommit = ""
|
||||
|
||||
// GitHubOAuthToken --ldflags -X
|
||||
GitHubOAuthToken = ""
|
||||
|
||||
OriginImage = "docker.io/naison/kubevpn:" + Version
|
||||
|
||||
DaemonPath string
|
||||
|
@@ -160,8 +160,8 @@ func (h tunHandler) printRoute() {
|
||||
sb.WriteString(fmt.Sprintf("to: %s, route: %s\n", key, strings.Join(s, " ")))
|
||||
}
|
||||
})
|
||||
fmt.Println(sb.String())
|
||||
fmt.Println(i)
|
||||
log.Debug(sb.String())
|
||||
log.Debug(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
104
pkg/daemon/action/sshdaemon.go
Normal file
104
pkg/daemon/action/sshdaemon.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
|
||||
"github.com/wencaiwulue/kubevpn/pkg/config"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/core"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/daemon/rpc"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/handler"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/tun"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/util"
|
||||
)
|
||||
|
||||
var _, bits = config.DockerCIDR.Mask.Size()
|
||||
var DefaultServerIP = (&net.IPNet{IP: config.DockerRouterIP, Mask: net.CIDRMask(bits, bits)}).String()
|
||||
|
||||
var serverIP string
|
||||
var mux sync.Mutex
|
||||
var sshCancelFunc context.CancelFunc
|
||||
|
||||
func (svr *Server) SshStart(ctx context.Context, req *rpc.SshStartRequest) (*rpc.SshStartResponse, error) {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
|
||||
clientIP, clientCIDR, err := net.ParseCIDR(req.ClientIP)
|
||||
if err != nil {
|
||||
log.Errorf("parse cidr error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if serverIP == "" {
|
||||
r := core.Route{
|
||||
ServeNodes: []string{
|
||||
"tun://127.0.0.1:8422?net=" + DefaultServerIP,
|
||||
"tcp://:10800",
|
||||
},
|
||||
Retries: 5,
|
||||
}
|
||||
servers, err := handler.Parse(r)
|
||||
if err != nil {
|
||||
log.Errorf("parse route error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
ctx, sshCancelFunc = context.WithCancel(context.Background())
|
||||
go func() {
|
||||
err := handler.Run(ctx, servers)
|
||||
if err != nil {
|
||||
log.Errorf("run route error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
ctx2, cancelF := context.WithCancel(ctx)
|
||||
wait.UntilWithContext(ctx2, func(ctx context.Context) {
|
||||
ip, _, _ := net.ParseCIDR(DefaultServerIP)
|
||||
ok, err := util.Ping(ip.String())
|
||||
if err != nil {
|
||||
} else if ok {
|
||||
cancelF()
|
||||
} else {
|
||||
// todo
|
||||
cancelF()
|
||||
}
|
||||
}, time.Millisecond*20)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serverIP = DefaultServerIP
|
||||
}
|
||||
|
||||
serverip, _, err := net.ParseCIDR(serverIP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tunDevice, err := util.GetTunDevice(serverip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tun.AddRoutes(tunDevice.Name, types.Route{
|
||||
Dst: net.IPNet{
|
||||
IP: clientIP,
|
||||
Mask: clientCIDR.Mask,
|
||||
},
|
||||
GW: nil,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("add route error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &rpc.SshStartResponse{ServerIP: serverIP}, nil
|
||||
}
|
||||
|
||||
func (svr *Server) SshStop(ctx context.Context, req *rpc.SshStopRequest) (*rpc.SshStopResponse, error) {
|
||||
if sshCancelFunc != nil {
|
||||
sshCancelFunc()
|
||||
}
|
||||
return &rpc.SshStopResponse{}, nil
|
||||
}
|
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -157,3 +159,25 @@ func runDaemon(ctx context.Context, exe string, isSudo bool) error {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func GetHttpClient(isSudo bool) *http.Client {
|
||||
client := http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
var d net.Dialer
|
||||
d.Timeout = 30 * time.Second
|
||||
d.KeepAlive = 30 * time.Second
|
||||
return d.DialContext(ctx, "unix", GetSockPath(isSudo))
|
||||
},
|
||||
},
|
||||
}
|
||||
return &client
|
||||
}
|
||||
|
||||
func GetTCPClient(isSudo bool) net.Conn {
|
||||
conn, err := net.Dial("unix", GetSockPath(isSudo))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
@@ -5,9 +5,12 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/admin"
|
||||
"google.golang.org/grpc/health"
|
||||
@@ -15,6 +18,7 @@ import (
|
||||
"google.golang.org/grpc/reflection"
|
||||
|
||||
"github.com/wencaiwulue/kubevpn/pkg/daemon/action"
|
||||
_ "github.com/wencaiwulue/kubevpn/pkg/daemon/handler"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/daemon/rpc"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/util"
|
||||
)
|
||||
@@ -64,8 +68,16 @@ func (o *SvrOption) Start(ctx context.Context) error {
|
||||
// [tun-client] 223.254.0.101 - 127.0.0.1:8422: dial tcp 127.0.0.1:55407: connect: can't assign requested address
|
||||
http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = 100
|
||||
rpc.RegisterDaemonServer(o.svr, &action.Server{Cancel: o.Stop, IsSudo: o.IsSudo, GetClient: GetClient, LogFile: file})
|
||||
// startup a http server
|
||||
// With downgrading-capable gRPC server, which can also handle HTTP.
|
||||
downgradingServer := &http.Server{}
|
||||
var h2Server http2.Server
|
||||
_ = http2.ConfigureServer(downgradingServer, &h2Server)
|
||||
handler := CreateDowngradingHandler(o.svr, http.HandlerFunc(http.DefaultServeMux.ServeHTTP))
|
||||
downgradingServer.Handler = h2c.NewHandler(handler, &h2Server)
|
||||
o.uptime = time.Now().Unix()
|
||||
return o.svr.Serve(lis)
|
||||
return downgradingServer.Serve(lis)
|
||||
//return o.svr.Serve(lis)
|
||||
}
|
||||
|
||||
func (o *SvrOption) Stop() {
|
||||
@@ -77,3 +89,22 @@ func (o *SvrOption) Stop() {
|
||||
path := GetSockPath(o.IsSudo)
|
||||
_ = os.Remove(path)
|
||||
}
|
||||
|
||||
// CreateDowngradingHandler takes a gRPC server and a plain HTTP handler, and returns an HTTP handler that has the
|
||||
// capability of handling HTTP requests and gRPC requests that may require downgrading the response to gRPC-Web or gRPC-WebSocket.
|
||||
//
|
||||
// if r.ProtoMajor == 2 && strings.HasPrefix(
|
||||
// r.Header.Get("Content-Type"), "application/grpc") {
|
||||
// grpcServer.ServeHTTP(w, r)
|
||||
// } else {
|
||||
// yourMux.ServeHTTP(w, r)
|
||||
// }
|
||||
func CreateDowngradingHandler(grpcServer *grpc.Server, httpHandler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.ProtoMajor == 2 && strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") {
|
||||
grpcServer.ServeHTTP(w, r)
|
||||
} else {
|
||||
httpHandler.ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
269
pkg/daemon/handler/ssh.go
Normal file
269
pkg/daemon/handler/ssh.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"golang.org/x/net/websocket"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/wencaiwulue/kubevpn/pkg/config"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/core"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/handler"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/upgrade"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/util"
|
||||
)
|
||||
|
||||
// Ws
|
||||
// 0) remote server install kubevpn if not found
|
||||
// 1) start remote kubevpn server
|
||||
// 2) start local tunnel
|
||||
// 3) ssh terminal
|
||||
func Ws(conn *websocket.Conn, sshConfig *util.SshConfig) {
|
||||
var ctx = context.Background()
|
||||
cancel, cancelFunc := context.WithCancel(ctx)
|
||||
defer cancelFunc()
|
||||
|
||||
err := remoteInstallKubevpnIfCommandNotFound(ctx, sshConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
clientIP, err := util.GetIPBaseNic()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
local, err := portMap(cancel, sshConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cmd := fmt.Sprintf(`export %s=%s && kubevpn ssh-daemon --client-ip %s`, config.EnvStartSudoKubeVPNByKubeVPN, "true", clientIP.String())
|
||||
serverIP, stderr, err := util.RemoteRun(sshConfig, cmd, nil)
|
||||
if err != nil {
|
||||
log.Errorf("run error: %v", err)
|
||||
log.Errorf("run stdout: %v", string(serverIP))
|
||||
log.Errorf("run stderr: %v", string(stderr))
|
||||
return
|
||||
}
|
||||
ip, _, err := net.ParseCIDR(string(serverIP))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r := core.Route{
|
||||
ServeNodes: []string{
|
||||
fmt.Sprintf("tun:/127.0.0.1:8422?net=%s&route=%s", clientIP, string(serverIP)),
|
||||
},
|
||||
ChainNode: fmt.Sprintf("tcp://127.0.0.1:%d", local),
|
||||
Retries: 5,
|
||||
}
|
||||
servers, err := handler.Parse(r)
|
||||
if err != nil {
|
||||
log.Errorf("parse route error: %v", err)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
log.Error(handler.Run(cancel, servers))
|
||||
}()
|
||||
tun, err := util.GetTunDevice(clientIP.IP)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Info("tunnel connected")
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Second * 2)
|
||||
for {
|
||||
select {
|
||||
case <-cancel.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
_, _ = util.Ping(clientIP.IP.String())
|
||||
_, _ = util.Ping(ip.String())
|
||||
_ = exec.CommandContext(cancel, "ping", "-c", "4", "-b", tun.Name, ip.String()).Run()
|
||||
}
|
||||
}
|
||||
}()
|
||||
err = enterTerminal(sshConfig, conn)
|
||||
return
|
||||
}
|
||||
|
||||
func portMap(ctx context.Context, conf *util.SshConfig) (localPort int, err error) {
|
||||
removePort := 10800
|
||||
localPort, err = util.GetAvailableTCPPortOrDie()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var remote netip.AddrPort
|
||||
remote, err = netip.ParseAddrPort(net.JoinHostPort("127.0.0.1", strconv.Itoa(removePort)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var local netip.AddrPort
|
||||
local, err = netip.ParseAddrPort(net.JoinHostPort("127.0.0.1", strconv.Itoa(localPort)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// pre-check network ip connect
|
||||
var cli *ssh.Client
|
||||
cli, err = util.DialSshRemote(conf)
|
||||
if err != nil {
|
||||
return
|
||||
} else {
|
||||
_ = cli.Close()
|
||||
}
|
||||
errChan := make(chan error, 1)
|
||||
readyChan := make(chan struct{}, 1)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
err := util.Main(ctx, remote, local, conf, readyChan)
|
||||
if err != nil {
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
log.Errorf("ssh forward failed err: %v", err)
|
||||
}
|
||||
select {
|
||||
case errChan <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-readyChan:
|
||||
return
|
||||
case err = <-errChan:
|
||||
log.Errorf("ssh proxy err: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func enterTerminal(conf *util.SshConfig, conn *websocket.Conn) error {
|
||||
cli, err := util.DialSshRemote(conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
session, err := cli.NewSession()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer session.Close()
|
||||
session.Stdout = conn
|
||||
session.Stderr = conn
|
||||
session.Stdin = conn
|
||||
|
||||
fd := int(os.Stdin.Fd())
|
||||
state, err := terminal.MakeRaw(fd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("terminal make raw: %s", err)
|
||||
}
|
||||
defer terminal.Restore(fd, state)
|
||||
|
||||
w, h, err := terminal.GetSize(fd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("terminal get size: %s", err)
|
||||
}
|
||||
modes := ssh.TerminalModes{
|
||||
ssh.ECHO: 1,
|
||||
ssh.ECHOCTL: 0,
|
||||
ssh.TTY_OP_ISPEED: 14400,
|
||||
ssh.TTY_OP_OSPEED: 14400,
|
||||
}
|
||||
if err := session.RequestPty("xterm", h, w, modes); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = session.Shell(); err != nil {
|
||||
return err
|
||||
}
|
||||
return session.Wait()
|
||||
}
|
||||
|
||||
func remoteInstallKubevpnIfCommandNotFound(ctx context.Context, sshConfig *util.SshConfig) error {
|
||||
cmd := `hash kubevpn1 || type kubevpn1 || which kubevpn1 || command -v kubevpn1`
|
||||
_, _, err := util.RemoteRun(sshConfig, cmd, nil)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
log.Infof("remote kubevpn command not found, try to install it...")
|
||||
var client = http.DefaultClient
|
||||
if config.GitHubOAuthToken != "" {
|
||||
client = oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{AccessToken: config.GitHubOAuthToken, TokenType: "Bearer"}))
|
||||
}
|
||||
latestVersion, latestCommit, url, err := upgrade.GetManifest(client, "linux", "amd64")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("The latest version is: %s, commit: %s\n", latestVersion, latestCommit)
|
||||
var temp *os.File
|
||||
temp, err = os.CreateTemp("", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = temp.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = upgrade.Download(client, url, temp.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var tempBin *os.File
|
||||
tempBin, err = os.CreateTemp("", "kubevpn")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tempBin.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = upgrade.UnzipKubeVPNIntoFile(temp.Name(), tempBin.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// scp kubevpn to remote ssh server and run daemon
|
||||
err = os.Chmod(tempBin.Name(), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Remove(temp.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("Upgrade daemon...")
|
||||
err = util.SCP(sshConfig, tempBin.Name(), "/usr/local/bin/kubevpn")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// try to startup daemon process
|
||||
go util.RemoteRun(sshConfig, "kubevpn get pods", nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
http.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
||||
sshConfig := util.SshConfig{
|
||||
Addr: conn.Request().Header.Get("ssh-addr"),
|
||||
User: conn.Request().Header.Get("ssh-username"),
|
||||
Password: conn.Request().Header.Get("ssh-password"),
|
||||
Keyfile: conn.Request().Header.Get("ssh-keyfile"),
|
||||
ConfigAlias: conn.Request().Header.Get("ssh-alias"),
|
||||
}
|
||||
Ws(conn, &sshConfig)
|
||||
}))
|
||||
}
|
16
pkg/daemon/handler/ssh_test.go
Normal file
16
pkg/daemon/handler/ssh_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/util"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestName(t *testing.T) {
|
||||
cmd := fmt.Sprintf(`hash kubevpn1 || type kubevpn1 || which kubevpn1 || command -v kubevpn1`)
|
||||
serverIP, stderr, err := util.RemoteRun(&util.SshConfig{
|
||||
ConfigAlias: "ry-dev-agd",
|
||||
}, cmd, nil)
|
||||
|
||||
fmt.Println(string(serverIP), string(stderr), err)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,9 @@ service Daemon {
|
||||
|
||||
rpc ConfigAdd (ConfigAddRequest) returns (ConfigAddResponse) {}
|
||||
rpc ConfigRemove (ConfigRemoveRequest) returns (ConfigRemoveResponse) {}
|
||||
rpc SshStart (SshStartRequest) returns (SshStartResponse) {}
|
||||
rpc SshStop (SshStopRequest) returns (SshStopResponse) {}
|
||||
rpc SshConnect (stream SshConnectRequest) returns (stream SshConnectResponse) {}
|
||||
|
||||
rpc Logs (LogRequest) returns (stream LogResponse) {}
|
||||
rpc List (ListRequest) returns (ListResponse) {}
|
||||
@@ -143,6 +146,32 @@ message ConfigAddRequest {
|
||||
SshJump SshJump = 3;
|
||||
}
|
||||
|
||||
message SshStartRequest {
|
||||
string ClientIP = 1;
|
||||
}
|
||||
|
||||
message SshStartResponse {
|
||||
string ServerIP = 1;
|
||||
}
|
||||
|
||||
message SshStopRequest {
|
||||
string ClientIP = 1;
|
||||
}
|
||||
|
||||
message SshStopResponse {
|
||||
string ServerIP = 1;
|
||||
}
|
||||
|
||||
message SshConnectRequest {
|
||||
string Stdin = 1;
|
||||
SshJump SshJump = 2;
|
||||
}
|
||||
|
||||
message SshConnectResponse {
|
||||
string Stdout = 1;
|
||||
string Stderr = 2;
|
||||
}
|
||||
|
||||
message ConfigAddResponse {
|
||||
string ClusterID = 1;
|
||||
}
|
||||
|
@@ -28,6 +28,9 @@ const (
|
||||
Daemon_Remove_FullMethodName = "/rpc.Daemon/Remove"
|
||||
Daemon_ConfigAdd_FullMethodName = "/rpc.Daemon/ConfigAdd"
|
||||
Daemon_ConfigRemove_FullMethodName = "/rpc.Daemon/ConfigRemove"
|
||||
Daemon_SshStart_FullMethodName = "/rpc.Daemon/SshStart"
|
||||
Daemon_SshStop_FullMethodName = "/rpc.Daemon/SshStop"
|
||||
Daemon_SshConnect_FullMethodName = "/rpc.Daemon/SshConnect"
|
||||
Daemon_Logs_FullMethodName = "/rpc.Daemon/Logs"
|
||||
Daemon_List_FullMethodName = "/rpc.Daemon/List"
|
||||
Daemon_Get_FullMethodName = "/rpc.Daemon/Get"
|
||||
@@ -50,6 +53,9 @@ type DaemonClient interface {
|
||||
Remove(ctx context.Context, in *RemoveRequest, opts ...grpc.CallOption) (Daemon_RemoveClient, error)
|
||||
ConfigAdd(ctx context.Context, in *ConfigAddRequest, opts ...grpc.CallOption) (*ConfigAddResponse, error)
|
||||
ConfigRemove(ctx context.Context, in *ConfigRemoveRequest, opts ...grpc.CallOption) (*ConfigRemoveResponse, error)
|
||||
SshStart(ctx context.Context, in *SshStartRequest, opts ...grpc.CallOption) (*SshStartResponse, error)
|
||||
SshStop(ctx context.Context, in *SshStopRequest, opts ...grpc.CallOption) (*SshStopResponse, error)
|
||||
SshConnect(ctx context.Context, opts ...grpc.CallOption) (Daemon_SshConnectClient, error)
|
||||
Logs(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (Daemon_LogsClient, error)
|
||||
List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error)
|
||||
Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error)
|
||||
@@ -309,8 +315,57 @@ func (c *daemonClient) ConfigRemove(ctx context.Context, in *ConfigRemoveRequest
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *daemonClient) SshStart(ctx context.Context, in *SshStartRequest, opts ...grpc.CallOption) (*SshStartResponse, error) {
|
||||
out := new(SshStartResponse)
|
||||
err := c.cc.Invoke(ctx, Daemon_SshStart_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *daemonClient) SshStop(ctx context.Context, in *SshStopRequest, opts ...grpc.CallOption) (*SshStopResponse, error) {
|
||||
out := new(SshStopResponse)
|
||||
err := c.cc.Invoke(ctx, Daemon_SshStop_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *daemonClient) SshConnect(ctx context.Context, opts ...grpc.CallOption) (Daemon_SshConnectClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &Daemon_ServiceDesc.Streams[7], Daemon_SshConnect_FullMethodName, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &daemonSshConnectClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type Daemon_SshConnectClient interface {
|
||||
Send(*SshConnectRequest) error
|
||||
Recv() (*SshConnectResponse, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type daemonSshConnectClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *daemonSshConnectClient) Send(m *SshConnectRequest) error {
|
||||
return x.ClientStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *daemonSshConnectClient) Recv() (*SshConnectResponse, error) {
|
||||
m := new(SshConnectResponse)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *daemonClient) Logs(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (Daemon_LogsClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &Daemon_ServiceDesc.Streams[7], Daemon_Logs_FullMethodName, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &Daemon_ServiceDesc.Streams[8], Daemon_Logs_FullMethodName, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -387,7 +442,7 @@ func (c *daemonClient) Version(ctx context.Context, in *VersionRequest, opts ...
|
||||
}
|
||||
|
||||
func (c *daemonClient) Quit(ctx context.Context, in *QuitRequest, opts ...grpc.CallOption) (Daemon_QuitClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &Daemon_ServiceDesc.Streams[8], Daemon_Quit_FullMethodName, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &Daemon_ServiceDesc.Streams[9], Daemon_Quit_FullMethodName, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -431,6 +486,9 @@ type DaemonServer interface {
|
||||
Remove(*RemoveRequest, Daemon_RemoveServer) error
|
||||
ConfigAdd(context.Context, *ConfigAddRequest) (*ConfigAddResponse, error)
|
||||
ConfigRemove(context.Context, *ConfigRemoveRequest) (*ConfigRemoveResponse, error)
|
||||
SshStart(context.Context, *SshStartRequest) (*SshStartResponse, error)
|
||||
SshStop(context.Context, *SshStopRequest) (*SshStopResponse, error)
|
||||
SshConnect(Daemon_SshConnectServer) error
|
||||
Logs(*LogRequest, Daemon_LogsServer) error
|
||||
List(context.Context, *ListRequest) (*ListResponse, error)
|
||||
Get(context.Context, *GetRequest) (*GetResponse, error)
|
||||
@@ -472,6 +530,15 @@ func (UnimplementedDaemonServer) ConfigAdd(context.Context, *ConfigAddRequest) (
|
||||
func (UnimplementedDaemonServer) ConfigRemove(context.Context, *ConfigRemoveRequest) (*ConfigRemoveResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ConfigRemove not implemented")
|
||||
}
|
||||
func (UnimplementedDaemonServer) SshStart(context.Context, *SshStartRequest) (*SshStartResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SshStart not implemented")
|
||||
}
|
||||
func (UnimplementedDaemonServer) SshStop(context.Context, *SshStopRequest) (*SshStopResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SshStop not implemented")
|
||||
}
|
||||
func (UnimplementedDaemonServer) SshConnect(Daemon_SshConnectServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method SshConnect not implemented")
|
||||
}
|
||||
func (UnimplementedDaemonServer) Logs(*LogRequest, Daemon_LogsServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method Logs not implemented")
|
||||
}
|
||||
@@ -689,6 +756,68 @@ func _Daemon_ConfigRemove_Handler(srv interface{}, ctx context.Context, dec func
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Daemon_SshStart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SshStartRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DaemonServer).SshStart(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Daemon_SshStart_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DaemonServer).SshStart(ctx, req.(*SshStartRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Daemon_SshStop_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SshStopRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DaemonServer).SshStop(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Daemon_SshStop_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DaemonServer).SshStop(ctx, req.(*SshStopRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Daemon_SshConnect_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(DaemonServer).SshConnect(&daemonSshConnectServer{stream})
|
||||
}
|
||||
|
||||
type Daemon_SshConnectServer interface {
|
||||
Send(*SshConnectResponse) error
|
||||
Recv() (*SshConnectRequest, error)
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type daemonSshConnectServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *daemonSshConnectServer) Send(m *SshConnectResponse) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *daemonSshConnectServer) Recv() (*SshConnectRequest, error) {
|
||||
m := new(SshConnectRequest)
|
||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func _Daemon_Logs_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(LogRequest)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
@@ -836,6 +965,14 @@ var Daemon_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "ConfigRemove",
|
||||
Handler: _Daemon_ConfigRemove_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SshStart",
|
||||
Handler: _Daemon_SshStart_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SshStop",
|
||||
Handler: _Daemon_SshStop_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "List",
|
||||
Handler: _Daemon_List_Handler,
|
||||
@@ -893,6 +1030,12 @@ var Daemon_ServiceDesc = grpc.ServiceDesc{
|
||||
Handler: _Daemon_Remove_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "SshConnect",
|
||||
Handler: _Daemon_SshConnect_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "Logs",
|
||||
Handler: _Daemon_Logs_Handler,
|
||||
|
61
pkg/daemon/ws_test.go
Normal file
61
pkg/daemon/ws_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestName(t *testing.T) {
|
||||
httpc := http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
var d net.Dialer
|
||||
d.Timeout = 30 * time.Second
|
||||
d.KeepAlive = 30 * time.Second
|
||||
return d.DialContext(ctx, "unix", GetSockPath(false))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := httpc.Get("http://test" + "/ws")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
//c.Transport = transport // use the unix dialer
|
||||
//uri := fmt.Sprintf("http://%s/%s", daemon.GetSockPath(false), "ws")
|
||||
//resp, err := c.Get(uri)
|
||||
//if err != nil {
|
||||
// fmt.Println(err.Error())
|
||||
// return
|
||||
//}
|
||||
all, _ := io.ReadAll(resp.Body)
|
||||
fmt.Println(string(all))
|
||||
}
|
||||
|
||||
type unixDialer struct {
|
||||
net.Dialer
|
||||
}
|
||||
|
||||
// overriding net.Dialer.Dial to force unix socket connection
|
||||
func (d *unixDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
parts := strings.Split(address, ":")
|
||||
return d.Dialer.Dial("unix", parts[0])
|
||||
}
|
||||
|
||||
// copied from http.DefaultTransport with minimal changes
|
||||
var transport http.RoundTripper = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&unixDialer{net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
},
|
||||
}).DialContext,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
}
|
@@ -762,7 +762,7 @@ func SshJump(ctx context.Context, conf *util.SshConfig, flags *pflag.FlagSet, pr
|
||||
fmt.Sprintf("sh -c 'kubectl config view --flatten --raw --kubeconfig %s || minikube kubectl -- config view --flatten --raw --kubeconfig %s'",
|
||||
conf.RemoteKubeconfig,
|
||||
conf.RemoteKubeconfig),
|
||||
[]string{clientcmd.RecommendedConfigPathEnvVar, conf.RemoteKubeconfig},
|
||||
map[string]string{clientcmd.RecommendedConfigPathEnvVar: conf.RemoteKubeconfig},
|
||||
)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, string(errOut))
|
||||
|
@@ -1,109 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/core"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/util"
|
||||
)
|
||||
|
||||
// SSH
|
||||
// 0) remote server install kubevpn if not found
|
||||
// 1) start remote kubevpn server
|
||||
// 2) start local tunnel
|
||||
// 3) ssh terminal
|
||||
func SSH(ctx context.Context, config *util.SshConfig) error {
|
||||
cancel, cancelFunc := context.WithCancel(ctx)
|
||||
defer cancelFunc()
|
||||
err := portMap(ctx, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
stdout, stderr, err := util.RemoteRun(config, fmt.Sprintf(`kubevpn serve -L "tcp://:10800" -L "tun://127.0.0.1:8422?net=223.254.0.123/32"`), nil)
|
||||
if err != nil {
|
||||
log.Errorf("run error: %v", err)
|
||||
log.Errorf("run stdout: %v", string(stdout))
|
||||
log.Errorf("run stderr: %v", string(stderr))
|
||||
cancelFunc()
|
||||
}
|
||||
}()
|
||||
|
||||
r := core.Route{
|
||||
ServeNodes: []string{
|
||||
fmt.Sprintf("tun:/127.0.0.1:8422?net=%s&route=%s", "223.254.0.124/32", "223.254.0.124/32"),
|
||||
},
|
||||
ChainNode: "tcp://172.17.64.35:10800",
|
||||
Retries: 5,
|
||||
}
|
||||
servers, err := Parse(r)
|
||||
if err != nil {
|
||||
log.Errorf("parse route error: %v", err)
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
log.Error(Run(cancel, servers))
|
||||
}()
|
||||
log.Info("tunnel connected")
|
||||
<-cancel.Done()
|
||||
return err
|
||||
}
|
||||
|
||||
func portMap(ctx context.Context, conf *util.SshConfig) (err error) {
|
||||
port := 10800
|
||||
var remote netip.AddrPort
|
||||
remote, err = netip.ParseAddrPort(net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var local netip.AddrPort
|
||||
local, err = netip.ParseAddrPort(net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// pre-check network ip connect
|
||||
var cli *ssh.Client
|
||||
cli, err = util.DialSshRemote(conf)
|
||||
if err != nil {
|
||||
return
|
||||
} else {
|
||||
_ = cli.Close()
|
||||
}
|
||||
errChan := make(chan error, 1)
|
||||
readyChan := make(chan struct{}, 1)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
err := util.Main(ctx, remote, local, conf, readyChan)
|
||||
if err != nil {
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
log.Errorf("ssh forward failed err: %v", err)
|
||||
}
|
||||
select {
|
||||
case errChan <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-readyChan:
|
||||
return
|
||||
case err = <-errChan:
|
||||
log.Errorf("ssh proxy err: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
@@ -33,7 +33,7 @@ import (
|
||||
// 6) check permission of putting new kubevpn back
|
||||
// 7) chmod +x, move old to /temp, move new to CURRENT_FOLDER
|
||||
func Main(ctx context.Context, current string, commit string, client *http.Client) error {
|
||||
latestVersion, latestCommit, url, err := getManifest(client)
|
||||
latestVersion, latestCommit, url, err := GetManifest(client, runtime.GOOS, runtime.GOARCH)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -84,7 +84,7 @@ func Main(ctx context.Context, current string, commit string, client *http.Clien
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = download(client, url, temp.Name())
|
||||
err = Download(client, url, temp.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func Main(ctx context.Context, current string, commit string, client *http.Clien
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = unzipKubeVPNIntoFile(temp.Name(), file.Name())
|
||||
err = UnzipKubeVPNIntoFile(temp.Name(), file.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -147,7 +147,7 @@ func Main(ctx context.Context, current string, commit string, client *http.Clien
|
||||
return err
|
||||
}
|
||||
|
||||
func getManifest(httpCli *http.Client) (version string, commit string, url string, err error) {
|
||||
func GetManifest(httpCli *http.Client, os string, arch string) (version string, commit string, url string, err error) {
|
||||
var resp *http.Response
|
||||
resp, err = httpCli.Get("https://api.github.com/repos/KubeNetworks/kubevpn/releases/latest")
|
||||
if err != nil {
|
||||
@@ -169,7 +169,7 @@ func getManifest(httpCli *http.Client) (version string, commit string, url strin
|
||||
version = m.TagName
|
||||
commit = m.TargetCommitish
|
||||
for _, asset := range m.Assets {
|
||||
if strings.Contains(asset.Name, runtime.GOARCH) && strings.Contains(asset.Name, runtime.GOOS) {
|
||||
if strings.Contains(asset.Name, arch) && strings.Contains(asset.Name, os) {
|
||||
url = asset.BrowserDownloadUrl
|
||||
break
|
||||
}
|
||||
@@ -177,9 +177,9 @@ func getManifest(httpCli *http.Client) (version string, commit string, url strin
|
||||
if len(url) == 0 {
|
||||
var found bool
|
||||
// if os is not windows and darwin, default is linux
|
||||
if !sets.New[string]("windows", "darwin").Has(runtime.GOOS) {
|
||||
if !sets.New[string]("windows", "darwin").Has(os) {
|
||||
for _, asset := range m.Assets {
|
||||
if strings.Contains(asset.Name, "linux") && strings.Contains(asset.Name, runtime.GOARCH) {
|
||||
if strings.Contains(asset.Name, "linux") && strings.Contains(asset.Name, arch) {
|
||||
url = asset.BrowserDownloadUrl
|
||||
found = true
|
||||
break
|
||||
@@ -201,7 +201,7 @@ func getManifest(httpCli *http.Client) (version string, commit string, url strin
|
||||
|
||||
// https://api.github.com/repos/KubeNetworks/kubevpn/releases
|
||||
// https://github.com/KubeNetworks/kubevpn/releases/download/v1.1.13/kubevpn-windows-arm64.exe
|
||||
func download(client *http.Client, url string, filename string) error {
|
||||
func Download(client *http.Client, url string, filename string) error {
|
||||
get, err := client.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -238,7 +238,7 @@ func download(client *http.Client, url string, filename string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func unzipKubeVPNIntoFile(zipFile, filename string) error {
|
||||
func UnzipKubeVPNIntoFile(zipFile, filename string) error {
|
||||
archive, err := zip.OpenReader(zipFile)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@@ -45,7 +45,7 @@ func GetClient() (*client.Client, *command.DockerCli, error) {
|
||||
// TransferImage
|
||||
// 1) if not special ssh config, just pull image and tag and push
|
||||
// 2) if special ssh config, pull image, tag image, save image and scp image to remote, load image and push
|
||||
func TransferImage(ctx context.Context, conf *SshConfig, from, to string, out io.Writer) error {
|
||||
func TransferImage(ctx context.Context, conf *SshConfig, remoteTemp, to string, out io.Writer) error {
|
||||
cli, c, err := GetClient()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get docker client: %v", err)
|
||||
@@ -53,15 +53,15 @@ func TransferImage(ctx context.Context, conf *SshConfig, from, to string, out io
|
||||
}
|
||||
// todo add flags? or detect k8s node runtime ?
|
||||
platform := &v1.Platform{Architecture: "amd64", OS: "linux"}
|
||||
err = PullImage(ctx, platform, cli, c, from, out)
|
||||
err = PullImage(ctx, platform, cli, c, remoteTemp, out)
|
||||
if err != nil {
|
||||
log.Errorf("failed to pull image: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = cli.ImageTag(ctx, from, to)
|
||||
err = cli.ImageTag(ctx, remoteTemp, to)
|
||||
if err != nil {
|
||||
log.Errorf("failed to tag image %s to %s: %v", from, to, err)
|
||||
log.Errorf("failed to tag image %s to %s: %v", remoteTemp, to, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -130,12 +130,13 @@ func TransferImage(ctx context.Context, conf *SshConfig, from, to string, out io
|
||||
defer os.Remove(file.Name())
|
||||
|
||||
logrus.Infof("Transfering image %s", to)
|
||||
remoteTemp = filepath.Join("/tmp", filepath.Base(file.Name()))
|
||||
cmd := fmt.Sprintf(
|
||||
"(docker load image -i kubevpndir/%s && docker push %s) || (nerdctl image load -i kubevpndir/%s && nerdctl image push %s)",
|
||||
filepath.Base(file.Name()), to,
|
||||
filepath.Base(file.Name()), to,
|
||||
"(docker load image -i %s && docker push %s) || (nerdctl image load -i %s && nerdctl image push %s)",
|
||||
remoteTemp, to,
|
||||
remoteTemp, to,
|
||||
)
|
||||
err = SCP(conf, file.Name(), []string{cmd}...)
|
||||
err = SCP(conf, file.Name(), remoteTemp, []string{cmd}...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -6,7 +6,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cilium/ipam/service/allocator"
|
||||
"github.com/cilium/ipam/service/ipallocator"
|
||||
"github.com/prometheus-community/pro-bing"
|
||||
|
||||
"github.com/wencaiwulue/kubevpn/pkg/config"
|
||||
)
|
||||
|
||||
func GetTunDevice(ips ...net.IP) (*net.Interface, error) {
|
||||
@@ -79,3 +83,29 @@ func IsIPv4(packet []byte) bool {
|
||||
func IsIPv6(packet []byte) bool {
|
||||
return 6 == (packet[0] >> 4)
|
||||
}
|
||||
|
||||
func GetIPBaseNic() (*net.IPNet, error) {
|
||||
addrs, _ := net.InterfaceAddrs()
|
||||
var sum int
|
||||
for _, addr := range addrs {
|
||||
ip, _, _ := net.ParseCIDR(addr.String())
|
||||
for _, b := range ip {
|
||||
sum = sum + int(b)
|
||||
}
|
||||
}
|
||||
dhcp, err := ipallocator.NewAllocatorCIDRRange(config.DockerCIDR, func(max int, rangeSpec string) (allocator.Interface, error) {
|
||||
return allocator.NewContiguousAllocationMap(max, rangeSpec), nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var next net.IP
|
||||
for i := 0; i < sum%255; i++ {
|
||||
next, err = dhcp.AllocateNext()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, bits := config.DockerCIDR.Mask.Size()
|
||||
return &net.IPNet{IP: next, Mask: net.CIDRMask(bits, bits)}, nil
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// SCP copy file to remote and exec command
|
||||
func SCP(conf *SshConfig, filename string, commands ...string) error {
|
||||
func SCP(conf *SshConfig, filename, to string, commands ...string) error {
|
||||
remote, err := DialSshRemote(conf)
|
||||
if err != nil {
|
||||
log.Errorf("Dial into remote server error: %s", err)
|
||||
@@ -23,7 +23,7 @@ func SCP(conf *SshConfig, filename string, commands ...string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = main(sess, filename)
|
||||
err = main(sess, filename, to)
|
||||
if err != nil {
|
||||
log.Errorf("Copy file to remote error: %s", err)
|
||||
return err
|
||||
@@ -45,7 +45,7 @@ func SCP(conf *SshConfig, filename string, commands ...string) error {
|
||||
}
|
||||
|
||||
// https://blog.neilpang.com/%E6%94%B6%E8%97%8F-scp-secure-copy%E5%8D%8F%E8%AE%AE/
|
||||
func main(sess *ssh.Session, filename string) error {
|
||||
func main(sess *ssh.Session, filename string, to string) error {
|
||||
open, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -59,7 +59,7 @@ func main(sess *ssh.Session, filename string) error {
|
||||
go func() {
|
||||
w, _ := sess.StdinPipe()
|
||||
defer w.Close()
|
||||
fmt.Fprintln(w, "D0755", 0, "kubevpndir") // mkdir
|
||||
fmt.Fprintln(w, "D0755", 0, filepath.Dir(to)) // mkdir
|
||||
fmt.Fprintln(w, "C0644", stat.Size(), filepath.Base(filename))
|
||||
err := sCopy(w, open, stat.Size())
|
||||
if err != nil {
|
||||
|
@@ -161,7 +161,7 @@ func DialSshRemote(conf *SshConfig) (*ssh.Client, error) {
|
||||
return remote, err
|
||||
}
|
||||
|
||||
func RemoteRun(conf *SshConfig, cmd string, env []string) (output []byte, errOut []byte, err error) {
|
||||
func RemoteRun(conf *SshConfig, cmd string, env map[string]string) (output []byte, errOut []byte, err error) {
|
||||
var remote *ssh.Client
|
||||
remote, err = DialSshRemote(conf)
|
||||
if err != nil {
|
||||
@@ -174,10 +174,10 @@ func RemoteRun(conf *SshConfig, cmd string, env []string) (output []byte, errOut
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(env) == 2 {
|
||||
for k, v := range env {
|
||||
// /etc/ssh/sshd_config
|
||||
// AcceptEnv DEBIAN_FRONTEND
|
||||
if err = session.Setenv(env[0], env[1]); err != nil {
|
||||
if err = session.Setenv(k, v); err != nil {
|
||||
log.Warn(err)
|
||||
err = nil
|
||||
}
|
||||
|
76
vendor/golang.org/x/crypto/ssh/terminal/terminal.go
generated
vendored
Normal file
76
vendor/golang.org/x/crypto/ssh/terminal/terminal.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package terminal provides support functions for dealing with terminals, as
|
||||
// commonly found on UNIX systems.
|
||||
//
|
||||
// Deprecated: this package moved to golang.org/x/term.
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// EscapeCodes contains escape sequences that can be written to the terminal in
|
||||
// order to achieve different styles of text.
|
||||
type EscapeCodes = term.EscapeCodes
|
||||
|
||||
// Terminal contains the state for running a VT100 terminal that is capable of
|
||||
// reading lines of input.
|
||||
type Terminal = term.Terminal
|
||||
|
||||
// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
|
||||
// a local terminal, that terminal must first have been put into raw mode.
|
||||
// prompt is a string that is written at the start of each input line (i.e.
|
||||
// "> ").
|
||||
func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
|
||||
return term.NewTerminal(c, prompt)
|
||||
}
|
||||
|
||||
// ErrPasteIndicator may be returned from ReadLine as the error, in addition
|
||||
// to valid line data. It indicates that bracketed paste mode is enabled and
|
||||
// that the returned line consists only of pasted data. Programs may wish to
|
||||
// interpret pasted data more literally than typed data.
|
||||
var ErrPasteIndicator = term.ErrPasteIndicator
|
||||
|
||||
// State contains the state of a terminal.
|
||||
type State = term.State
|
||||
|
||||
// IsTerminal returns whether the given file descriptor is a terminal.
|
||||
func IsTerminal(fd int) bool {
|
||||
return term.IsTerminal(fd)
|
||||
}
|
||||
|
||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||
// returned does not include the \n.
|
||||
func ReadPassword(fd int) ([]byte, error) {
|
||||
return term.ReadPassword(fd)
|
||||
}
|
||||
|
||||
// MakeRaw puts the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func MakeRaw(fd int) (*State, error) {
|
||||
return term.MakeRaw(fd)
|
||||
}
|
||||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func Restore(fd int, oldState *State) error {
|
||||
return term.Restore(fd, oldState)
|
||||
}
|
||||
|
||||
// GetState returns the current state of a terminal which may be useful to
|
||||
// restore the terminal after a signal.
|
||||
func GetState(fd int) (*State, error) {
|
||||
return term.GetState(fd)
|
||||
}
|
||||
|
||||
// GetSize returns the dimensions of the given terminal.
|
||||
func GetSize(fd int) (width, height int, err error) {
|
||||
return term.GetSize(fd)
|
||||
}
|
240
vendor/golang.org/x/net/http2/h2c/h2c.go
generated
vendored
Normal file
240
vendor/golang.org/x/net/http2/h2c/h2c.go
generated
vendored
Normal file
@@ -0,0 +1,240 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package h2c implements the unencrypted "h2c" form of HTTP/2.
|
||||
//
|
||||
// The h2c protocol is the non-TLS version of HTTP/2 which is not available from
|
||||
// net/http or golang.org/x/net/http2.
|
||||
package h2c
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
var (
|
||||
http2VerboseLogs bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
e := os.Getenv("GODEBUG")
|
||||
if strings.Contains(e, "http2debug=1") || strings.Contains(e, "http2debug=2") {
|
||||
http2VerboseLogs = true
|
||||
}
|
||||
}
|
||||
|
||||
// h2cHandler is a Handler which implements h2c by hijacking the HTTP/1 traffic
|
||||
// that should be h2c traffic. There are two ways to begin a h2c connection
|
||||
// (RFC 7540 Section 3.2 and 3.4): (1) Starting with Prior Knowledge - this
|
||||
// works by starting an h2c connection with a string of bytes that is valid
|
||||
// HTTP/1, but unlikely to occur in practice and (2) Upgrading from HTTP/1 to
|
||||
// h2c - this works by using the HTTP/1 Upgrade header to request an upgrade to
|
||||
// h2c. When either of those situations occur we hijack the HTTP/1 connection,
|
||||
// convert it to a HTTP/2 connection and pass the net.Conn to http2.ServeConn.
|
||||
type h2cHandler struct {
|
||||
Handler http.Handler
|
||||
s *http2.Server
|
||||
}
|
||||
|
||||
// NewHandler returns an http.Handler that wraps h, intercepting any h2c
|
||||
// traffic. If a request is an h2c connection, it's hijacked and redirected to
|
||||
// s.ServeConn. Otherwise the returned Handler just forwards requests to h. This
|
||||
// works because h2c is designed to be parseable as valid HTTP/1, but ignored by
|
||||
// any HTTP server that does not handle h2c. Therefore we leverage the HTTP/1
|
||||
// compatible parts of the Go http library to parse and recognize h2c requests.
|
||||
// Once a request is recognized as h2c, we hijack the connection and convert it
|
||||
// to an HTTP/2 connection which is understandable to s.ServeConn. (s.ServeConn
|
||||
// understands HTTP/2 except for the h2c part of it.)
|
||||
//
|
||||
// The first request on an h2c connection is read entirely into memory before
|
||||
// the Handler is called. To limit the memory consumed by this request, wrap
|
||||
// the result of NewHandler in an http.MaxBytesHandler.
|
||||
func NewHandler(h http.Handler, s *http2.Server) http.Handler {
|
||||
return &h2cHandler{
|
||||
Handler: h,
|
||||
s: s,
|
||||
}
|
||||
}
|
||||
|
||||
// extractServer extracts existing http.Server instance from http.Request or create an empty http.Server
|
||||
func extractServer(r *http.Request) *http.Server {
|
||||
server, ok := r.Context().Value(http.ServerContextKey).(*http.Server)
|
||||
if ok {
|
||||
return server
|
||||
}
|
||||
return new(http.Server)
|
||||
}
|
||||
|
||||
// ServeHTTP implement the h2c support that is enabled by h2c.GetH2CHandler.
|
||||
func (s h2cHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Handle h2c with prior knowledge (RFC 7540 Section 3.4)
|
||||
if r.Method == "PRI" && len(r.Header) == 0 && r.URL.Path == "*" && r.Proto == "HTTP/2.0" {
|
||||
if http2VerboseLogs {
|
||||
log.Print("h2c: attempting h2c with prior knowledge.")
|
||||
}
|
||||
conn, err := initH2CWithPriorKnowledge(w)
|
||||
if err != nil {
|
||||
if http2VerboseLogs {
|
||||
log.Printf("h2c: error h2c with prior knowledge: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
s.s.ServeConn(conn, &http2.ServeConnOpts{
|
||||
Context: r.Context(),
|
||||
BaseConfig: extractServer(r),
|
||||
Handler: s.Handler,
|
||||
SawClientPreface: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
// Handle Upgrade to h2c (RFC 7540 Section 3.2)
|
||||
if isH2CUpgrade(r.Header) {
|
||||
conn, settings, err := h2cUpgrade(w, r)
|
||||
if err != nil {
|
||||
if http2VerboseLogs {
|
||||
log.Printf("h2c: error h2c upgrade: %v", err)
|
||||
}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
s.s.ServeConn(conn, &http2.ServeConnOpts{
|
||||
Context: r.Context(),
|
||||
BaseConfig: extractServer(r),
|
||||
Handler: s.Handler,
|
||||
UpgradeRequest: r,
|
||||
Settings: settings,
|
||||
})
|
||||
return
|
||||
}
|
||||
s.Handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// initH2CWithPriorKnowledge implements creating a h2c connection with prior
|
||||
// knowledge (Section 3.4) and creates a net.Conn suitable for http2.ServeConn.
|
||||
// All we have to do is look for the client preface that is suppose to be part
|
||||
// of the body, and reforward the client preface on the net.Conn this function
|
||||
// creates.
|
||||
func initH2CWithPriorKnowledge(w http.ResponseWriter) (net.Conn, error) {
|
||||
hijacker, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
return nil, errors.New("h2c: connection does not support Hijack")
|
||||
}
|
||||
conn, rw, err := hijacker.Hijack()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
const expectedBody = "SM\r\n\r\n"
|
||||
|
||||
buf := make([]byte, len(expectedBody))
|
||||
n, err := io.ReadFull(rw, buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("h2c: error reading client preface: %s", err)
|
||||
}
|
||||
|
||||
if string(buf[:n]) == expectedBody {
|
||||
return newBufConn(conn, rw), nil
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
return nil, errors.New("h2c: invalid client preface")
|
||||
}
|
||||
|
||||
// h2cUpgrade establishes a h2c connection using the HTTP/1 upgrade (Section 3.2).
|
||||
func h2cUpgrade(w http.ResponseWriter, r *http.Request) (_ net.Conn, settings []byte, err error) {
|
||||
settings, err = getH2Settings(r.Header)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
hijacker, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
return nil, nil, errors.New("h2c: connection does not support Hijack")
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
r.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
|
||||
conn, rw, err := hijacker.Hijack()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rw.Write([]byte("HTTP/1.1 101 Switching Protocols\r\n" +
|
||||
"Connection: Upgrade\r\n" +
|
||||
"Upgrade: h2c\r\n\r\n"))
|
||||
return newBufConn(conn, rw), settings, nil
|
||||
}
|
||||
|
||||
// isH2CUpgrade returns true if the header properly request an upgrade to h2c
|
||||
// as specified by Section 3.2.
|
||||
func isH2CUpgrade(h http.Header) bool {
|
||||
return httpguts.HeaderValuesContainsToken(h[textproto.CanonicalMIMEHeaderKey("Upgrade")], "h2c") &&
|
||||
httpguts.HeaderValuesContainsToken(h[textproto.CanonicalMIMEHeaderKey("Connection")], "HTTP2-Settings")
|
||||
}
|
||||
|
||||
// getH2Settings returns the settings in the HTTP2-Settings header.
|
||||
func getH2Settings(h http.Header) ([]byte, error) {
|
||||
vals, ok := h[textproto.CanonicalMIMEHeaderKey("HTTP2-Settings")]
|
||||
if !ok {
|
||||
return nil, errors.New("missing HTTP2-Settings header")
|
||||
}
|
||||
if len(vals) != 1 {
|
||||
return nil, fmt.Errorf("expected 1 HTTP2-Settings. Got: %v", vals)
|
||||
}
|
||||
settings, err := base64.RawURLEncoding.DecodeString(vals[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
func newBufConn(conn net.Conn, rw *bufio.ReadWriter) net.Conn {
|
||||
rw.Flush()
|
||||
if rw.Reader.Buffered() == 0 {
|
||||
// If there's no buffered data to be read,
|
||||
// we can just discard the bufio.ReadWriter.
|
||||
return conn
|
||||
}
|
||||
return &bufConn{conn, rw.Reader}
|
||||
}
|
||||
|
||||
// bufConn wraps a net.Conn, but reads drain the bufio.Reader first.
|
||||
type bufConn struct {
|
||||
net.Conn
|
||||
*bufio.Reader
|
||||
}
|
||||
|
||||
func (c *bufConn) Read(p []byte) (int, error) {
|
||||
if c.Reader == nil {
|
||||
return c.Conn.Read(p)
|
||||
}
|
||||
n := c.Reader.Buffered()
|
||||
if n == 0 {
|
||||
c.Reader = nil
|
||||
return c.Conn.Read(p)
|
||||
}
|
||||
if n < len(p) {
|
||||
p = p[:n]
|
||||
}
|
||||
return c.Reader.Read(p)
|
||||
}
|
106
vendor/golang.org/x/net/websocket/client.go
generated
vendored
Normal file
106
vendor/golang.org/x/net/websocket/client.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// DialError is an error that occurs while dialling a websocket server.
|
||||
type DialError struct {
|
||||
*Config
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *DialError) Error() string {
|
||||
return "websocket.Dial " + e.Config.Location.String() + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
// NewConfig creates a new WebSocket config for client connection.
|
||||
func NewConfig(server, origin string) (config *Config, err error) {
|
||||
config = new(Config)
|
||||
config.Version = ProtocolVersionHybi13
|
||||
config.Location, err = url.ParseRequestURI(server)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
config.Origin, err = url.ParseRequestURI(origin)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
config.Header = http.Header(make(map[string][]string))
|
||||
return
|
||||
}
|
||||
|
||||
// NewClient creates a new WebSocket client connection over rwc.
|
||||
func NewClient(config *Config, rwc io.ReadWriteCloser) (ws *Conn, err error) {
|
||||
br := bufio.NewReader(rwc)
|
||||
bw := bufio.NewWriter(rwc)
|
||||
err = hybiClientHandshake(config, br, bw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
buf := bufio.NewReadWriter(br, bw)
|
||||
ws = newHybiClientConn(config, buf, rwc)
|
||||
return
|
||||
}
|
||||
|
||||
// Dial opens a new client connection to a WebSocket.
|
||||
func Dial(url_, protocol, origin string) (ws *Conn, err error) {
|
||||
config, err := NewConfig(url_, origin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if protocol != "" {
|
||||
config.Protocol = []string{protocol}
|
||||
}
|
||||
return DialConfig(config)
|
||||
}
|
||||
|
||||
var portMap = map[string]string{
|
||||
"ws": "80",
|
||||
"wss": "443",
|
||||
}
|
||||
|
||||
func parseAuthority(location *url.URL) string {
|
||||
if _, ok := portMap[location.Scheme]; ok {
|
||||
if _, _, err := net.SplitHostPort(location.Host); err != nil {
|
||||
return net.JoinHostPort(location.Host, portMap[location.Scheme])
|
||||
}
|
||||
}
|
||||
return location.Host
|
||||
}
|
||||
|
||||
// DialConfig opens a new client connection to a WebSocket with a config.
|
||||
func DialConfig(config *Config) (ws *Conn, err error) {
|
||||
var client net.Conn
|
||||
if config.Location == nil {
|
||||
return nil, &DialError{config, ErrBadWebSocketLocation}
|
||||
}
|
||||
if config.Origin == nil {
|
||||
return nil, &DialError{config, ErrBadWebSocketOrigin}
|
||||
}
|
||||
dialer := config.Dialer
|
||||
if dialer == nil {
|
||||
dialer = &net.Dialer{}
|
||||
}
|
||||
client, err = dialWithDialer(dialer, config)
|
||||
if err != nil {
|
||||
goto Error
|
||||
}
|
||||
ws, err = NewClient(config, client)
|
||||
if err != nil {
|
||||
client.Close()
|
||||
goto Error
|
||||
}
|
||||
return
|
||||
|
||||
Error:
|
||||
return nil, &DialError{config, err}
|
||||
}
|
24
vendor/golang.org/x/net/websocket/dial.go
generated
vendored
Normal file
24
vendor/golang.org/x/net/websocket/dial.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
)
|
||||
|
||||
func dialWithDialer(dialer *net.Dialer, config *Config) (conn net.Conn, err error) {
|
||||
switch config.Location.Scheme {
|
||||
case "ws":
|
||||
conn, err = dialer.Dial("tcp", parseAuthority(config.Location))
|
||||
|
||||
case "wss":
|
||||
conn, err = tls.DialWithDialer(dialer, "tcp", parseAuthority(config.Location), config.TlsConfig)
|
||||
|
||||
default:
|
||||
err = ErrBadScheme
|
||||
}
|
||||
return
|
||||
}
|
583
vendor/golang.org/x/net/websocket/hybi.go
generated
vendored
Normal file
583
vendor/golang.org/x/net/websocket/hybi.go
generated
vendored
Normal file
@@ -0,0 +1,583 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
// This file implements a protocol of hybi draft.
|
||||
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
websocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
closeStatusNormal = 1000
|
||||
closeStatusGoingAway = 1001
|
||||
closeStatusProtocolError = 1002
|
||||
closeStatusUnsupportedData = 1003
|
||||
closeStatusFrameTooLarge = 1004
|
||||
closeStatusNoStatusRcvd = 1005
|
||||
closeStatusAbnormalClosure = 1006
|
||||
closeStatusBadMessageData = 1007
|
||||
closeStatusPolicyViolation = 1008
|
||||
closeStatusTooBigData = 1009
|
||||
closeStatusExtensionMismatch = 1010
|
||||
|
||||
maxControlFramePayloadLength = 125
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBadMaskingKey = &ProtocolError{"bad masking key"}
|
||||
ErrBadPongMessage = &ProtocolError{"bad pong message"}
|
||||
ErrBadClosingStatus = &ProtocolError{"bad closing status"}
|
||||
ErrUnsupportedExtensions = &ProtocolError{"unsupported extensions"}
|
||||
ErrNotImplemented = &ProtocolError{"not implemented"}
|
||||
|
||||
handshakeHeader = map[string]bool{
|
||||
"Host": true,
|
||||
"Upgrade": true,
|
||||
"Connection": true,
|
||||
"Sec-Websocket-Key": true,
|
||||
"Sec-Websocket-Origin": true,
|
||||
"Sec-Websocket-Version": true,
|
||||
"Sec-Websocket-Protocol": true,
|
||||
"Sec-Websocket-Accept": true,
|
||||
}
|
||||
)
|
||||
|
||||
// A hybiFrameHeader is a frame header as defined in hybi draft.
|
||||
type hybiFrameHeader struct {
|
||||
Fin bool
|
||||
Rsv [3]bool
|
||||
OpCode byte
|
||||
Length int64
|
||||
MaskingKey []byte
|
||||
|
||||
data *bytes.Buffer
|
||||
}
|
||||
|
||||
// A hybiFrameReader is a reader for hybi frame.
|
||||
type hybiFrameReader struct {
|
||||
reader io.Reader
|
||||
|
||||
header hybiFrameHeader
|
||||
pos int64
|
||||
length int
|
||||
}
|
||||
|
||||
func (frame *hybiFrameReader) Read(msg []byte) (n int, err error) {
|
||||
n, err = frame.reader.Read(msg)
|
||||
if frame.header.MaskingKey != nil {
|
||||
for i := 0; i < n; i++ {
|
||||
msg[i] = msg[i] ^ frame.header.MaskingKey[frame.pos%4]
|
||||
frame.pos++
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (frame *hybiFrameReader) PayloadType() byte { return frame.header.OpCode }
|
||||
|
||||
func (frame *hybiFrameReader) HeaderReader() io.Reader {
|
||||
if frame.header.data == nil {
|
||||
return nil
|
||||
}
|
||||
if frame.header.data.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
return frame.header.data
|
||||
}
|
||||
|
||||
func (frame *hybiFrameReader) TrailerReader() io.Reader { return nil }
|
||||
|
||||
func (frame *hybiFrameReader) Len() (n int) { return frame.length }
|
||||
|
||||
// A hybiFrameReaderFactory creates new frame reader based on its frame type.
|
||||
type hybiFrameReaderFactory struct {
|
||||
*bufio.Reader
|
||||
}
|
||||
|
||||
// NewFrameReader reads a frame header from the connection, and creates new reader for the frame.
|
||||
// See Section 5.2 Base Framing protocol for detail.
|
||||
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17#section-5.2
|
||||
func (buf hybiFrameReaderFactory) NewFrameReader() (frame frameReader, err error) {
|
||||
hybiFrame := new(hybiFrameReader)
|
||||
frame = hybiFrame
|
||||
var header []byte
|
||||
var b byte
|
||||
// First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
|
||||
b, err = buf.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
header = append(header, b)
|
||||
hybiFrame.header.Fin = ((header[0] >> 7) & 1) != 0
|
||||
for i := 0; i < 3; i++ {
|
||||
j := uint(6 - i)
|
||||
hybiFrame.header.Rsv[i] = ((header[0] >> j) & 1) != 0
|
||||
}
|
||||
hybiFrame.header.OpCode = header[0] & 0x0f
|
||||
|
||||
// Second byte. Mask/Payload len(7bits)
|
||||
b, err = buf.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
header = append(header, b)
|
||||
mask := (b & 0x80) != 0
|
||||
b &= 0x7f
|
||||
lengthFields := 0
|
||||
switch {
|
||||
case b <= 125: // Payload length 7bits.
|
||||
hybiFrame.header.Length = int64(b)
|
||||
case b == 126: // Payload length 7+16bits
|
||||
lengthFields = 2
|
||||
case b == 127: // Payload length 7+64bits
|
||||
lengthFields = 8
|
||||
}
|
||||
for i := 0; i < lengthFields; i++ {
|
||||
b, err = buf.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if lengthFields == 8 && i == 0 { // MSB must be zero when 7+64 bits
|
||||
b &= 0x7f
|
||||
}
|
||||
header = append(header, b)
|
||||
hybiFrame.header.Length = hybiFrame.header.Length*256 + int64(b)
|
||||
}
|
||||
if mask {
|
||||
// Masking key. 4 bytes.
|
||||
for i := 0; i < 4; i++ {
|
||||
b, err = buf.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
header = append(header, b)
|
||||
hybiFrame.header.MaskingKey = append(hybiFrame.header.MaskingKey, b)
|
||||
}
|
||||
}
|
||||
hybiFrame.reader = io.LimitReader(buf.Reader, hybiFrame.header.Length)
|
||||
hybiFrame.header.data = bytes.NewBuffer(header)
|
||||
hybiFrame.length = len(header) + int(hybiFrame.header.Length)
|
||||
return
|
||||
}
|
||||
|
||||
// A HybiFrameWriter is a writer for hybi frame.
|
||||
type hybiFrameWriter struct {
|
||||
writer *bufio.Writer
|
||||
|
||||
header *hybiFrameHeader
|
||||
}
|
||||
|
||||
func (frame *hybiFrameWriter) Write(msg []byte) (n int, err error) {
|
||||
var header []byte
|
||||
var b byte
|
||||
if frame.header.Fin {
|
||||
b |= 0x80
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
if frame.header.Rsv[i] {
|
||||
j := uint(6 - i)
|
||||
b |= 1 << j
|
||||
}
|
||||
}
|
||||
b |= frame.header.OpCode
|
||||
header = append(header, b)
|
||||
if frame.header.MaskingKey != nil {
|
||||
b = 0x80
|
||||
} else {
|
||||
b = 0
|
||||
}
|
||||
lengthFields := 0
|
||||
length := len(msg)
|
||||
switch {
|
||||
case length <= 125:
|
||||
b |= byte(length)
|
||||
case length < 65536:
|
||||
b |= 126
|
||||
lengthFields = 2
|
||||
default:
|
||||
b |= 127
|
||||
lengthFields = 8
|
||||
}
|
||||
header = append(header, b)
|
||||
for i := 0; i < lengthFields; i++ {
|
||||
j := uint((lengthFields - i - 1) * 8)
|
||||
b = byte((length >> j) & 0xff)
|
||||
header = append(header, b)
|
||||
}
|
||||
if frame.header.MaskingKey != nil {
|
||||
if len(frame.header.MaskingKey) != 4 {
|
||||
return 0, ErrBadMaskingKey
|
||||
}
|
||||
header = append(header, frame.header.MaskingKey...)
|
||||
frame.writer.Write(header)
|
||||
data := make([]byte, length)
|
||||
for i := range data {
|
||||
data[i] = msg[i] ^ frame.header.MaskingKey[i%4]
|
||||
}
|
||||
frame.writer.Write(data)
|
||||
err = frame.writer.Flush()
|
||||
return length, err
|
||||
}
|
||||
frame.writer.Write(header)
|
||||
frame.writer.Write(msg)
|
||||
err = frame.writer.Flush()
|
||||
return length, err
|
||||
}
|
||||
|
||||
func (frame *hybiFrameWriter) Close() error { return nil }
|
||||
|
||||
type hybiFrameWriterFactory struct {
|
||||
*bufio.Writer
|
||||
needMaskingKey bool
|
||||
}
|
||||
|
||||
func (buf hybiFrameWriterFactory) NewFrameWriter(payloadType byte) (frame frameWriter, err error) {
|
||||
frameHeader := &hybiFrameHeader{Fin: true, OpCode: payloadType}
|
||||
if buf.needMaskingKey {
|
||||
frameHeader.MaskingKey, err = generateMaskingKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &hybiFrameWriter{writer: buf.Writer, header: frameHeader}, nil
|
||||
}
|
||||
|
||||
type hybiFrameHandler struct {
|
||||
conn *Conn
|
||||
payloadType byte
|
||||
}
|
||||
|
||||
func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (frameReader, error) {
|
||||
if handler.conn.IsServerConn() {
|
||||
// The client MUST mask all frames sent to the server.
|
||||
if frame.(*hybiFrameReader).header.MaskingKey == nil {
|
||||
handler.WriteClose(closeStatusProtocolError)
|
||||
return nil, io.EOF
|
||||
}
|
||||
} else {
|
||||
// The server MUST NOT mask all frames.
|
||||
if frame.(*hybiFrameReader).header.MaskingKey != nil {
|
||||
handler.WriteClose(closeStatusProtocolError)
|
||||
return nil, io.EOF
|
||||
}
|
||||
}
|
||||
if header := frame.HeaderReader(); header != nil {
|
||||
io.Copy(ioutil.Discard, header)
|
||||
}
|
||||
switch frame.PayloadType() {
|
||||
case ContinuationFrame:
|
||||
frame.(*hybiFrameReader).header.OpCode = handler.payloadType
|
||||
case TextFrame, BinaryFrame:
|
||||
handler.payloadType = frame.PayloadType()
|
||||
case CloseFrame:
|
||||
return nil, io.EOF
|
||||
case PingFrame, PongFrame:
|
||||
b := make([]byte, maxControlFramePayloadLength)
|
||||
n, err := io.ReadFull(frame, b)
|
||||
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, frame)
|
||||
if frame.PayloadType() == PingFrame {
|
||||
if _, err := handler.WritePong(b[:n]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
return frame, nil
|
||||
}
|
||||
|
||||
func (handler *hybiFrameHandler) WriteClose(status int) (err error) {
|
||||
handler.conn.wio.Lock()
|
||||
defer handler.conn.wio.Unlock()
|
||||
w, err := handler.conn.frameWriterFactory.NewFrameWriter(CloseFrame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(msg, uint16(status))
|
||||
_, err = w.Write(msg)
|
||||
w.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func (handler *hybiFrameHandler) WritePong(msg []byte) (n int, err error) {
|
||||
handler.conn.wio.Lock()
|
||||
defer handler.conn.wio.Unlock()
|
||||
w, err := handler.conn.frameWriterFactory.NewFrameWriter(PongFrame)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err = w.Write(msg)
|
||||
w.Close()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// newHybiConn creates a new WebSocket connection speaking hybi draft protocol.
|
||||
func newHybiConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
|
||||
if buf == nil {
|
||||
br := bufio.NewReader(rwc)
|
||||
bw := bufio.NewWriter(rwc)
|
||||
buf = bufio.NewReadWriter(br, bw)
|
||||
}
|
||||
ws := &Conn{config: config, request: request, buf: buf, rwc: rwc,
|
||||
frameReaderFactory: hybiFrameReaderFactory{buf.Reader},
|
||||
frameWriterFactory: hybiFrameWriterFactory{
|
||||
buf.Writer, request == nil},
|
||||
PayloadType: TextFrame,
|
||||
defaultCloseStatus: closeStatusNormal}
|
||||
ws.frameHandler = &hybiFrameHandler{conn: ws}
|
||||
return ws
|
||||
}
|
||||
|
||||
// generateMaskingKey generates a masking key for a frame.
|
||||
func generateMaskingKey() (maskingKey []byte, err error) {
|
||||
maskingKey = make([]byte, 4)
|
||||
if _, err = io.ReadFull(rand.Reader, maskingKey); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// generateNonce generates a nonce consisting of a randomly selected 16-byte
|
||||
// value that has been base64-encoded.
|
||||
func generateNonce() (nonce []byte) {
|
||||
key := make([]byte, 16)
|
||||
if _, err := io.ReadFull(rand.Reader, key); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
nonce = make([]byte, 24)
|
||||
base64.StdEncoding.Encode(nonce, key)
|
||||
return
|
||||
}
|
||||
|
||||
// removeZone removes IPv6 zone identifier from host.
|
||||
// E.g., "[fe80::1%en0]:8080" to "[fe80::1]:8080"
|
||||
func removeZone(host string) string {
|
||||
if !strings.HasPrefix(host, "[") {
|
||||
return host
|
||||
}
|
||||
i := strings.LastIndex(host, "]")
|
||||
if i < 0 {
|
||||
return host
|
||||
}
|
||||
j := strings.LastIndex(host[:i], "%")
|
||||
if j < 0 {
|
||||
return host
|
||||
}
|
||||
return host[:j] + host[i:]
|
||||
}
|
||||
|
||||
// getNonceAccept computes the base64-encoded SHA-1 of the concatenation of
|
||||
// the nonce ("Sec-WebSocket-Key" value) with the websocket GUID string.
|
||||
func getNonceAccept(nonce []byte) (expected []byte, err error) {
|
||||
h := sha1.New()
|
||||
if _, err = h.Write(nonce); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = h.Write([]byte(websocketGUID)); err != nil {
|
||||
return
|
||||
}
|
||||
expected = make([]byte, 28)
|
||||
base64.StdEncoding.Encode(expected, h.Sum(nil))
|
||||
return
|
||||
}
|
||||
|
||||
// Client handshake described in draft-ietf-hybi-thewebsocket-protocol-17
|
||||
func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
|
||||
bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
|
||||
|
||||
// According to RFC 6874, an HTTP client, proxy, or other
|
||||
// intermediary must remove any IPv6 zone identifier attached
|
||||
// to an outgoing URI.
|
||||
bw.WriteString("Host: " + removeZone(config.Location.Host) + "\r\n")
|
||||
bw.WriteString("Upgrade: websocket\r\n")
|
||||
bw.WriteString("Connection: Upgrade\r\n")
|
||||
nonce := generateNonce()
|
||||
if config.handshakeData != nil {
|
||||
nonce = []byte(config.handshakeData["key"])
|
||||
}
|
||||
bw.WriteString("Sec-WebSocket-Key: " + string(nonce) + "\r\n")
|
||||
bw.WriteString("Origin: " + strings.ToLower(config.Origin.String()) + "\r\n")
|
||||
|
||||
if config.Version != ProtocolVersionHybi13 {
|
||||
return ErrBadProtocolVersion
|
||||
}
|
||||
|
||||
bw.WriteString("Sec-WebSocket-Version: " + fmt.Sprintf("%d", config.Version) + "\r\n")
|
||||
if len(config.Protocol) > 0 {
|
||||
bw.WriteString("Sec-WebSocket-Protocol: " + strings.Join(config.Protocol, ", ") + "\r\n")
|
||||
}
|
||||
// TODO(ukai): send Sec-WebSocket-Extensions.
|
||||
err = config.Header.WriteSubset(bw, handshakeHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bw.WriteString("\r\n")
|
||||
if err = bw.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != 101 {
|
||||
return ErrBadStatus
|
||||
}
|
||||
if strings.ToLower(resp.Header.Get("Upgrade")) != "websocket" ||
|
||||
strings.ToLower(resp.Header.Get("Connection")) != "upgrade" {
|
||||
return ErrBadUpgrade
|
||||
}
|
||||
expectedAccept, err := getNonceAccept(nonce)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Header.Get("Sec-WebSocket-Accept") != string(expectedAccept) {
|
||||
return ErrChallengeResponse
|
||||
}
|
||||
if resp.Header.Get("Sec-WebSocket-Extensions") != "" {
|
||||
return ErrUnsupportedExtensions
|
||||
}
|
||||
offeredProtocol := resp.Header.Get("Sec-WebSocket-Protocol")
|
||||
if offeredProtocol != "" {
|
||||
protocolMatched := false
|
||||
for i := 0; i < len(config.Protocol); i++ {
|
||||
if config.Protocol[i] == offeredProtocol {
|
||||
protocolMatched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !protocolMatched {
|
||||
return ErrBadWebSocketProtocol
|
||||
}
|
||||
config.Protocol = []string{offeredProtocol}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// newHybiClientConn creates a client WebSocket connection after handshake.
|
||||
func newHybiClientConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn {
|
||||
return newHybiConn(config, buf, rwc, nil)
|
||||
}
|
||||
|
||||
// A HybiServerHandshaker performs a server handshake using hybi draft protocol.
|
||||
type hybiServerHandshaker struct {
|
||||
*Config
|
||||
accept []byte
|
||||
}
|
||||
|
||||
func (c *hybiServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) {
|
||||
c.Version = ProtocolVersionHybi13
|
||||
if req.Method != "GET" {
|
||||
return http.StatusMethodNotAllowed, ErrBadRequestMethod
|
||||
}
|
||||
// HTTP version can be safely ignored.
|
||||
|
||||
if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
|
||||
!strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") {
|
||||
return http.StatusBadRequest, ErrNotWebSocket
|
||||
}
|
||||
|
||||
key := req.Header.Get("Sec-Websocket-Key")
|
||||
if key == "" {
|
||||
return http.StatusBadRequest, ErrChallengeResponse
|
||||
}
|
||||
version := req.Header.Get("Sec-Websocket-Version")
|
||||
switch version {
|
||||
case "13":
|
||||
c.Version = ProtocolVersionHybi13
|
||||
default:
|
||||
return http.StatusBadRequest, ErrBadWebSocketVersion
|
||||
}
|
||||
var scheme string
|
||||
if req.TLS != nil {
|
||||
scheme = "wss"
|
||||
} else {
|
||||
scheme = "ws"
|
||||
}
|
||||
c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI())
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol"))
|
||||
if protocol != "" {
|
||||
protocols := strings.Split(protocol, ",")
|
||||
for i := 0; i < len(protocols); i++ {
|
||||
c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i]))
|
||||
}
|
||||
}
|
||||
c.accept, err = getNonceAccept([]byte(key))
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
return http.StatusSwitchingProtocols, nil
|
||||
}
|
||||
|
||||
// Origin parses the Origin header in req.
|
||||
// If the Origin header is not set, it returns nil and nil.
|
||||
func Origin(config *Config, req *http.Request) (*url.URL, error) {
|
||||
var origin string
|
||||
switch config.Version {
|
||||
case ProtocolVersionHybi13:
|
||||
origin = req.Header.Get("Origin")
|
||||
}
|
||||
if origin == "" {
|
||||
return nil, nil
|
||||
}
|
||||
return url.ParseRequestURI(origin)
|
||||
}
|
||||
|
||||
func (c *hybiServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) {
|
||||
if len(c.Protocol) > 0 {
|
||||
if len(c.Protocol) != 1 {
|
||||
// You need choose a Protocol in Handshake func in Server.
|
||||
return ErrBadWebSocketProtocol
|
||||
}
|
||||
}
|
||||
buf.WriteString("HTTP/1.1 101 Switching Protocols\r\n")
|
||||
buf.WriteString("Upgrade: websocket\r\n")
|
||||
buf.WriteString("Connection: Upgrade\r\n")
|
||||
buf.WriteString("Sec-WebSocket-Accept: " + string(c.accept) + "\r\n")
|
||||
if len(c.Protocol) > 0 {
|
||||
buf.WriteString("Sec-WebSocket-Protocol: " + c.Protocol[0] + "\r\n")
|
||||
}
|
||||
// TODO(ukai): send Sec-WebSocket-Extensions.
|
||||
if c.Header != nil {
|
||||
err := c.Header.WriteSubset(buf, handshakeHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
buf.WriteString("\r\n")
|
||||
return buf.Flush()
|
||||
}
|
||||
|
||||
func (c *hybiServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
|
||||
return newHybiServerConn(c.Config, buf, rwc, request)
|
||||
}
|
||||
|
||||
// newHybiServerConn returns a new WebSocket connection speaking hybi draft protocol.
|
||||
func newHybiServerConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
|
||||
return newHybiConn(config, buf, rwc, request)
|
||||
}
|
113
vendor/golang.org/x/net/websocket/server.go
generated
vendored
Normal file
113
vendor/golang.org/x/net/websocket/server.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func newServerConn(rwc io.ReadWriteCloser, buf *bufio.ReadWriter, req *http.Request, config *Config, handshake func(*Config, *http.Request) error) (conn *Conn, err error) {
|
||||
var hs serverHandshaker = &hybiServerHandshaker{Config: config}
|
||||
code, err := hs.ReadHandshake(buf.Reader, req)
|
||||
if err == ErrBadWebSocketVersion {
|
||||
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
|
||||
fmt.Fprintf(buf, "Sec-WebSocket-Version: %s\r\n", SupportedProtocolVersion)
|
||||
buf.WriteString("\r\n")
|
||||
buf.WriteString(err.Error())
|
||||
buf.Flush()
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
|
||||
buf.WriteString("\r\n")
|
||||
buf.WriteString(err.Error())
|
||||
buf.Flush()
|
||||
return
|
||||
}
|
||||
if handshake != nil {
|
||||
err = handshake(config, req)
|
||||
if err != nil {
|
||||
code = http.StatusForbidden
|
||||
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
|
||||
buf.WriteString("\r\n")
|
||||
buf.Flush()
|
||||
return
|
||||
}
|
||||
}
|
||||
err = hs.AcceptHandshake(buf.Writer)
|
||||
if err != nil {
|
||||
code = http.StatusBadRequest
|
||||
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
|
||||
buf.WriteString("\r\n")
|
||||
buf.Flush()
|
||||
return
|
||||
}
|
||||
conn = hs.NewServerConn(buf, rwc, req)
|
||||
return
|
||||
}
|
||||
|
||||
// Server represents a server of a WebSocket.
|
||||
type Server struct {
|
||||
// Config is a WebSocket configuration for new WebSocket connection.
|
||||
Config
|
||||
|
||||
// Handshake is an optional function in WebSocket handshake.
|
||||
// For example, you can check, or don't check Origin header.
|
||||
// Another example, you can select config.Protocol.
|
||||
Handshake func(*Config, *http.Request) error
|
||||
|
||||
// Handler handles a WebSocket connection.
|
||||
Handler
|
||||
}
|
||||
|
||||
// ServeHTTP implements the http.Handler interface for a WebSocket
|
||||
func (s Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
s.serveWebSocket(w, req)
|
||||
}
|
||||
|
||||
func (s Server) serveWebSocket(w http.ResponseWriter, req *http.Request) {
|
||||
rwc, buf, err := w.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
panic("Hijack failed: " + err.Error())
|
||||
}
|
||||
// The server should abort the WebSocket connection if it finds
|
||||
// the client did not send a handshake that matches with protocol
|
||||
// specification.
|
||||
defer rwc.Close()
|
||||
conn, err := newServerConn(rwc, buf, req, &s.Config, s.Handshake)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if conn == nil {
|
||||
panic("unexpected nil conn")
|
||||
}
|
||||
s.Handler(conn)
|
||||
}
|
||||
|
||||
// Handler is a simple interface to a WebSocket browser client.
|
||||
// It checks if Origin header is valid URL by default.
|
||||
// You might want to verify websocket.Conn.Config().Origin in the func.
|
||||
// If you use Server instead of Handler, you could call websocket.Origin and
|
||||
// check the origin in your Handshake func. So, if you want to accept
|
||||
// non-browser clients, which do not send an Origin header, set a
|
||||
// Server.Handshake that does not check the origin.
|
||||
type Handler func(*Conn)
|
||||
|
||||
func checkOrigin(config *Config, req *http.Request) (err error) {
|
||||
config.Origin, err = Origin(config, req)
|
||||
if err == nil && config.Origin == nil {
|
||||
return fmt.Errorf("null origin")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ServeHTTP implements the http.Handler interface for a WebSocket
|
||||
func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
s := Server{Handler: h, Handshake: checkOrigin}
|
||||
s.serveWebSocket(w, req)
|
||||
}
|
449
vendor/golang.org/x/net/websocket/websocket.go
generated
vendored
Normal file
449
vendor/golang.org/x/net/websocket/websocket.go
generated
vendored
Normal file
@@ -0,0 +1,449 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package websocket implements a client and server for the WebSocket protocol
|
||||
// as specified in RFC 6455.
|
||||
//
|
||||
// This package currently lacks some features found in an alternative
|
||||
// and more actively maintained WebSocket package:
|
||||
//
|
||||
// https://pkg.go.dev/nhooyr.io/websocket
|
||||
package websocket // import "golang.org/x/net/websocket"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ProtocolVersionHybi13 = 13
|
||||
ProtocolVersionHybi = ProtocolVersionHybi13
|
||||
SupportedProtocolVersion = "13"
|
||||
|
||||
ContinuationFrame = 0
|
||||
TextFrame = 1
|
||||
BinaryFrame = 2
|
||||
CloseFrame = 8
|
||||
PingFrame = 9
|
||||
PongFrame = 10
|
||||
UnknownFrame = 255
|
||||
|
||||
DefaultMaxPayloadBytes = 32 << 20 // 32MB
|
||||
)
|
||||
|
||||
// ProtocolError represents WebSocket protocol errors.
|
||||
type ProtocolError struct {
|
||||
ErrorString string
|
||||
}
|
||||
|
||||
func (err *ProtocolError) Error() string { return err.ErrorString }
|
||||
|
||||
var (
|
||||
ErrBadProtocolVersion = &ProtocolError{"bad protocol version"}
|
||||
ErrBadScheme = &ProtocolError{"bad scheme"}
|
||||
ErrBadStatus = &ProtocolError{"bad status"}
|
||||
ErrBadUpgrade = &ProtocolError{"missing or bad upgrade"}
|
||||
ErrBadWebSocketOrigin = &ProtocolError{"missing or bad WebSocket-Origin"}
|
||||
ErrBadWebSocketLocation = &ProtocolError{"missing or bad WebSocket-Location"}
|
||||
ErrBadWebSocketProtocol = &ProtocolError{"missing or bad WebSocket-Protocol"}
|
||||
ErrBadWebSocketVersion = &ProtocolError{"missing or bad WebSocket Version"}
|
||||
ErrChallengeResponse = &ProtocolError{"mismatch challenge/response"}
|
||||
ErrBadFrame = &ProtocolError{"bad frame"}
|
||||
ErrBadFrameBoundary = &ProtocolError{"not on frame boundary"}
|
||||
ErrNotWebSocket = &ProtocolError{"not websocket protocol"}
|
||||
ErrBadRequestMethod = &ProtocolError{"bad method"}
|
||||
ErrNotSupported = &ProtocolError{"not supported"}
|
||||
)
|
||||
|
||||
// ErrFrameTooLarge is returned by Codec's Receive method if payload size
|
||||
// exceeds limit set by Conn.MaxPayloadBytes
|
||||
var ErrFrameTooLarge = errors.New("websocket: frame payload size exceeds limit")
|
||||
|
||||
// Addr is an implementation of net.Addr for WebSocket.
|
||||
type Addr struct {
|
||||
*url.URL
|
||||
}
|
||||
|
||||
// Network returns the network type for a WebSocket, "websocket".
|
||||
func (addr *Addr) Network() string { return "websocket" }
|
||||
|
||||
// Config is a WebSocket configuration
|
||||
type Config struct {
|
||||
// A WebSocket server address.
|
||||
Location *url.URL
|
||||
|
||||
// A Websocket client origin.
|
||||
Origin *url.URL
|
||||
|
||||
// WebSocket subprotocols.
|
||||
Protocol []string
|
||||
|
||||
// WebSocket protocol version.
|
||||
Version int
|
||||
|
||||
// TLS config for secure WebSocket (wss).
|
||||
TlsConfig *tls.Config
|
||||
|
||||
// Additional header fields to be sent in WebSocket opening handshake.
|
||||
Header http.Header
|
||||
|
||||
// Dialer used when opening websocket connections.
|
||||
Dialer *net.Dialer
|
||||
|
||||
handshakeData map[string]string
|
||||
}
|
||||
|
||||
// serverHandshaker is an interface to handle WebSocket server side handshake.
|
||||
type serverHandshaker interface {
|
||||
// ReadHandshake reads handshake request message from client.
|
||||
// Returns http response code and error if any.
|
||||
ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error)
|
||||
|
||||
// AcceptHandshake accepts the client handshake request and sends
|
||||
// handshake response back to client.
|
||||
AcceptHandshake(buf *bufio.Writer) (err error)
|
||||
|
||||
// NewServerConn creates a new WebSocket connection.
|
||||
NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn)
|
||||
}
|
||||
|
||||
// frameReader is an interface to read a WebSocket frame.
|
||||
type frameReader interface {
|
||||
// Reader is to read payload of the frame.
|
||||
io.Reader
|
||||
|
||||
// PayloadType returns payload type.
|
||||
PayloadType() byte
|
||||
|
||||
// HeaderReader returns a reader to read header of the frame.
|
||||
HeaderReader() io.Reader
|
||||
|
||||
// TrailerReader returns a reader to read trailer of the frame.
|
||||
// If it returns nil, there is no trailer in the frame.
|
||||
TrailerReader() io.Reader
|
||||
|
||||
// Len returns total length of the frame, including header and trailer.
|
||||
Len() int
|
||||
}
|
||||
|
||||
// frameReaderFactory is an interface to creates new frame reader.
|
||||
type frameReaderFactory interface {
|
||||
NewFrameReader() (r frameReader, err error)
|
||||
}
|
||||
|
||||
// frameWriter is an interface to write a WebSocket frame.
|
||||
type frameWriter interface {
|
||||
// Writer is to write payload of the frame.
|
||||
io.WriteCloser
|
||||
}
|
||||
|
||||
// frameWriterFactory is an interface to create new frame writer.
|
||||
type frameWriterFactory interface {
|
||||
NewFrameWriter(payloadType byte) (w frameWriter, err error)
|
||||
}
|
||||
|
||||
type frameHandler interface {
|
||||
HandleFrame(frame frameReader) (r frameReader, err error)
|
||||
WriteClose(status int) (err error)
|
||||
}
|
||||
|
||||
// Conn represents a WebSocket connection.
|
||||
//
|
||||
// Multiple goroutines may invoke methods on a Conn simultaneously.
|
||||
type Conn struct {
|
||||
config *Config
|
||||
request *http.Request
|
||||
|
||||
buf *bufio.ReadWriter
|
||||
rwc io.ReadWriteCloser
|
||||
|
||||
rio sync.Mutex
|
||||
frameReaderFactory
|
||||
frameReader
|
||||
|
||||
wio sync.Mutex
|
||||
frameWriterFactory
|
||||
|
||||
frameHandler
|
||||
PayloadType byte
|
||||
defaultCloseStatus int
|
||||
|
||||
// MaxPayloadBytes limits the size of frame payload received over Conn
|
||||
// by Codec's Receive method. If zero, DefaultMaxPayloadBytes is used.
|
||||
MaxPayloadBytes int
|
||||
}
|
||||
|
||||
// Read implements the io.Reader interface:
|
||||
// it reads data of a frame from the WebSocket connection.
|
||||
// if msg is not large enough for the frame data, it fills the msg and next Read
|
||||
// will read the rest of the frame data.
|
||||
// it reads Text frame or Binary frame.
|
||||
func (ws *Conn) Read(msg []byte) (n int, err error) {
|
||||
ws.rio.Lock()
|
||||
defer ws.rio.Unlock()
|
||||
again:
|
||||
if ws.frameReader == nil {
|
||||
frame, err := ws.frameReaderFactory.NewFrameReader()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ws.frameReader, err = ws.frameHandler.HandleFrame(frame)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if ws.frameReader == nil {
|
||||
goto again
|
||||
}
|
||||
}
|
||||
n, err = ws.frameReader.Read(msg)
|
||||
if err == io.EOF {
|
||||
if trailer := ws.frameReader.TrailerReader(); trailer != nil {
|
||||
io.Copy(ioutil.Discard, trailer)
|
||||
}
|
||||
ws.frameReader = nil
|
||||
goto again
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write implements the io.Writer interface:
|
||||
// it writes data as a frame to the WebSocket connection.
|
||||
func (ws *Conn) Write(msg []byte) (n int, err error) {
|
||||
ws.wio.Lock()
|
||||
defer ws.wio.Unlock()
|
||||
w, err := ws.frameWriterFactory.NewFrameWriter(ws.PayloadType)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err = w.Write(msg)
|
||||
w.Close()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Close implements the io.Closer interface.
|
||||
func (ws *Conn) Close() error {
|
||||
err := ws.frameHandler.WriteClose(ws.defaultCloseStatus)
|
||||
err1 := ws.rwc.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err1
|
||||
}
|
||||
|
||||
// IsClientConn reports whether ws is a client-side connection.
|
||||
func (ws *Conn) IsClientConn() bool { return ws.request == nil }
|
||||
|
||||
// IsServerConn reports whether ws is a server-side connection.
|
||||
func (ws *Conn) IsServerConn() bool { return ws.request != nil }
|
||||
|
||||
// LocalAddr returns the WebSocket Origin for the connection for client, or
|
||||
// the WebSocket location for server.
|
||||
func (ws *Conn) LocalAddr() net.Addr {
|
||||
if ws.IsClientConn() {
|
||||
return &Addr{ws.config.Origin}
|
||||
}
|
||||
return &Addr{ws.config.Location}
|
||||
}
|
||||
|
||||
// RemoteAddr returns the WebSocket location for the connection for client, or
|
||||
// the Websocket Origin for server.
|
||||
func (ws *Conn) RemoteAddr() net.Addr {
|
||||
if ws.IsClientConn() {
|
||||
return &Addr{ws.config.Location}
|
||||
}
|
||||
return &Addr{ws.config.Origin}
|
||||
}
|
||||
|
||||
var errSetDeadline = errors.New("websocket: cannot set deadline: not using a net.Conn")
|
||||
|
||||
// SetDeadline sets the connection's network read & write deadlines.
|
||||
func (ws *Conn) SetDeadline(t time.Time) error {
|
||||
if conn, ok := ws.rwc.(net.Conn); ok {
|
||||
return conn.SetDeadline(t)
|
||||
}
|
||||
return errSetDeadline
|
||||
}
|
||||
|
||||
// SetReadDeadline sets the connection's network read deadline.
|
||||
func (ws *Conn) SetReadDeadline(t time.Time) error {
|
||||
if conn, ok := ws.rwc.(net.Conn); ok {
|
||||
return conn.SetReadDeadline(t)
|
||||
}
|
||||
return errSetDeadline
|
||||
}
|
||||
|
||||
// SetWriteDeadline sets the connection's network write deadline.
|
||||
func (ws *Conn) SetWriteDeadline(t time.Time) error {
|
||||
if conn, ok := ws.rwc.(net.Conn); ok {
|
||||
return conn.SetWriteDeadline(t)
|
||||
}
|
||||
return errSetDeadline
|
||||
}
|
||||
|
||||
// Config returns the WebSocket config.
|
||||
func (ws *Conn) Config() *Config { return ws.config }
|
||||
|
||||
// Request returns the http request upgraded to the WebSocket.
|
||||
// It is nil for client side.
|
||||
func (ws *Conn) Request() *http.Request { return ws.request }
|
||||
|
||||
// Codec represents a symmetric pair of functions that implement a codec.
|
||||
type Codec struct {
|
||||
Marshal func(v interface{}) (data []byte, payloadType byte, err error)
|
||||
Unmarshal func(data []byte, payloadType byte, v interface{}) (err error)
|
||||
}
|
||||
|
||||
// Send sends v marshaled by cd.Marshal as single frame to ws.
|
||||
func (cd Codec) Send(ws *Conn, v interface{}) (err error) {
|
||||
data, payloadType, err := cd.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws.wio.Lock()
|
||||
defer ws.wio.Unlock()
|
||||
w, err := ws.frameWriterFactory.NewFrameWriter(payloadType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(data)
|
||||
w.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
// Receive receives single frame from ws, unmarshaled by cd.Unmarshal and stores
|
||||
// in v. The whole frame payload is read to an in-memory buffer; max size of
|
||||
// payload is defined by ws.MaxPayloadBytes. If frame payload size exceeds
|
||||
// limit, ErrFrameTooLarge is returned; in this case frame is not read off wire
|
||||
// completely. The next call to Receive would read and discard leftover data of
|
||||
// previous oversized frame before processing next frame.
|
||||
func (cd Codec) Receive(ws *Conn, v interface{}) (err error) {
|
||||
ws.rio.Lock()
|
||||
defer ws.rio.Unlock()
|
||||
if ws.frameReader != nil {
|
||||
_, err = io.Copy(ioutil.Discard, ws.frameReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws.frameReader = nil
|
||||
}
|
||||
again:
|
||||
frame, err := ws.frameReaderFactory.NewFrameReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
frame, err = ws.frameHandler.HandleFrame(frame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if frame == nil {
|
||||
goto again
|
||||
}
|
||||
maxPayloadBytes := ws.MaxPayloadBytes
|
||||
if maxPayloadBytes == 0 {
|
||||
maxPayloadBytes = DefaultMaxPayloadBytes
|
||||
}
|
||||
if hf, ok := frame.(*hybiFrameReader); ok && hf.header.Length > int64(maxPayloadBytes) {
|
||||
// payload size exceeds limit, no need to call Unmarshal
|
||||
//
|
||||
// set frameReader to current oversized frame so that
|
||||
// the next call to this function can drain leftover
|
||||
// data before processing the next frame
|
||||
ws.frameReader = frame
|
||||
return ErrFrameTooLarge
|
||||
}
|
||||
payloadType := frame.PayloadType()
|
||||
data, err := ioutil.ReadAll(frame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cd.Unmarshal(data, payloadType, v)
|
||||
}
|
||||
|
||||
func marshal(v interface{}) (msg []byte, payloadType byte, err error) {
|
||||
switch data := v.(type) {
|
||||
case string:
|
||||
return []byte(data), TextFrame, nil
|
||||
case []byte:
|
||||
return data, BinaryFrame, nil
|
||||
}
|
||||
return nil, UnknownFrame, ErrNotSupported
|
||||
}
|
||||
|
||||
func unmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
|
||||
switch data := v.(type) {
|
||||
case *string:
|
||||
*data = string(msg)
|
||||
return nil
|
||||
case *[]byte:
|
||||
*data = msg
|
||||
return nil
|
||||
}
|
||||
return ErrNotSupported
|
||||
}
|
||||
|
||||
/*
|
||||
Message is a codec to send/receive text/binary data in a frame on WebSocket connection.
|
||||
To send/receive text frame, use string type.
|
||||
To send/receive binary frame, use []byte type.
|
||||
|
||||
Trivial usage:
|
||||
|
||||
import "websocket"
|
||||
|
||||
// receive text frame
|
||||
var message string
|
||||
websocket.Message.Receive(ws, &message)
|
||||
|
||||
// send text frame
|
||||
message = "hello"
|
||||
websocket.Message.Send(ws, message)
|
||||
|
||||
// receive binary frame
|
||||
var data []byte
|
||||
websocket.Message.Receive(ws, &data)
|
||||
|
||||
// send binary frame
|
||||
data = []byte{0, 1, 2}
|
||||
websocket.Message.Send(ws, data)
|
||||
*/
|
||||
var Message = Codec{marshal, unmarshal}
|
||||
|
||||
func jsonMarshal(v interface{}) (msg []byte, payloadType byte, err error) {
|
||||
msg, err = json.Marshal(v)
|
||||
return msg, TextFrame, err
|
||||
}
|
||||
|
||||
func jsonUnmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
|
||||
return json.Unmarshal(msg, v)
|
||||
}
|
||||
|
||||
/*
|
||||
JSON is a codec to send/receive JSON data in a frame from a WebSocket connection.
|
||||
|
||||
Trivial usage:
|
||||
|
||||
import "websocket"
|
||||
|
||||
type T struct {
|
||||
Msg string
|
||||
Count int
|
||||
}
|
||||
|
||||
// receive JSON type T
|
||||
var data T
|
||||
websocket.JSON.Receive(ws, &data)
|
||||
|
||||
// send JSON type T
|
||||
websocket.JSON.Send(ws, data)
|
||||
*/
|
||||
var JSON = Codec{jsonMarshal, jsonUnmarshal}
|
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@@ -915,6 +915,7 @@ golang.org/x/crypto/pkcs12/internal/rc2
|
||||
golang.org/x/crypto/poly1305
|
||||
golang.org/x/crypto/ssh
|
||||
golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
|
||||
golang.org/x/crypto/ssh/terminal
|
||||
# golang.org/x/exp v0.0.0-20230725093048-515e97ebf090
|
||||
## explicit; go 1.20
|
||||
golang.org/x/exp/constraints
|
||||
@@ -927,6 +928,7 @@ golang.org/x/net/bpf
|
||||
golang.org/x/net/context
|
||||
golang.org/x/net/http/httpguts
|
||||
golang.org/x/net/http2
|
||||
golang.org/x/net/http2/h2c
|
||||
golang.org/x/net/http2/hpack
|
||||
golang.org/x/net/icmp
|
||||
golang.org/x/net/idna
|
||||
@@ -939,6 +941,7 @@ golang.org/x/net/ipv6
|
||||
golang.org/x/net/proxy
|
||||
golang.org/x/net/route
|
||||
golang.org/x/net/trace
|
||||
golang.org/x/net/websocket
|
||||
# golang.org/x/oauth2 v0.6.0
|
||||
## explicit; go 1.17
|
||||
golang.org/x/oauth2
|
||||
|
Reference in New Issue
Block a user