Skip to content

Commit

Permalink
v0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
hang666 committed Dec 2, 2022
1 parent 0cf903e commit ce5d086
Show file tree
Hide file tree
Showing 8 changed files with 898 additions and 0 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# S5light
A lightweight socks5 proxy server and install script.

This software supports Windows/MacOS/Centos/Debian/Ubuntu and, in theory, all Linux.

- Support Multiple Validation
- Support for IP address whitelisting

The configuration file using YAML language.

Please fill in the template according to "config.yaml.example", and save it as "config.yaml".

You can also use the "--config" flag to customize the path to the config file.
39 changes: 39 additions & 0 deletions cmd/s5light/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package main

import (
"log"
"os"

"github.com/hang666/s5light"
"github.com/urfave/cli/v2"
)

func main() {
var configPath string

app := &cli.App{
Name: "s5light",
Usage: "A lightweight socks5 proxy server.",
Version: "v0.1.0",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
Value: "",
Usage: "config file path",
Destination: &configPath,
},
},
Action: func(*cli.Context) error {
s5light.SetConfigPath(configPath)
s5light.ReadConfig()
s5light.Server()
return nil
},
}

if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}

}
64 changes: 64 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package s5light

import (
"fmt"

"github.com/spf13/viper"
)

var Accounts []*AccountStruct

var tcp_timeout_default = 60
var udp_timeout_default = 60

type AccountStruct struct {
Username string `yaml:"username" mapstructure:"username"`
Password string `yaml:"password" mapstructure:"password"`
BindAddress string `yaml:"bind_address" mapstructure:"bind_address"`
ReqAddress string `yaml:"req_address" mapstructure:"req_address"`
Whitelist []string `yaml:"whitelist" mapstructure:"whitelist"`
TCPTimeout int `yaml:"tcp_timeout" mapstructure:"tcp_timeout"`
UDPTimeout int `yaml:"udp_timeout" mapstructure:"udp_timeout"`
WhitelistMap WhitelistMapType
}

type WhitelistMapType map[string]bool

var customConfigPath string = ""

func SetConfigPath(path string) {
customConfigPath = path
}

func ReadConfig() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
if customConfigPath == "" {
viper.AddConfigPath("/etc/s5light/")
viper.AddConfigPath("$HOME/.s5light")
viper.AddConfigPath(".")
} else {
viper.SetConfigFile(customConfigPath)
}
err := viper.ReadInConfig()
if err != nil {
panic(fmt.Errorf("fatal error config file: %w", err))
}
viper.UnmarshalKey("accounts", &Accounts)

for _, acc := range Accounts {
if acc.TCPTimeout == 0 {
acc.TCPTimeout = tcp_timeout_default
}
if acc.UDPTimeout == 0 {
acc.UDPTimeout = udp_timeout_default
}
wMap := make(map[string]bool)
for _, w := range acc.Whitelist {
if w != "" {
wMap[w] = true
}
}
acc.WhitelistMap = wMap
}
}
11 changes: 11 additions & 0 deletions config.yaml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
accounts:
- username: "test"
password: "test123456"
bind_address: "0.0.0.0:8080"
req_address: ""
tcp_timeout: 60
udp_timeout: 60
whitelist:
- "192.168.100.1"
- "127.0.0.1"
- bind_address: "0.0.0.0:8081"
34 changes: 34 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module github.com/hang666/s5light

go 1.19

require (
github.com/spf13/viper v1.14.0
github.com/txthinking/socks5 v0.0.0-20220615051428-39268faee3e6
github.com/urfave/cli/v2 v2.23.5
)

require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/afero v1.9.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
golang.org/x/text v0.4.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
494 changes: 494 additions & 0 deletions go.sum

Large diffs are not rendered by default.

213 changes: 213 additions & 0 deletions handle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package s5light

import (
"fmt"
"io"
"log"
"net"
"strings"
"time"

"github.com/txthinking/socks5"
)

type DefaultHandle struct {
whitelistMap WhitelistMapType
}

func checkIsWhitelisted(address string, whitelistMap WhitelistMapType) bool {
//log.Printf("client come in: %s", address)
w_map := whitelistMap
if len(w_map) == 0 {
return true
}
var ok bool
if strings.Contains(address, ":") {
_, ok = w_map[strings.Split(address, ":")[0]]
} else {
_, ok = w_map[address]
}
return ok
}

func (h *DefaultHandle) TCPHandle(s *socks5.Server, c *net.TCPConn, r *socks5.Request) error {
if !checkIsWhitelisted(c.RemoteAddr().String(), h.whitelistMap) {
return fmt.Errorf("%s is not whitelisted", c.RemoteAddr().String())
}
if r.Cmd == socks5.CmdConnect {
rc, err := r.Connect(c)
if err != nil {
return err
}
defer rc.Close()
go func() {
var bf [1024 * 2]byte
for {
if s.TCPTimeout != 0 {
if err := rc.SetDeadline(time.Now().Add(time.Duration(s.TCPTimeout) * time.Second)); err != nil {
return
}
}
i, err := rc.Read(bf[:])
if err != nil {
return
}
if _, err := c.Write(bf[0:i]); err != nil {
return
}
}
}()
var bf [1024 * 2]byte
for {
if s.TCPTimeout != 0 {
if err := c.SetDeadline(time.Now().Add(time.Duration(s.TCPTimeout) * time.Second)); err != nil {
return nil
}
}
i, err := c.Read(bf[:])
if err != nil {
return nil
}
if _, err := rc.Write(bf[0:i]); err != nil {
return nil
}
}
return nil
}
if r.Cmd == socks5.CmdUDP {
caddr, err := r.UDP(c, s.ServerAddr)
if err != nil {
return err
}
ch := make(chan byte)
defer close(ch)
s.AssociatedUDP.Set(caddr.String(), ch, -1)
defer s.AssociatedUDP.Delete(caddr.String())
io.Copy(io.Discard, c)
if socks5.Debug {
log.Printf("A tcp connection that udp %#v associated closed\n", caddr.String())
}
return nil
}
return socks5.ErrUnsupportCmd
}

