Skip to content

Commit

Permalink
feat: add ssh outbound (#1087)
Browse files Browse the repository at this point in the history
* feat: add ssh outbound

* fix: Modify the way to get dstAddr

---------

Co-authored-by: trevid <trevidmy@gmail.com>
  • Loading branch information
2 people authored and H1JK committed Mar 8, 2024
1 parent 37b02b1 commit 0bb5568
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 1 deletion.
98 changes: 98 additions & 0 deletions adapter/outbound/ssh.go
Original file line number Diff line number Diff line change
@@ -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
}
7 changes: 7 additions & 0 deletions adapter/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
4 changes: 3 additions & 1 deletion constant/adapters.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const (
Hysteria2
WireGuard
Tuic
Ssh
)

const (
Expand Down Expand Up @@ -222,7 +223,8 @@ func (at AdapterType) String() string {
return "URLTest"
case LoadBalance:
return "LoadBalance"

case Ssh:
return "Ssh"
default:
return "Unknown"
}
Expand Down

0 comments on commit 0bb5568

Please sign in to comment.