feat: add ssh outbound (#1087)

* feat: add ssh outbound

* fix: Modify the way to get dstAddr

---------

Co-authored-by: trevid <trevidmy@gmail.com>
This commit is contained in:
TreviD 2024-03-08 17:38:27 +08:00 committed by H1JK
parent 37b02b18f7
commit 0bb5568de9
3 changed files with 108 additions and 1 deletions

98
adapter/outbound/ssh.go Normal file
View file

@ -0,0 +1,98 @@
package outbound
import (
"context"
"net"
"os"
"runtime"
"strconv"
CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer"
C "github.com/metacubex/mihomo/constant"
"golang.org/x/crypto/ssh"
)
type Ssh struct {
*Base
option *SshOption
client *ssh.Client
}
type SshOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UserName string `proxy:"username"`
Password string `proxy:"password,omitempty"`
PrivateKey string `proxy:"privateKey,omitempty"`
}
func (h *Ssh) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
c, err := h.client.Dial("tcp", metadata.RemoteAddress())
if err != nil {
return nil, err
}
return NewConn(CN.NewRefConn(c, h), h), nil
}
func closeSsh(h *Ssh) {
if h.client != nil {
_ = h.client.Close()
}
}
func NewSsh(option SshOption) (*Ssh, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
config := ssh.ClientConfig{
User: option.UserName,
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
if option.Password == "" {
b, err := os.ReadFile(option.PrivateKey)
if err != nil {
return nil, err
}
pKey, err := ssh.ParsePrivateKey(b)
if err != nil {
return nil, err
}
config.Auth = []ssh.AuthMethod{
ssh.PublicKeys(pKey),
}
} else {
config.Auth = []ssh.AuthMethod{
ssh.Password(option.Password),
}
}
client, err := ssh.Dial("tcp", addr, &config)
if err != nil {
return nil, err
}
outbound := &Ssh{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Ssh,
udp: true,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
option: &option,
client: client,
}
runtime.SetFinalizer(outbound, closeSsh)
return outbound, nil
}

View file

@ -134,6 +134,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
break
}
proxy = outbound.NewRejectWithOption(*rejectOption)
case "ssh":
sshOption := &outbound.SshOption{}
err = decoder.Decode(mapping, sshOption)
if err != nil {
break
}
proxy, err = outbound.NewSsh(*sshOption)
default:
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
}

View file

@ -41,6 +41,7 @@ const (
Hysteria2
WireGuard
Tuic
Ssh
)
const (
@ -222,7 +223,8 @@ func (at AdapterType) String() string {
return "URLTest"
case LoadBalance:
return "LoadBalance"
case Ssh:
return "Ssh"
default:
return "Unknown"
}