├── .gitignore ├── .gitlab-ci.yml ├── Dockerfile ├── README.md ├── build_linux.sh ├── config.go ├── example.toml ├── init.go ├── interface.go ├── log.go ├── log └── log.go ├── main.go ├── mem.go ├── redis.go ├── resque.go ├── run.go ├── ss.go ├── storage.go ├── utils.go └── web_api.go /.gitignore: -------------------------------------------------------------------------------- 1 | ss-go-mu 2 | mu_linux64 3 | config.toml 4 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # This file is a template, and might need editing before it works on your project. 2 | # Official docker image. 3 | image: docker:latest 4 | services: 5 | - docker:dind 6 | stages: 7 | - build 8 | build: 9 | stage: build 10 | image: gitlab/dind 11 | script: 12 | - export IMAGE_TAG=$(echo -en $CI_BUILD_REF_NAME | tr -c '[:alnum:]_.-' '-') 13 | - docker login -u "$CI_BUILD_USER" -p "$CI_BUILD_TOKEN" $CI_REGISTRY 14 | - docker build --pull -t "$CI_REGISTRY_IMAGE:$IMAGE_TAG" . 15 | - docker push "$CI_REGISTRY_IMAGE:$IMAGE_TAG" 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.8.0 2 | 3 | ## Create a directory and Add Code 4 | RUN mkdir -p /go/src/github.com/catpie/ss-go-mu 5 | WORKDIR /go/src/github.com/catpie/ss-go-mu 6 | ADD . /go/src/github.com/catpie/ss-go-mu 7 | 8 | # Download and install any required third party dependencies into the container. 9 | RUN go-wrapper download 10 | RUN go-wrapper install 11 | 12 | EXPOSE 10000-20000 13 | 14 | # Now tell Docker what command to run when the container starts 15 | CMD ["go-wrapper", "run"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Install 2 | 3 | ### Build from source 4 | 5 | ``` 6 | go get 7 | go build 8 | ``` 9 | 10 | ### Edit config 11 | 12 | ``` 13 | config example.toml config.toml 14 | ``` -------------------------------------------------------------------------------- /build_linux.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o mu_linux64 -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "time" 4 | 5 | var ( 6 | config = new(Config) 7 | cfgFilePath = "config.toml" 8 | ) 9 | 10 | type Config struct { 11 | WebApi WebApiCfg `toml:"web_api"` 12 | Redis RedisCfg `toml:"redis"` 13 | Base BaseCfg `toml:"base"` 14 | } 15 | 16 | type BaseCfg struct { 17 | SyncTime time.Duration `toml:"sync_time"` 18 | } 19 | 20 | type WebApiCfg struct { 21 | Url string `toml:"base_url"` 22 | Token string `toml:"token"` 23 | NodeId int `toml:"node_id"` 24 | } 25 | 26 | type RedisCfg struct { 27 | Host string `toml:"host"` 28 | Pass string `toml:"pass"` 29 | Db int64 `toml:"db"` 30 | } 31 | -------------------------------------------------------------------------------- /example.toml: -------------------------------------------------------------------------------- 1 | [base] 2 | sync_time = 60 3 | 4 | [web_api] 5 | base_url = "http://sspanel.xyz" 6 | token = "token" 7 | node_id = 1 8 | 9 | [redis] 10 | host = "127.0.0.1:6379" 11 | pass = "" 12 | db = 1 -------------------------------------------------------------------------------- /init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/BurntSushi/toml" 5 | "github.com/catpie/musdk-go" 6 | . "github.com/catpie/ss-go-mu/log" 7 | "io/ioutil" 8 | "time" 9 | ) 10 | 11 | func InitConfig() error { 12 | data, err := ioutil.ReadFile(cfgFilePath) 13 | if err != nil { 14 | return err 15 | } 16 | if _, err := toml.Decode(string(data), &config); err != nil { 17 | return err 18 | } 19 | Log.Info(config) 20 | return nil 21 | } 22 | 23 | func InitWebApi() error { 24 | cfg := config.WebApi 25 | WebApiClient = musdk.NewClient(cfg.Url, cfg.Token, cfg.NodeId, musdk.TypeSs) 26 | return nil 27 | } 28 | 29 | func BootSs() error { 30 | storage.ClearAll() 31 | go func() { 32 | for { 33 | CheckUsers() 34 | SubmitTraffic() 35 | time.Sleep(config.Base.SyncTime * time.Second) 36 | } 37 | }() 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /interface.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | ss "github.com/orvice/shadowsocks-go/shadowsocks" 5 | ) 6 | 7 | type LogTraffic func(id, u, d int64) error 8 | 9 | type UserInterface interface { 10 | GetId() int64 11 | GetPort() int 12 | GetPasswd() string 13 | GetMethod() string 14 | IsEnable() bool 15 | GetCipher() (*ss.Cipher, error, bool) 16 | // UpdateTraffic(storageSize int) error 17 | } 18 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package main 2 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "github.com/catpie/logrus" 5 | ) 6 | 7 | var ( 8 | Log = logrus.New() 9 | ) 10 | 11 | func Init() { 12 | Log.Formatter = new(logrus.JSONFormatter) 13 | Log.Formatter = new(logrus.TextFormatter) // default 14 | Log.Level = logrus.DebugLevel 15 | } 16 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | . "github.com/catpie/ss-go-mu/log" 5 | ) 6 | 7 | func main() { 8 | var err error 9 | Log.Info("Start") 10 | err = InitConfig() 11 | if err != nil { 12 | panic(err) 13 | } 14 | InitWebApi() 15 | InitRedis() 16 | BootSs() 17 | 18 | waitSignal() 19 | } 20 | -------------------------------------------------------------------------------- /mem.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var ( 4 | users = make(map[int64]UserInterface) 5 | ) 6 | -------------------------------------------------------------------------------- /redis.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/catpie/musdk-go" 6 | . "github.com/catpie/ss-go-mu/log" 7 | redis "gopkg.in/redis.v5" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | const ( 13 | DefaultExpireTime = 0 14 | DefaultOnlineKeyExpireTime = time.Minute * 5 15 | ) 16 | 17 | var RedisLock = new(sync.Mutex) 18 | 19 | var Redis = new(RedisClient) 20 | 21 | type RedisClient struct { 22 | client *redis.Client 23 | } 24 | 25 | func (r *RedisClient) SetClient(client *redis.Client) { 26 | r.client = client 27 | } 28 | 29 | func (r *RedisClient) GetClient() (client *redis.Client) { 30 | return r.client 31 | } 32 | 33 | func (r *RedisClient) StoreUser(user UserInterface) error { 34 | data, err := json.Marshal(user) 35 | if err != nil { 36 | return err 37 | } 38 | err = r.client.Set(genUserInfoKey(user), data, DefaultExpireTime).Err() 39 | return err 40 | } 41 | 42 | func (r *RedisClient) Exists(u UserInterface) (bool, error) { 43 | return r.client.Exists(genUserInfoKey(u)).Result() 44 | } 45 | 46 | func (r *RedisClient) Del(u UserInterface) error { 47 | return r.client.Del(genUserInfoKey(u)).Err() 48 | } 49 | 50 | func (r *RedisClient) ClearAll() error { 51 | return r.client.FlushAll().Err() 52 | } 53 | 54 | // traffic 55 | func (r *RedisClient) IncrSizeToQueue(u UserInterface, size int) error { 56 | log := musdk.UserTrafficLog{ 57 | UserId: u.GetId(), 58 | D: int64(size), 59 | } 60 | return r.client.LPush(QueueName, log).Err() 61 | } 62 | 63 | func (r *RedisClient) IncrSize(u UserInterface, size int) error { 64 | key := genUserFlowKey(u) 65 | incrSize := int(float32(size)) 66 | isExits, err := r.client.Exists(key).Result() 67 | if err != nil { 68 | return err 69 | } 70 | if !isExits { 71 | return r.client.Set(key, incrSize, DefaultExpireTime).Err() 72 | } 73 | return r.client.IncrBy(key, int64(incrSize)).Err() 74 | } 75 | 76 | func (r *RedisClient) GetSize(u UserInterface) (int64, error) { 77 | key := genUserFlowKey(u) 78 | isExits, err := r.client.Exists(key).Result() 79 | if err != nil { 80 | return 0, err 81 | } 82 | if !isExits { 83 | return 0, nil 84 | } 85 | return r.client.Get(key).Int64() 86 | } 87 | 88 | func (r *RedisClient) SetSize(u UserInterface, size int) error { 89 | key := genUserFlowKey(u) 90 | return r.client.Set(key, size, DefaultExpireTime).Err() 91 | } 92 | 93 | func (r *RedisClient) ClearSize() error { 94 | return nil 95 | } 96 | 97 | func (r *RedisClient) MarkUserOnline(u UserInterface) error { 98 | key := genUserOnlineKey(u) 99 | return r.client.Set(key, "1", DefaultOnlineKeyExpireTime).Err() 100 | } 101 | 102 | func (r *RedisClient) IsUserOnline(u UserInterface) bool { 103 | key := genUserOnlineKey(u) 104 | isExits, err := r.client.Exists(key).Result() 105 | if err != nil { 106 | return false 107 | } 108 | return isExits 109 | } 110 | 111 | func (r *RedisClient) GetOnlineUsersCount(users []UserInterface) int { 112 | count := 0 113 | for _, v := range users { 114 | if r.IsUserOnline(v) { 115 | count++ 116 | } 117 | } 118 | return count 119 | } 120 | 121 | func InitRedis() error { 122 | conf := config.Redis 123 | client := redis.NewClient(&redis.Options{ 124 | Addr: conf.Host, 125 | Password: conf.Pass, // no password set 126 | DB: int(conf.Db), // use default DB 127 | }) 128 | 129 | pong, err := client.Ping().Result() 130 | if err != nil { 131 | return err 132 | } 133 | Log.Info(pong) 134 | Redis.SetClient(client) 135 | // set storage 136 | SetStorage(Redis) 137 | return nil 138 | } 139 | -------------------------------------------------------------------------------- /resque.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/catpie/ss-go-mu/log" 5 | ) 6 | 7 | const ( 8 | QueueName = "ss-go-mu-queue" 9 | ) 10 | 11 | func Pop() { 12 | err := Redis.GetClient().LPop(QueueName).Err() 13 | log.Log.Error(err) 14 | } 15 | -------------------------------------------------------------------------------- /run.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/catpie/musdk-go" 6 | . "github.com/catpie/ss-go-mu/log" 7 | "strconv" 8 | ) 9 | 10 | func RunSs(user UserInterface) error { 11 | runWithCustomMethod(user) 12 | users[user.GetId()] = user 13 | return nil 14 | } 15 | 16 | func StopSs(user UserInterface) error { 17 | passwdManager.del(strconv.Itoa(user.GetPort())) 18 | delete(users, user.GetId()) 19 | return nil 20 | } 21 | 22 | func CheckUser(user UserInterface) error { 23 | Log.Info("check user: ", user) 24 | u, ok := users[user.GetId()] 25 | if !ok { 26 | return func() error { 27 | if user.IsEnable() { 28 | Log.Infof("run user %d", user.GetId()) 29 | return RunSs(user) 30 | } 31 | return nil 32 | }() 33 | } 34 | if !u.IsEnable() { 35 | Log.Infof("disable user %d", u.GetId()) 36 | return StopSs(user) 37 | } 38 | if u.GetPasswd() != user.GetPasswd() || u.GetMethod() != user.GetMethod() { 39 | Log.Infof("%d info is changed... restart ...", user.GetId()) 40 | StopSs(user) 41 | return RunSs(user) 42 | } 43 | return nil 44 | } 45 | 46 | func CheckUsers() error { 47 | Log.Info("check users...") 48 | Log.Info("user in memery: ", users) 49 | us, err := WebApiClient.GetUsers() 50 | Log.Debug(us) 51 | if err != nil { 52 | // handle error 53 | Log.Error(err) 54 | } 55 | 56 | for _, u := range us { 57 | go CheckUser(u) 58 | } 59 | 60 | return nil 61 | } 62 | 63 | func SubmitTraffic() error { 64 | Log.Info("submit traffic....") 65 | users, err := WebApiClient.GetUsers() 66 | if err != nil { 67 | return err 68 | } 69 | var logs []musdk.UserTrafficLog 70 | for _, user := range users { 71 | size, err := storage.GetSize(user) 72 | if err != nil { 73 | Log.Error(fmt.Sprintf("get size fail for port:%d", user.GetPort()), err) 74 | continue 75 | } 76 | if size < 1024 { 77 | continue 78 | } 79 | log := musdk.UserTrafficLog{ 80 | U: 0, 81 | D: size, 82 | UserId: user.GetId(), 83 | } 84 | logs = append(logs, log) 85 | err = storage.SetSize(user, 0) 86 | if err != nil { 87 | Log.Error(fmt.Sprintf("set storage size to 0 fail for port:%d", user.GetPort()), err) 88 | continue 89 | } 90 | } 91 | if len(logs) == 0 { 92 | return nil 93 | } 94 | err = WebApiClient.UpdateTraffic(logs) 95 | if err != nil { 96 | // @todo 97 | return err 98 | } 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /ss.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/binary" 7 | "fmt" 8 | . "github.com/catpie/ss-go-mu/log" 9 | "github.com/cyfdecyf/leakybuf" 10 | "github.com/orvice/shadowsocks-go/mu/user" 11 | ss "github.com/orvice/shadowsocks-go/shadowsocks" 12 | "io" 13 | "net" 14 | "net/http" 15 | "os" 16 | "os/signal" 17 | "strconv" 18 | "strings" 19 | "sync" 20 | "syscall" 21 | "time" 22 | ) 23 | 24 | const ( 25 | idType = 0 // address type index 26 | idIP0 = 1 // ip addres start index 27 | idDmLen = 1 // domain address length index 28 | idDm0 = 2 // domain address start index 29 | 30 | typeIPv4 = 1 // type is ipv4 address 31 | typeDm = 3 // type is domain address 32 | typeIPv6 = 4 // type is ipv6 address 33 | 34 | lenIPv4 = net.IPv4len + 2 // ipv4 + 2port 35 | lenIPv6 = net.IPv6len + 2 // ipv6 + 2port 36 | lenDmBase = 2 // 1addrLen + 2port, plus addrLen 37 | lenHmacSha1 = 10 38 | ) 39 | 40 | var ssdebug ss.DebugLog 41 | 42 | func getRequest(conn *ss.Conn, auth bool) (host string, res_size int, ota bool, err error) { 43 | var n int 44 | ss.SetReadTimeout(conn) 45 | 46 | // buf size should at least have the same size with the largest possible 47 | // request size (when addrType is 3, domain name has at most 256 bytes) 48 | // 1(addrType) + 1(lenByte) + 256(max length address) + 2(port) + 10(hmac-sha1) 49 | buf := make([]byte, 270) 50 | // read till we get possible domain length field 51 | if n, err = io.ReadFull(conn, buf[:idType+1]); err != nil { 52 | return 53 | } 54 | res_size += n 55 | 56 | var reqStart, reqEnd int 57 | addrType := buf[idType] 58 | switch addrType & ss.AddrMask { 59 | case typeIPv4: 60 | reqStart, reqEnd = idIP0, idIP0+lenIPv4 61 | case typeIPv6: 62 | reqStart, reqEnd = idIP0, idIP0+lenIPv6 63 | case typeDm: 64 | if n, err = io.ReadFull(conn, buf[idType+1:idDmLen+1]); err != nil { 65 | return 66 | } 67 | reqStart, reqEnd = idDm0, int(idDm0+buf[idDmLen]+lenDmBase) 68 | default: 69 | err = fmt.Errorf("addr type %d not supported", addrType&ss.AddrMask) 70 | return 71 | } 72 | res_size += n 73 | 74 | if n, err = io.ReadFull(conn, buf[reqStart:reqEnd]); err != nil { 75 | return 76 | } 77 | res_size += n 78 | 79 | // Return string for typeIP is not most efficient, but browsers (Chrome, 80 | // Safari, Firefox) all seems using typeDm exclusively. So this is not a 81 | // big problem. 82 | switch addrType & ss.AddrMask { 83 | case typeIPv4: 84 | host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String() 85 | case typeIPv6: 86 | host = net.IP(buf[idIP0 : idIP0+net.IPv6len]).String() 87 | case typeDm: 88 | host = string(buf[idDm0 : idDm0+buf[idDmLen]]) 89 | } 90 | // parse port 91 | port := binary.BigEndian.Uint16(buf[reqEnd-2 : reqEnd]) 92 | host = net.JoinHostPort(host, strconv.Itoa(int(port))) 93 | // if specified one time auth enabled, we should verify this 94 | if auth || addrType&ss.OneTimeAuthMask > 0 { 95 | ota = true 96 | if n, err = io.ReadFull(conn, buf[reqEnd:reqEnd+lenHmacSha1]); err != nil { 97 | return 98 | } 99 | iv := conn.GetIv() 100 | key := conn.GetKey() 101 | actualHmacSha1Buf := ss.HmacSha1(append(iv, key...), buf[:reqEnd]) 102 | if !bytes.Equal(buf[reqEnd:reqEnd+lenHmacSha1], actualHmacSha1Buf) { 103 | err = fmt.Errorf("verify one time auth failed, iv=%v key=%v data=%v", iv, key, buf[:reqEnd]) 104 | return 105 | } 106 | res_size += n 107 | } 108 | return 109 | } 110 | 111 | const logCntDelta = 100 112 | 113 | var connCnt int 114 | var nextLogConnCnt int = logCntDelta 115 | 116 | func handleConnection(user UserInterface, conn *ss.Conn, auth bool) { 117 | var host string 118 | 119 | connCnt++ // this maybe not accurate, but should be enough 120 | if connCnt-nextLogConnCnt >= 0 { 121 | // XXX There's no xadd in the atomic package, so it's difficult to log 122 | // the message only once with low cost. Also note nextLogConnCnt maybe 123 | // added twice for current peak connection number level. 124 | Log.Debug("Number of client connections reaches %d\n", nextLogConnCnt) 125 | nextLogConnCnt += logCntDelta 126 | } 127 | 128 | // function arguments are always evaluated, so surround debug statement 129 | // with if statement 130 | Log.Debug(fmt.Sprintf("new client %s->%s\n", conn.RemoteAddr().String(), conn.LocalAddr())) 131 | closed := false 132 | defer func() { 133 | if ssdebug { 134 | Log.Debug(fmt.Sprintf("closed pipe %s<->%s\n", conn.RemoteAddr(), host)) 135 | } 136 | connCnt-- 137 | if !closed { 138 | conn.Close() 139 | } 140 | }() 141 | 142 | host, res_size, ota, err := getRequest(conn, auth) 143 | if err != nil { 144 | Log.Error("error getting request", conn.RemoteAddr(), conn.LocalAddr(), err) 145 | return 146 | } 147 | Log.Info(fmt.Sprintf("[port-%d]connecting %s ", user.GetPort(), host)) 148 | remote, err := net.Dial("tcp", host) 149 | if err != nil { 150 | if ne, ok := err.(*net.OpError); ok && (ne.Err == syscall.EMFILE || ne.Err == syscall.ENFILE) { 151 | // log too many open file error 152 | // EMFILE is process reaches open file limits, ENFILE is system limit 153 | Log.Error("dial error:", err) 154 | } else { 155 | Log.Error("error connecting to:", host, err) 156 | } 157 | return 158 | } 159 | defer func() { 160 | if !closed { 161 | remote.Close() 162 | } 163 | }() 164 | 165 | // debug conn info 166 | Log.Debug(fmt.Sprintf("%d conn debug: local addr: %s | remote addr: %s network: %s ", user.GetPort(), 167 | conn.LocalAddr().String(), conn.RemoteAddr().String(), conn.RemoteAddr().Network())) 168 | go func() { 169 | err = storage.IncrSize(user, res_size) 170 | if err != nil { 171 | Log.Error(err) 172 | return 173 | } 174 | err = storage.MarkUserOnline(user) 175 | if err != nil { 176 | Log.Error(err) 177 | return 178 | } 179 | Log.Debug(fmt.Sprintf("[port-%d] store size: %d", user.GetPort(), res_size)) 180 | 181 | Log.Info(fmt.Sprintf("piping %s<->%s ota=%v connOta=%v", conn.RemoteAddr(), host, ota, conn.IsOta())) 182 | 183 | }() 184 | if ota { 185 | go PipeThenCloseOta(conn, remote, false, host, user) 186 | } else { 187 | go PipeThenClose(conn, remote, false, host, user) 188 | } 189 | 190 | PipeThenClose(remote, conn, true, host, user) 191 | closed = true 192 | return 193 | } 194 | 195 | type PortListener struct { 196 | password string 197 | listener net.Listener 198 | } 199 | 200 | type PasswdManager struct { 201 | sync.Mutex 202 | portListener map[string]*PortListener 203 | } 204 | 205 | func (pm *PasswdManager) add(port, password string, listener net.Listener) { 206 | pm.Lock() 207 | pm.portListener[port] = &PortListener{password, listener} 208 | pm.Unlock() 209 | } 210 | 211 | func (pm *PasswdManager) get(port string) (pl *PortListener, ok bool) { 212 | pm.Lock() 213 | pl, ok = pm.portListener[port] 214 | pm.Unlock() 215 | return 216 | } 217 | 218 | func (pm *PasswdManager) del(port string) { 219 | pl, ok := pm.get(port) 220 | if !ok { 221 | return 222 | } 223 | pl.listener.Close() 224 | pm.Lock() 225 | delete(pm.portListener, port) 226 | pm.Unlock() 227 | } 228 | 229 | var passwdManager = PasswdManager{portListener: map[string]*PortListener{}} 230 | 231 | func waitSignal() { 232 | var sigChan = make(chan os.Signal, 1) 233 | signal.Notify(sigChan, syscall.SIGHUP) 234 | for sig := range sigChan { 235 | if sig == syscall.SIGHUP { 236 | } else { 237 | // is this going to happen? 238 | Log.Printf("caught signal %v, exit", sig) 239 | os.Exit(0) 240 | } 241 | } 242 | } 243 | 244 | func runWithCustomMethod(user UserInterface) { 245 | // port, password string, Cipher *ss.Cipher 246 | port := strconv.Itoa(user.GetPort()) 247 | password := user.GetPasswd() 248 | ln, err := net.Listen("tcp", ":"+port) 249 | if err != nil { 250 | Log.Error(fmt.Sprintf("error listening port %v: %v\n", port, err)) 251 | // os.Exit(1) 252 | return 253 | } 254 | passwdManager.add(port, password, ln) 255 | cipher, err, auth := user.GetCipher() 256 | if err != nil { 257 | return 258 | } 259 | Log.Info(fmt.Sprintf("server listening port %v ...\n", port)) 260 | for { 261 | conn, err := ln.Accept() 262 | if err != nil { 263 | // listener maybe closed to update password 264 | Log.Debug(fmt.Sprintf("accept error: %v\n", err)) 265 | return 266 | } 267 | // Creating cipher upon first connection. 268 | if cipher == nil { 269 | Log.Debug("creating cipher for port:", port) 270 | method := user.GetMethod() 271 | 272 | if strings.HasSuffix(method, "-auth") { 273 | method = method[:len(method)-5] 274 | auth = true 275 | } else { 276 | auth = false 277 | } 278 | 279 | cipher, err = ss.NewCipher(method, password) 280 | if err != nil { 281 | Log.Error(fmt.Sprintf("Error generating cipher for port: %s %v\n", port, err)) 282 | conn.Close() 283 | continue 284 | } 285 | } 286 | go handleConnection(user, ss.NewConn(conn, cipher.Copy()), auth) 287 | } 288 | } 289 | 290 | const bufSize = 4096 291 | const nBuf = 2048 292 | 293 | func PipeThenClose(src, dst net.Conn, is_res bool, host string, user UserInterface) { 294 | var pipeBuf = leakybuf.NewLeakyBuf(nBuf, bufSize) 295 | defer dst.Close() 296 | buf := pipeBuf.Get() 297 | // defer pipeBuf.Put(buf) 298 | var size int 299 | 300 | for { 301 | SetReadTimeout(src) 302 | n, err := src.Read(buf) 303 | // read may return EOF with n > 0 304 | // should always process n > 0 bytes before handling error 305 | if n > 0 { 306 | size, err = dst.Write(buf[0:n]) 307 | if is_res { 308 | go func() { 309 | err = storage.IncrSize(user, size) 310 | if err != nil { 311 | Log.Error(err) 312 | } 313 | }() 314 | Log.Debug(fmt.Sprintf("[port-%d] store size: %d", user.GetPort(), size)) 315 | } 316 | if err != nil { 317 | Log.Debug("write:", err) 318 | break 319 | } 320 | } 321 | if err != nil || n == 0 { 322 | // Always "use of closed network connection", but no easy way to 323 | // identify this specific error. So just leave the error along for now. 324 | // More info here: https://code.google.com/p/go/issues/detail?id=4373 325 | break 326 | } 327 | } 328 | return 329 | } 330 | 331 | func PipeThenCloseOta(src *ss.Conn, dst net.Conn, is_res bool, host string, user UserInterface) { 332 | const ( 333 | dataLenLen = 2 334 | hmacSha1Len = 10 335 | idxData0 = dataLenLen + hmacSha1Len 336 | ) 337 | 338 | defer func() { 339 | dst.Close() 340 | }() 341 | var pipeBuf = leakybuf.NewLeakyBuf(nBuf, bufSize) 342 | buf := pipeBuf.Get() 343 | // sometimes it have to fill large block 344 | for i := 1; ; i += 1 { 345 | SetReadTimeout(src) 346 | n, err := io.ReadFull(src, buf[:dataLenLen+hmacSha1Len]) 347 | if err != nil { 348 | if err == io.EOF { 349 | break 350 | } 351 | Log.Debug(fmt.Sprintf("conn=%p #%v read header error n=%v: %v", src, i, n, err)) 352 | break 353 | } 354 | dataLen := binary.BigEndian.Uint16(buf[:dataLenLen]) 355 | expectedHmacSha1 := buf[dataLenLen:idxData0] 356 | 357 | var dataBuf []byte 358 | if len(buf) < int(idxData0+dataLen) { 359 | dataBuf = make([]byte, dataLen) 360 | } else { 361 | dataBuf = buf[idxData0 : idxData0+dataLen] 362 | } 363 | if n, err := io.ReadFull(src, dataBuf); err != nil { 364 | if err == io.EOF { 365 | break 366 | } 367 | Log.Debug(fmt.Sprintf("conn=%p #%v read data error n=%v: %v", src, i, n, err)) 368 | break 369 | } 370 | chunkIdBytes := make([]byte, 4) 371 | chunkId := src.GetAndIncrChunkId() 372 | binary.BigEndian.PutUint32(chunkIdBytes, chunkId) 373 | actualHmacSha1 := ss.HmacSha1(append(src.GetIv(), chunkIdBytes...), dataBuf) 374 | if !bytes.Equal(expectedHmacSha1, actualHmacSha1) { 375 | Log.Debug(fmt.Sprintf("conn=%p #%v read data hmac-sha1 mismatch, iv=%v chunkId=%v src=%v dst=%v len=%v expeced=%v actual=%v", src, i, src.GetIv(), chunkId, src.RemoteAddr(), dst.RemoteAddr(), dataLen, expectedHmacSha1, actualHmacSha1)) 376 | break 377 | } 378 | 379 | if n, err := dst.Write(dataBuf); err != nil { 380 | Log.Debug(fmt.Sprintf("conn=%p #%v write data error n=%v: %v", dst, i, n, err)) 381 | break 382 | } 383 | if is_res { 384 | go func() { 385 | err := storage.IncrSize(user, n) 386 | if err != nil { 387 | Log.Error(err) 388 | } 389 | Log.Debug(fmt.Sprintf("[port-%d] store size: %d", user.GetPort(), n)) 390 | }() 391 | } 392 | } 393 | return 394 | } 395 | 396 | var readTimeout time.Duration 397 | 398 | func SetReadTimeout(c net.Conn) { 399 | if readTimeout != 0 { 400 | c.SetReadDeadline(time.Now().Add(readTimeout)) 401 | } 402 | } 403 | 404 | func showConn(raw_req_header, raw_res_header []byte, host string, user user.User, size int, is_http bool) { 405 | if size == 0 { 406 | Log.Error(fmt.Sprintf("[port-%d] Error: request %s cancel", user.GetPort(), host)) 407 | return 408 | } 409 | if is_http { 410 | req, _ := http.ReadRequest(bufio.NewReader(bytes.NewReader(raw_req_header))) 411 | if req == nil { 412 | lines := bytes.SplitN(raw_req_header, []byte(" "), 2) 413 | Log.Debug(fmt.Sprintf("%s http://%s/ \"Unknow\" HTTP/1.1 unknow user-port: %d size: %d\n", lines[0], host, user.GetPort(), size)) 414 | return 415 | } 416 | res, _ := http.ReadResponse(bufio.NewReader(bytes.NewReader(raw_res_header)), req) 417 | statusCode := 200 418 | if res != nil { 419 | statusCode = res.StatusCode 420 | } 421 | Log.Debug(fmt.Sprintf("%s http://%s%s \"%s\" %s %d user-port: %d size: %d\n", req.Method, req.Host, req.URL.String(), req.Header.Get("user-agent"), req.Proto, statusCode, user.GetPort(), size)) 422 | } else { 423 | Log.Debug(fmt.Sprintf("CONNECT %s \"NONE\" NONE NONE user-port: %d size: %d\n", host, user.GetPort(), size)) 424 | } 425 | 426 | } 427 | 428 | func checkHttp(extra []byte, conn *ss.Conn) (is_http bool, data []byte, err error) { 429 | var buf []byte 430 | var methods = []string{"GET", "HEAD", "POST", "PUT", "TRACE", "OPTIONS", "DELETE"} 431 | is_http = false 432 | if extra == nil || len(extra) < 10 { 433 | buf = make([]byte, 10) 434 | if _, err = io.ReadFull(conn, buf); err != nil { 435 | return 436 | } 437 | } 438 | 439 | if buf == nil { 440 | data = extra 441 | } else if extra == nil { 442 | data = buf 443 | } else { 444 | buffer := bytes.NewBuffer(extra) 445 | buffer.Write(buf) 446 | data = buffer.Bytes() 447 | } 448 | 449 | for _, method := range methods { 450 | if bytes.HasPrefix(data, []byte(method)) { 451 | is_http = true 452 | break 453 | } 454 | } 455 | return 456 | } 457 | -------------------------------------------------------------------------------- /storage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var storage Storage 4 | 5 | func SetStorage(s Storage) { 6 | storage = s 7 | } 8 | 9 | type Storage interface { 10 | // GetUserInfo(UserInterface ) (UserInterface Info, error) 11 | // StoreUser(u UserInterface) error 12 | // Exists(u UserInterface) (bool, error) 13 | Del(u UserInterface) error 14 | ClearAll() error 15 | IncrSize(u UserInterface, size int) error 16 | GetSize(u UserInterface) (int64, error) 17 | SetSize(u UserInterface, size int) error 18 | MarkUserOnline(u UserInterface) error 19 | // IsUserOnline(u UserInterface) bool 20 | // GetOnlineUsersCount(u []UserInterface) int 21 | } 22 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func genUserInfoKey(user UserInterface) string { 8 | return fmt.Sprintf("userinfo:%v", user.GetId()) 9 | } 10 | 11 | func genUserFlowKey(user UserInterface) string { 12 | return fmt.Sprintf("userflow:%v", user.GetId()) 13 | } 14 | 15 | func genUserOnlineKey(user UserInterface) string { 16 | return fmt.Sprintf("useronline:%v", user.GetId()) 17 | } 18 | -------------------------------------------------------------------------------- /web_api.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/catpie/musdk-go" 5 | ) 6 | 7 | var ( 8 | WebApiClient *musdk.Client 9 | ) 10 | --------------------------------------------------------------------------------