func (h *DefaultHandle) UDPHandle(s *socks5.Server, addr *net.UDPAddr, d *socks5.Datagram) error {
if !checkIsWhitelisted(string(addr.IP), h.whitelistMap) {
return fmt.Errorf("%s is not whitelisted", string(addr.IP))
}
src := addr.String()
var ch chan byte
if s.LimitUDP {
any, ok := s.AssociatedUDP.Get(src)
if !ok {
return fmt.Errorf("This udp address %s is not associated with tcp", src)
}
ch = any.(chan byte)
}
send := func(ue *socks5.UDPExchange, data []byte) error {
select {
case <-ch:
return fmt.Errorf("This udp address %s is not associated with tcp", src)
default:
_, err := ue.RemoteConn.Write(data)
if err != nil {
return err
}
if socks5.Debug {
log.Printf("Sent UDP data to remote. client: %#v server: %#v remote: %#v data: %#v\n", ue.ClientAddr.String(), ue.RemoteConn.LocalAddr().String(), ue.RemoteConn.RemoteAddr().String(), data)
}
}
return nil
}

dst := d.Address()
var ue *socks5.UDPExchange
iue, ok := s.UDPExchanges.Get(src + dst)
if ok {
ue = iue.(*socks5.UDPExchange)
return send(ue, d.Data)
}

if socks5.Debug {
log.Printf("Call udp: %#v\n", dst)
}
var laddr *net.UDPAddr
any, ok := s.UDPSrc.Get(src + dst)
if ok {
laddr = any.(*net.UDPAddr)
}
raddr, err := net.ResolveUDPAddr("udp", dst)
if err != nil {
return err
}
rc, err := socks5.Dial.DialUDP("udp", laddr, raddr)
if err != nil {
if !strings.Contains(err.Error(), "address already in use") {
return err
}
rc, err = socks5.Dial.DialUDP("udp", nil, raddr)
if err != nil {
return err
}
laddr = nil
}
if laddr == nil {
s.UDPSrc.Set(src+dst, rc.LocalAddr().(*net.UDPAddr), -1)
}
ue = &socks5.UDPExchange{
ClientAddr: addr,
RemoteConn: rc,
}
if socks5.Debug {
log.Printf("Created remote UDP conn for client. client: %#v server: %#v remote: %#v\n", addr.String(), ue.RemoteConn.LocalAddr().String(), d.Address())
}
if err := send(ue, d.Data); err != nil {
ue.RemoteConn.Close()
return err
}
s.UDPExchanges.Set(src+dst, ue, -1)
go func(ue *socks5.UDPExchange, dst string) {
defer func() {
ue.RemoteConn.Close()
s.UDPExchanges.Delete(ue.ClientAddr.String() + dst)
}()
var b [65507]byte
for {
select {
case <-ch:
if socks5.Debug {
log.Printf("The tcp that udp address %s associated closed\n", ue.ClientAddr.String())
}
return
default:
if s.UDPTimeout != 0 {
if err := ue.RemoteConn.SetDeadline(time.Now().Add(time.Duration(s.UDPTimeout) * time.Second)); err != nil {
log.Println(err)
return
}
}
n, err := ue.RemoteConn.Read(b[:])
if err != nil {
return
}
if socks5.Debug {
log.Printf("Got UDP data from remote. client: %#v server: %#v remote: %#v data: %#v\n", ue.ClientAddr.String(), ue.RemoteConn.LocalAddr().String(), ue.RemoteConn.RemoteAddr().String(), b[0:n])
}
a, addr, port, err := socks5.ParseAddress(dst)
if err != nil {
log.Println(err)
return
}
d1 := socks5.NewDatagram(a, addr, port, b[0:n])
if _, err := s.UDPConn.WriteToUDP(d1.Bytes(), ue.ClientAddr); err != nil {
return
}
if socks5.Debug {
log.Printf("Sent Datagram. client: %#v server: %#v remote: %#v data: %#v %#v %#v %#v %#v %#v datagram address: %#v\n", ue.ClientAddr.String(), ue.RemoteConn.LocalAddr().String(), ue.RemoteConn.RemoteAddr().String(), d1.Rsv, d1.Frag, d1.Atyp, d1.DstAddr, d1.DstPort, d1.Data, d1.Address())
}
}
}
}(ue, dst)
return nil
}
Loading

0 comments on commit ce5d086

Please sign in to comment.