mirror of
https://github.com/SagerNet/sing.git
synced 2025-04-04 20:37:40 +03:00
252 lines
6.9 KiB
Go
252 lines
6.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/sagernet/sing"
|
|
"github.com/sagernet/sing/common"
|
|
"github.com/sagernet/sing/common/buf"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
"github.com/sagernet/sing/common/gsync"
|
|
M "github.com/sagernet/sing/common/metadata"
|
|
"github.com/sagernet/sing/common/network"
|
|
"github.com/sagernet/sing/common/random"
|
|
"github.com/sagernet/sing/common/rw"
|
|
"github.com/sagernet/sing/protocol/shadowsocks"
|
|
"github.com/sagernet/sing/protocol/shadowsocks/shadowaead"
|
|
"github.com/sagernet/sing/protocol/shadowsocks/shadowaead_2022"
|
|
"github.com/sagernet/sing/protocol/socks"
|
|
"github.com/sagernet/sing/transport/tcp"
|
|
"github.com/sagernet/sing/transport/udp"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const udpTimeout = 5 * 60
|
|
|
|
type flags struct {
|
|
Server string `json:"server"`
|
|
ServerPort uint16 `json:"server_port"`
|
|
Bind string `json:"local_address"`
|
|
LocalPort uint16 `json:"local_port"`
|
|
Password string `json:"password"`
|
|
Key string `json:"key"`
|
|
Method string `json:"method"`
|
|
Verbose bool `json:"verbose"`
|
|
ConfigFile string
|
|
}
|
|
|
|
func main() {
|
|
f := new(flags)
|
|
|
|
command := &cobra.Command{
|
|
Use: "ss-local",
|
|
Short: "shadowsocks client",
|
|
Version: sing.VersionStr,
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
run(cmd, f)
|
|
},
|
|
}
|
|
|
|
command.Flags().StringVarP(&f.Server, "server", "s", "", "Set the server’s hostname or IP.")
|
|
command.Flags().Uint16VarP(&f.ServerPort, "server-port", "p", 0, "Set the server’s port number.")
|
|
command.Flags().StringVarP(&f.Bind, "local-address", "b", "", "Set the local address.")
|
|
command.Flags().Uint16VarP(&f.LocalPort, "local-port", "l", 0, "Set the local port number.")
|
|
command.Flags().StringVar(&f.Key, "key", "", "Set the key directly. The key should be encoded with URL-safe Base64.")
|
|
command.Flags().StringVarP(&f.Password, "password", "k", "", "Set the password. The server and the client should use the same password.")
|
|
|
|
var supportedCiphers []string
|
|
supportedCiphers = append(supportedCiphers, shadowsocks.MethodNone)
|
|
supportedCiphers = append(supportedCiphers, shadowaead.List...)
|
|
supportedCiphers = append(supportedCiphers, shadowaead_2022.List...)
|
|
|
|
command.Flags().StringVarP(&f.Method, "encrypt-method", "m", "", "Set the cipher.\n\nSupported ciphers:\n\n"+strings.Join(supportedCiphers, "\n"))
|
|
command.Flags().StringVarP(&f.ConfigFile, "config", "c", "", "Use a configuration file.")
|
|
command.Flags().BoolVarP(&f.Verbose, "verbose", "v", false, "Enable verbose mode.")
|
|
|
|
err := command.Execute()
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func run(cmd *cobra.Command, f *flags) {
|
|
s, err := newServer(f)
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
err = s.tcpIn.Start()
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
err = s.udpIn.Start()
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
logrus.Info("server started at ", s.tcpIn.TCPListener.Addr())
|
|
|
|
osSignals := make(chan os.Signal, 1)
|
|
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM)
|
|
<-osSignals
|
|
|
|
s.tcpIn.Close()
|
|
s.udpIn.Close()
|
|
}
|
|
|
|
type server struct {
|
|
tcpIn *tcp.Listener
|
|
udpIn *udp.Listener
|
|
service shadowsocks.Service
|
|
udpNat gsync.Map[string, *net.UDPConn]
|
|
}
|
|
|
|
func (s *server) Start() error {
|
|
err := s.tcpIn.Start()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = s.udpIn.Start()
|
|
return err
|
|
}
|
|
|
|
func (s *server) Close() error {
|
|
s.tcpIn.Close()
|
|
s.udpIn.Close()
|
|
return nil
|
|
}
|
|
|
|
func newServer(f *flags) (*server, error) {
|
|
s := new(server)
|
|
|
|
if f.ConfigFile != "" {
|
|
configFile, err := ioutil.ReadFile(f.ConfigFile)
|
|
if err != nil {
|
|
return nil, E.Cause(err, "read config file")
|
|
}
|
|
flagsNew := new(flags)
|
|
err = json.Unmarshal(configFile, flagsNew)
|
|
if err != nil {
|
|
return nil, E.Cause(err, "decode config file")
|
|
}
|
|
if flagsNew.Server != "" && f.Server == "" {
|
|
f.Server = flagsNew.Server
|
|
}
|
|
if flagsNew.ServerPort != 0 && f.ServerPort == 0 {
|
|
f.ServerPort = flagsNew.ServerPort
|
|
}
|
|
if flagsNew.Bind != "" && f.Bind == "" {
|
|
f.Bind = flagsNew.Bind
|
|
}
|
|
if flagsNew.LocalPort != 0 && f.LocalPort == 0 {
|
|
f.LocalPort = flagsNew.LocalPort
|
|
}
|
|
if flagsNew.Password != "" && f.Password == "" {
|
|
f.Password = flagsNew.Password
|
|
}
|
|
if flagsNew.Key != "" && f.Key == "" {
|
|
f.Key = flagsNew.Key
|
|
}
|
|
if flagsNew.Method != "" && f.Method == "" {
|
|
f.Method = flagsNew.Method
|
|
}
|
|
if flagsNew.Verbose {
|
|
f.Verbose = true
|
|
}
|
|
}
|
|
|
|
if f.Verbose {
|
|
logrus.SetLevel(logrus.TraceLevel)
|
|
}
|
|
|
|
if f.Server == "" {
|
|
return nil, E.New("missing server address")
|
|
} else if f.ServerPort == 0 {
|
|
return nil, E.New("missing server port")
|
|
} else if f.Method == "" {
|
|
return nil, E.New("missing method")
|
|
}
|
|
|
|
var key []byte
|
|
if f.Key != "" {
|
|
kb, err := base64.StdEncoding.DecodeString(f.Key)
|
|
if err != nil {
|
|
return nil, E.Cause(err, "decode key")
|
|
}
|
|
key = kb
|
|
}
|
|
|
|
if f.Method == shadowsocks.MethodNone {
|
|
s.service = shadowsocks.NewNoneService(udpTimeout, s)
|
|
} else if common.Contains(shadowaead.List, f.Method) {
|
|
service, err := shadowaead.NewService(f.Method, key, []byte(f.Password), random.Blake3KeyedHash(), false, udpTimeout, s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.service = service
|
|
} else if common.Contains(shadowaead_2022.List, f.Method) {
|
|
service, err := shadowaead_2022.NewService(f.Method, key, random.Blake3KeyedHash(), udpTimeout, s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.service = service
|
|
} else {
|
|
return nil, E.New("unsupported method " + f.Method)
|
|
}
|
|
|
|
var bind netip.Addr
|
|
if f.Server != "" {
|
|
addr, err := netip.ParseAddr(f.Server)
|
|
if err != nil {
|
|
return nil, E.Cause(err, "bad server address")
|
|
}
|
|
bind = addr
|
|
} else {
|
|
bind = netip.IPv6Unspecified()
|
|
}
|
|
s.tcpIn = tcp.NewTCPListener(netip.AddrPortFrom(bind, f.ServerPort), s)
|
|
s.udpIn = udp.NewUDPListener(netip.AddrPortFrom(bind, f.ServerPort), s)
|
|
return s, nil
|
|
}
|
|
|
|
func (s *server) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
|
if metadata.Protocol != "shadowsocks" {
|
|
logrus.Trace("inbound raw TCP from ", metadata.Source)
|
|
return s.service.NewConnection(ctx, conn, metadata)
|
|
}
|
|
logrus.Info("inbound TCP ", conn.RemoteAddr(), " ==> ", metadata.Destination)
|
|
destConn, err := network.SystemDialer.DialContext(context.Background(), "tcp", metadata.Destination)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return rw.CopyConn(ctx, conn, destConn)
|
|
}
|
|
|
|
func (s *server) NewPacketConnection(conn socks.PacketConn, metadata M.Metadata) error {
|
|
logrus.Info("inbound UDP ", metadata.Source, " ==> ", metadata.Destination)
|
|
udpConn, err := net.ListenUDP("udp", nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return socks.CopyNetPacketConn(context.Background(), udpConn, conn)
|
|
}
|
|
|
|
func (s *server) NewPacket(conn socks.PacketConn, buffer *buf.Buffer, metadata M.Metadata) error {
|
|
logrus.Trace("inbound raw UDP from ", metadata.Source)
|
|
return s.service.NewPacket(conn, buffer, metadata)
|
|
}
|
|
|
|
func (s *server) HandleError(err error) {
|
|
if E.IsClosed(err) {
|
|
return
|
|
}
|
|
logrus.Warn(err)
|
|
}
|