diff --git a/.gitignore b/.gitignore
index fc2d952ed..8d6aeaab4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,5 @@ bin_resp/
custom_entrance_server_resp.bin
dec_bin8_data_dump.bin
entrance_resp_bin8_encrypted.bin
-tw_server_list_resp.bin
\ No newline at end of file
+tw_server_list_resp.bin
+Erupe.exe
\ No newline at end of file
diff --git a/channelserver/channel_server.go b/channelserver/channel_server.go
index d7aa386d8..e70d922fc 100644
--- a/channelserver/channel_server.go
+++ b/channelserver/channel_server.go
@@ -5,21 +5,27 @@ import (
"fmt"
"net"
"sync"
+
+ "github.com/Andoryuuta/Erupe/config"
+ "go.uber.org/zap"
)
// Config struct allows configuring the server.
type Config struct {
- DB *sql.DB
- ListenAddr string
+ Logger *zap.Logger
+ DB *sql.DB
+ ErupeConfig *config.Config
}
// Server is a MHF channel server.
type Server struct {
sync.Mutex
+ logger *zap.Logger
+ db *sql.DB
+ erupeConfig *config.Config
acceptConns chan net.Conn
deleteConns chan net.Conn
sessions map[net.Conn]*Session
- db *sql.DB
listenAddr string
listener net.Listener // Listener that is created when Server.Start is called.
}
@@ -27,23 +33,23 @@ type Server struct {
// NewServer creates a new Server type.
func NewServer(config *Config) *Server {
s := &Server{
+ logger: config.Logger,
+ db: config.DB,
+ erupeConfig: config.ErupeConfig,
acceptConns: make(chan net.Conn),
deleteConns: make(chan net.Conn),
sessions: make(map[net.Conn]*Session),
- db: config.DB,
- listenAddr: config.ListenAddr,
}
return s
}
// Start starts the server in a new goroutine.
func (s *Server) Start() error {
- l, err := net.Listen("tcp", s.listenAddr)
+ l, err := net.Listen("tcp", fmt.Sprintf(":%d", s.erupeConfig.Channel.Port))
if err != nil {
return err
}
s.listener = l
- //defer l.Close()
go s.acceptClients()
go s.manageSessions()
diff --git a/config.json b/config.json
new file mode 100644
index 000000000..cfb58cd45
--- /dev/null
+++ b/config.json
@@ -0,0 +1,51 @@
+{
+ "host_ip": "127.0.0.1",
+
+ "database": {
+ "host": "localhost",
+ "port": 5432,
+ "user": "postgres",
+ "password": "admin",
+ "database": "erupe"
+ },
+ "launcher": {
+ "port": 80
+ },
+ "sign": {
+ "port": 53312
+ },
+ "channel": {
+ "port": 54001
+ },
+ "entrance": {
+ "port": 53310,
+ "entries": [
+ {
+ "name": "AErupe server 1",
+ "ip": "127.0.0.1",
+ "unk2": 0,
+ "type": 1,
+ "season": 0,
+ "unk6": 3,
+ "allowedclientflags": "4096",
+ "channels": [
+ {
+ "port": 54001,
+ "MaxPlayers": 100,
+ "CurrentPlayers": 0,
+ "Unk4": 0,
+ "Unk5": 0,
+ "Unk6": 0,
+ "Unk7": 0,
+ "Unk8": 0,
+ "Unk9": 0,
+ "Unk10": 319,
+ "Unk11": 248,
+ "Unk12": 159,
+ "Unk13": 12345
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/config/config.go b/config/config.go
index 1cb7cd0fc..5886386c6 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1,25 +1,119 @@
package config
-import "github.com/BurntSushi/toml"
+import (
+ "log"
+ "net"
-// Config holds the configuration settings from the toml file.
+ "github.com/spf13/viper"
+)
+
+// Config holds the global server-wide config.
type Config struct {
- DB Database `toml:"database"`
+ HostIP string `mapstructure:"host_ip"`
+
+ Database Database
+ Launcher Launcher
+ Sign Sign
+ Channel Channel
+ Entrance Entrance
}
// Database holds the postgres database config.
type Database struct {
- Server string
- Port int
+ Host string
+ Port int
+ User string
+ Password string
+ Database string
+}
+
+// Launcher holds the launcher server config.
+type Launcher struct {
+ Port int
+}
+
+// Sign holds the sign server config.
+type Sign struct {
+ Port int
+}
+
+// Channel holds the channel server config.
+type Channel struct {
+ Port int
+}
+
+// Entrance holds the entrance server config.
+type Entrance struct {
+ Port uint16
+ Entries []EntranceServerInfo
+}
+
+// EntranceServerInfo represents an entry in the serverlist.
+type EntranceServerInfo struct {
+ IP string
+ Unk2 uint16
+ Type uint8 // Server type. 0=?, 1=open, 2=cities, 3=newbie, 4=bar
+ Season uint8 // Server activity. 0 = green, 1 = orange, 2 = blue
+ Unk6 uint8 // Something to do with server recommendation on 0, 3, and 5.
+ Name string // Server name, 66 byte null terminated Shift-JIS(JP) or Big5(TW).
+
+ // 4096(PC, PS3/PS4)?, 8258(PC, PS3/PS4)?, 8192 == nothing?
+ // THIS ONLY EXISTS IF Binary8Header.type == "SV2", NOT "SVR"!
+ AllowedClientFlags uint32
+
+ Channels []EntranceChannelInfo
+}
+
+// EntranceChannelInfo represents an entry in a server's channel list.
+type EntranceChannelInfo struct {
+ Port uint16
+ MaxPlayers uint16
+ CurrentPlayers uint16
+ Unk4 uint16
+ Unk5 uint16
+ Unk6 uint16
+ Unk7 uint16
+ Unk8 uint16
+ Unk9 uint16
+ Unk10 uint16
+ Unk11 uint16
+ Unk12 uint16
+ Unk13 uint16
+}
+
+// getOutboundIP4 gets the preferred outbound ip4 of this machine
+// From https://stackoverflow.com/a/37382208
+func getOutboundIP4() net.IP {
+ conn, err := net.Dial("udp4", "8.8.8.8:80")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer conn.Close()
+
+ localAddr := conn.LocalAddr().(*net.UDPAddr)
+
+ return localAddr.IP.To4()
}
// LoadConfig loads the given config toml file.
-func LoadConfig(filepath string) (*Config, error) {
- c := &Config{}
- _, err := toml.DecodeFile(filepath, c)
+func LoadConfig() (*Config, error) {
+ viper.SetConfigName("config")
+ viper.AddConfigPath(".")
+
+ err := viper.ReadInConfig()
if err != nil {
return nil, err
}
+ c := &Config{}
+ err = viper.Unmarshal(c)
+ if err != nil {
+ return nil, err
+ }
+
+ if c.HostIP == "" {
+ c.HostIP = getOutboundIP4().To4().String()
+ }
+
return c, nil
}
diff --git a/entranceserver/entrance_server.go b/entranceserver/entrance_server.go
index e43ede422..8ac3a9846 100644
--- a/entranceserver/entrance_server.go
+++ b/entranceserver/entrance_server.go
@@ -1,80 +1,120 @@
package entranceserver
import (
+ "database/sql"
"encoding/hex"
"fmt"
"io"
"net"
+ "sync"
+ "github.com/Andoryuuta/Erupe/config"
"github.com/Andoryuuta/Erupe/network"
+ "go.uber.org/zap"
)
-func handleEntranceServerConnection(conn net.Conn) {
+// Server is a MHF entrance server.
+type Server struct {
+ sync.Mutex
+ logger *zap.Logger
+ erupeConfig *config.Config
+ db *sql.DB
+ listener net.Listener
+ isShuttingDown bool
+}
+
+// Config struct allows configuring the server.
+type Config struct {
+ Logger *zap.Logger
+ DB *sql.DB
+ ErupeConfig *config.Config
+}
+
+// NewServer creates a new Server type.
+func NewServer(config *Config) *Server {
+ s := &Server{
+ logger: config.Logger,
+ erupeConfig: config.ErupeConfig,
+ db: config.DB,
+ }
+ return s
+}
+
+// Start starts the server in a new goroutine.
+func (s *Server) Start() error {
+
+ l, err := net.Listen("tcp", fmt.Sprintf(":%d", s.erupeConfig.Entrance.Port))
+ if err != nil {
+ return err
+ }
+
+ s.listener = l
+
+ go s.acceptClients()
+
+ return nil
+}
+
+// Shutdown exits the server gracefully.
+func (s *Server) Shutdown() {
+ s.logger.Debug("Shutting down")
+
+ s.Lock()
+ s.isShuttingDown = true
+ s.Unlock()
+
+ // This will cause the acceptor goroutine to error and exit gracefully.
+ s.listener.Close()
+}
+
+//acceptClients handles accepting new clients in a loop.
+func (s *Server) acceptClients() {
+ for {
+ conn, err := s.listener.Accept()
+ if err != nil {
+ // Check if we are shutting down and exit gracefully if so.
+ s.Lock()
+ shutdown := s.isShuttingDown
+ s.Unlock()
+
+ if shutdown {
+ break
+ } else {
+ continue
+ }
+ }
+
+ // Start a new goroutine for the connection so that we don't block other incoming connections.
+ go s.handleEntranceServerConnection(conn)
+ }
+}
+
+func (s *Server) handleEntranceServerConnection(conn net.Conn) {
// Client initalizes the connection with a one-time buffer of 8 NULL bytes.
nullInit := make([]byte, 8)
n, err := io.ReadFull(conn, nullInit)
if err != nil {
- fmt.Println(err)
+ s.logger.Warn("Failed to read 8 NULL init", zap.Error(err))
return
} else if n != len(nullInit) {
- fmt.Println("io.ReadFull couldn't read the full 8 byte init.")
+ s.logger.Warn("io.ReadFull couldn't read the full 8 byte init.")
return
}
+ // Create a new encrypted connection handler and read a packet from it.
cc := network.NewCryptConn(conn)
- for {
- pkt, err := cc.ReadPacket()
- if err != nil {
- return
- }
-
- fmt.Printf("Got entrance server command:\n%s\n", hex.Dump(pkt))
-
- data := makeResp([]ServerInfo{
- ServerInfo{
- IP: net.ParseIP("127.0.0.1"),
- Unk2: 0,
- Type: 1,
- Season: 0,
- Unk6: 3,
- Name: "AErupe Server in Go! @localhost",
- AllowedClientFlags: 4096,
- Channels: []ChannelInfo{
- ChannelInfo{
- Port: 54001,
- MaxPlayers: 100,
- CurrentPlayers: 0,
- Unk4: 0,
- Unk5: 0,
- Unk6: 0,
- Unk7: 0,
- Unk8: 0,
- Unk9: 0,
- Unk10: 319,
- Unk11: 248,
- Unk12: 159,
- Unk13: 12345,
- },
- },
- },
- })
- cc.SendPacket(data)
-
- }
-}
-
-func DoEntranceServer(listenAddr string) {
- l, err := net.Listen("tcp", listenAddr)
+ pkt, err := cc.ReadPacket()
if err != nil {
- panic(err)
+ s.logger.Warn("Error reading packet", zap.Error(err))
+ return
}
- defer l.Close()
- for {
- conn, err := l.Accept()
- if err != nil {
- panic(err)
- }
- go handleEntranceServerConnection(conn)
- }
+ s.logger.Debug("Got entrance server command:\n", zap.String("raw", hex.Dump(pkt)))
+
+ data := makeResp(s.erupeConfig.Entrance.Entries)
+ cc.SendPacket(data)
+
+ // Close because we only need to send the response once.
+ // Any further requests from the client will come from a new connection.
+ conn.Close()
}
diff --git a/entranceserver/make_resp.go b/entranceserver/make_resp.go
index bd3ff98e8..73ac12ea6 100644
--- a/entranceserver/make_resp.go
+++ b/entranceserver/make_resp.go
@@ -4,6 +4,7 @@ import (
"encoding/binary"
"net"
+ "github.com/Andoryuuta/Erupe/config"
"github.com/Andoryuuta/byteframe"
)
@@ -16,45 +17,11 @@ func paddedString(x string, size uint) []byte {
return out
}
-// ServerInfo represents an entry in the serverlist.
-type ServerInfo struct {
- IP net.IP
- Unk2 uint16
- Type uint8 // Server type. 0=?, 1=open, 2=cities, 3=newbie, 4=bar
- Season uint8 // Server activity. 0 = green, 1 = orange, 2 = blue
- Unk6 uint8 // Something to do with server recommendation on 0, 3, and 5.
- Name string // Server name, 66 byte null terminated Shift-JIS.
-
- // 4096(PC, PS3/PS4)?, 8258(PC, PS3/PS4)?, 8192 == nothing?
- // THIS ONLY EXISTS IF Binary8Header.type == "SV2", NOT "SVR"!
- AllowedClientFlags uint32
-
- Channels []ChannelInfo
-}
-
-// ChannelInfo represents an entry in a server's channel list.
-type ChannelInfo struct {
- Port uint16
- //ChannelIndex uint16
- MaxPlayers uint16
- CurrentPlayers uint16
- Unk4 uint16
- Unk5 uint16
- Unk6 uint16
- Unk7 uint16
- Unk8 uint16
- Unk9 uint16
- Unk10 uint16
- Unk11 uint16
- Unk12 uint16
- Unk13 uint16
-}
-
-func encodeServerInfo(serverInfos []ServerInfo) []byte {
+func encodeServerInfo(serverInfos []config.EntranceServerInfo) []byte {
bf := byteframe.NewByteFrame()
for serverIdx, si := range serverInfos {
- bf.WriteUint32(binary.LittleEndian.Uint32(si.IP.To4()))
+ bf.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(si.IP).To4()))
bf.WriteUint16(16 + uint16(serverIdx))
bf.WriteUint16(si.Unk2)
bf.WriteUint16(uint16(len(si.Channels)))
@@ -103,7 +70,7 @@ func makeHeader(data []byte, respType string, entryCount uint16, key byte) []byt
return bf.Data()
}
-func makeResp(servers []ServerInfo) []byte {
+func makeResp(servers []config.EntranceServerInfo) []byte {
rawServerData := encodeServerInfo(servers)
bf := byteframe.NewByteFrame()
diff --git a/go.mod b/go.mod
index 7148e08f1..7f512f3a0 100644
--- a/go.mod
+++ b/go.mod
@@ -8,4 +8,10 @@ require (
github.com/gorilla/handlers v1.4.2
github.com/gorilla/mux v1.7.3
github.com/lib/pq v1.3.0
+ github.com/spf13/viper v1.6.1
+ go.uber.org/atomic v1.5.1 // indirect
+ go.uber.org/multierr v1.4.0 // indirect
+ go.uber.org/zap v1.13.0
+ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect
+ golang.org/x/tools v0.0.0-20200110213125-a7a6caa82ab2 // indirect
)
diff --git a/go.sum b/go.sum
index d300d1197..b236b54e0 100644
--- a/go.sum
+++ b/go.sum
@@ -1,10 +1,192 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Andoryuuta/byteframe v0.0.0-20191219124302-41f4085eb4c0 h1:2pVgen9rh18IxSWxOa80bObcpyfrS6d5bJtZeCUN7rY=
github.com/Andoryuuta/byteframe v0.0.0-20191219124302-41f4085eb4c0/go.mod h1:koVyx+gN3TfE70rpOidywETVODk87304YpwW69Y27J4=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
+github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk=
+github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
+go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.5.1 h1:rsqfU5vBkVknbhUGbAUwQKR2H4ItV8tjJ+6kJX4cxHM=
+go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
+go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E=
+go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
+go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200110213125-a7a6caa82ab2 h1:V9r/14uGBqLgNlHRYWdVqjMdWkcOHnE2KG8DwVqQSEc=
+golang.org/x/tools v0.0.0-20200110213125-a7a6caa82ab2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
+gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
diff --git a/launcherserver/handler.go b/launcherserver/handler.go
new file mode 100644
index 000000000..d1cad4f3c
--- /dev/null
+++ b/launcherserver/handler.go
@@ -0,0 +1,20 @@
+package launcherserver
+
+import (
+ "net/http"
+)
+
+// ServerHandler is a handler function akin to http.Handler's ServeHTTP,
+// but has an additional *Server argument.
+type ServerHandler func(*Server, http.ResponseWriter, *http.Request)
+
+// ServerHandlerFunc is a small type that implements http.Handler and
+// wraps a calling ServerHandler with a *Server argument.
+type ServerHandlerFunc struct {
+ server *Server
+ f ServerHandler
+}
+
+func (shf ServerHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ shf.f(shf.server, w, r)
+}
diff --git a/launcherserver/launcher_server.go b/launcherserver/launcher_server.go
new file mode 100644
index 000000000..8d2e39adb
--- /dev/null
+++ b/launcherserver/launcher_server.go
@@ -0,0 +1,98 @@
+package launcherserver
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+ "net/http"
+ "os"
+ "sync"
+ "time"
+
+ "github.com/Andoryuuta/Erupe/config"
+ "github.com/gorilla/handlers"
+ "github.com/gorilla/mux"
+ "go.uber.org/zap"
+)
+
+// Config struct allows configuring the server.
+type Config struct {
+ Logger *zap.Logger
+ DB *sql.DB
+ ErupeConfig *config.Config
+ UseOriginalLauncherFiles bool
+}
+
+// Server is the MHF launcher HTTP server.
+type Server struct {
+ sync.Mutex
+ logger *zap.Logger
+ erupeConfig *config.Config
+ db *sql.DB
+ httpServer *http.Server
+ useOriginalLauncherFiles bool
+ isShuttingDown bool
+}
+
+// NewServer creates a new Server type.
+func NewServer(config *Config) *Server {
+ s := &Server{
+ logger: config.Logger,
+ erupeConfig: config.ErupeConfig,
+ db: config.DB,
+ useOriginalLauncherFiles: config.UseOriginalLauncherFiles,
+ httpServer: &http.Server{},
+ }
+ return s
+}
+
+// Start starts the server in a new goroutine.
+func (s *Server) Start() error {
+ // Set up the routes responsible for serving the launcher HTML, serverlist, unique name check, and JP auth.
+ r := mux.NewRouter()
+
+ // Universal serverlist.xml route
+ s.setupServerlistRoutes(r)
+
+ // Change the launcher HTML routes if we are using the custom launcher instead of the original.
+ if s.useOriginalLauncherFiles {
+ s.setupOriginalLauncherRotues(r)
+ } else {
+ s.setupCustomLauncherRotues(r)
+ }
+
+ s.httpServer.Addr = fmt.Sprintf(":%d", s.erupeConfig.Launcher.Port)
+ s.httpServer.Handler = handlers.LoggingHandler(os.Stdout, r)
+
+ serveError := make(chan error, 1)
+ go func() {
+ if err := s.httpServer.ListenAndServe(); err != nil {
+ // Send error if any.
+ serveError <- err
+ }
+ }()
+
+ // Get the error from calling ListenAndServe, otherwise assume it's good after 250 milliseconds.
+ select {
+ case err := <-serveError:
+ return err
+ case <-time.After(250 * time.Millisecond):
+ return nil
+ }
+}
+
+// Shutdown exits the server gracefully.
+func (s *Server) Shutdown() {
+ s.logger.Debug("Shutting down")
+
+ s.Lock()
+ s.isShuttingDown = true
+ s.Unlock()
+
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ if err := s.httpServer.Shutdown(ctx); err != nil {
+ // Just warn because we are shutting down the server anyway.
+ s.logger.Warn("Got error on httpServer shutdown", zap.Error(err))
+ }
+}
diff --git a/launcher_server.go b/launcherserver/routes.go
similarity index 55%
rename from launcher_server.go
rename to launcherserver/routes.go
index 9bc81d785..bcdce830b 100644
--- a/launcher_server.go
+++ b/launcherserver/routes.go
@@ -1,36 +1,20 @@
-package main
+package launcherserver
import (
"fmt"
"html"
- "log"
- "net"
"net/http"
- "os"
- "github.com/gorilla/handlers"
"github.com/gorilla/mux"
//"github.com/julienschmidt/httprouter"
)
-// GetOutboundIP4 gets the preferred outbound ip4 of this machine
-// From https://stackoverflow.com/a/37382208
-func GetOutboundIP4() net.IP {
- conn, err := net.Dial("udp4", "8.8.8.8:80")
- if err != nil {
- log.Fatal(err)
- }
- defer conn.Close()
-
- localAddr := conn.LocalAddr().(*net.UDPAddr)
-
- return localAddr.IP.To4()
-}
-
-func serverList(w http.ResponseWriter, r *http.Request) {
- // TODO(Andoryuuta): Redo launcher server to allow configurable serverlist host and port.
- fmt.Fprintf(w, ``, GetOutboundIP4().String())
-
+func serverList(s *Server, w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w,
+ ``,
+ s.erupeConfig.HostIP,
+ s.erupeConfig.Entrance.Port,
+ )
}
func serverUniqueName(w http.ResponseWriter, r *http.Request) {
@@ -39,8 +23,7 @@ func serverUniqueName(w http.ResponseWriter, r *http.Request) {
}
func jpLogin(w http.ResponseWriter, r *http.Request) {
- // HACK: Return the given password back as the `skey` to defer the login logic to the sign server.
-
+ // HACK(Andoryuuta): Return the given password back as the `skey` to defer the login logic to the sign server.
resultJSON := fmt.Sprintf(`{"result": "Ok", "skey": "%s", "code": "000", "msg": ""}`, r.FormValue("pw"))
fmt.Fprintf(w,
@@ -58,18 +41,18 @@ func jpLogin(w http.ResponseWriter, r *http.Request) {
}
-func setupServerlistRoutes(r *mux.Router) {
+func (s *Server) setupServerlistRoutes(r *mux.Router) {
// TW
twServerList := r.Host("mhf-n.capcom.com.tw").Subrouter()
twServerList.HandleFunc("/server/unique.php", serverUniqueName) // Name checking is also done on this host.
- twServerList.HandleFunc("/server/serverlist.xml", serverList)
+ twServerList.Handle("/server/serverlist.xml", ServerHandlerFunc{s, serverList})
// JP
jpServerList := r.Host("srv-mhf.capcom-networks.jp").Subrouter()
- jpServerList.HandleFunc("/serverlist.xml", serverList)
+ jpServerList.Handle("/serverlist.xml", ServerHandlerFunc{s, serverList})
}
-func setupOriginalLauncherRotues(r *mux.Router) {
+func (s *Server) setupOriginalLauncherRotues(r *mux.Router) {
// TW
twMain := r.Host("mhfg.capcom.com.tw").Subrouter()
twMain.PathPrefix("/").Handler(http.FileServer(http.Dir("./www/tw/")))
@@ -85,7 +68,7 @@ func setupOriginalLauncherRotues(r *mux.Router) {
}
-func setupCustomLauncherRotues(r *mux.Router) {
+func (s *Server) setupCustomLauncherRotues(r *mux.Router) {
// TW
twMain := r.Host("mhfg.capcom.com.tw").Subrouter()
twMain.PathPrefix("/g6_launcher/").Handler(http.StripPrefix("/g6_launcher/", http.FileServer(http.Dir("./www/erupe/"))))
@@ -94,24 +77,3 @@ func setupCustomLauncherRotues(r *mux.Router) {
jpMain := r.Host("cog-members.mhf-z.jp").Subrouter()
jpMain.PathPrefix("/launcher/").Handler(http.StripPrefix("/launcher/", http.FileServer(http.Dir("./www/erupe"))))
}
-
-// serveLauncherHTML is responsible for serving the launcher HTML, serverlist, unique name check, and JP auth.
-func serveLauncherHTML(listenAddr string, useOriginalLauncher bool) {
- r := mux.NewRouter()
-
- setupServerlistRoutes(r)
-
- if useOriginalLauncher {
- setupOriginalLauncherRotues(r)
- } else {
- setupCustomLauncherRotues(r)
- }
- /*
- http.ListenAndServe(listenAddr, handlers.CustomLoggingHandler(os.Stdout, r, func(writer io.Writer, params handlers.LogFormatterParams) {
- dump, _ := httputil.DumpRequest(params.Request, true)
- writer.Write(dump)
- }))
- */
-
- http.ListenAndServe(listenAddr, handlers.LoggingHandler(os.Stdout, r))
-}
diff --git a/main.go b/main.go
index ed6d0d43c..ae16f0977 100644
--- a/main.go
+++ b/main.go
@@ -3,58 +3,117 @@ package main
import (
"database/sql"
"fmt"
- "time"
+ "os"
+ "os/signal"
+ "syscall"
"github.com/Andoryuuta/Erupe/channelserver"
- "github.com/Andoryuuta/Erupe/signserver"
+ "github.com/Andoryuuta/Erupe/config"
"github.com/Andoryuuta/Erupe/entranceserver"
+ "github.com/Andoryuuta/Erupe/launcherserver"
+ "github.com/Andoryuuta/Erupe/signserver"
_ "github.com/lib/pq"
+ "go.uber.org/zap"
)
func main() {
- fmt.Println("Starting!")
+ zapLogger, _ := zap.NewDevelopment()
+ defer zapLogger.Sync()
+ logger := zapLogger.Named("main")
- // Load the config.toml configuration.
- // TODO(Andoryuuta): implement config loading.
+ logger.Info("Starting Erupe")
+
+ // Load the configuration.
+ erupeConfig, err := config.LoadConfig()
+ if err != nil {
+ logger.Fatal("Failed to load config", zap.Error(err))
+ }
// Create the postgres DB pool.
- db, err := sql.Open("postgres", "host=localhost port=5432 user=postgres password=admin dbname=erupe sslmode=disable")
+ connectString := fmt.Sprintf(
+ "host=%s port=%d user=%s password=%s dbname= %s sslmode=disable",
+ erupeConfig.Database.Host,
+ erupeConfig.Database.Port,
+ erupeConfig.Database.User,
+ erupeConfig.Database.Password,
+ erupeConfig.Database.Database,
+ )
+
+ db, err := sql.Open("postgres", connectString)
if err != nil {
- panic(err)
+ logger.Fatal("Failed to open sql database", zap.Error(err))
}
// Test the DB connection.
err = db.Ping()
if err != nil {
- panic(err)
+ logger.Fatal("Failed to ping database", zap.Error(err))
}
+ logger.Info("Connected to database.")
- // Finally start our server(s).
- go serveLauncherHTML(":80", false)
- go entranceserver.DoEntranceServer(":53310")
+ // Now start our server(s).
+ // Launcher HTTP server.
+ launcherServer := launcherserver.NewServer(
+ &launcherserver.Config{
+ Logger: logger.Named("launcher"),
+ ErupeConfig: erupeConfig,
+ DB: db,
+ UseOriginalLauncherFiles: false,
+ })
+ err = launcherServer.Start()
+ if err != nil {
+ logger.Fatal("Failed to start launcher server", zap.Error(err))
+ }
+ logger.Info("Started launcher server.")
+
+ // Entrance server.
+ entranceServer := entranceserver.NewServer(
+ &entranceserver.Config{
+ Logger: logger.Named("entrance"),
+ ErupeConfig: erupeConfig,
+ DB: db,
+ })
+ err = entranceServer.Start()
+ if err != nil {
+ logger.Fatal("Failed to start entrance server", zap.Error(err))
+ }
+ logger.Info("Started entrance server.")
+
+ // Sign server.
signServer := signserver.NewServer(
&signserver.Config{
- DB: db,
- ListenAddr: ":53312",
+ Logger: logger.Named("sign"),
+ ErupeConfig: erupeConfig,
+ DB: db,
})
err = signServer.Start()
if err != nil {
- panic(err)
+ logger.Fatal("Failed to start sign server", zap.Error(err))
}
+ logger.Info("Started sign server.")
+ // Channel Server
channelServer := channelserver.NewServer(
&channelserver.Config{
- DB: db,
- ListenAddr: ":54001",
+ Logger: logger.Named("channel"),
+ ErupeConfig: erupeConfig,
+ DB: db,
})
err = channelServer.Start()
if err != nil {
- panic(err)
+ logger.Fatal("Failed to start channel server", zap.Error(err))
}
+ logger.Info("Started channel server.")
- for {
- time.Sleep(1 * time.Second)
- }
+ // Wait for exit or interrupt with ctrl+C.
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt, syscall.SIGTERM)
+ <-c
+
+ logger.Info("Trying to shutdown gracefully.")
+ signServer.Shutdown()
+ entranceServer.Shutdown()
+ launcherServer.Shutdown()
}
diff --git a/signserver/dsgn_resp.go b/signserver/dsgn_resp.go
index 000d68c91..593368eca 100644
--- a/signserver/dsgn_resp.go
+++ b/signserver/dsgn_resp.go
@@ -27,7 +27,7 @@ func makeSignInFailureResp(respID RespID) []byte {
return bf.Data()
}
-func makeSignInResp(username string) []byte {
+func (session *Session) makeSignInResp(username string) []byte {
bf := byteframe.NewByteFrame()
// delete me:
diff --git a/signserver/respid.go b/signserver/respid.go
index a55aed14a..47e7683d4 100644
--- a/signserver/respid.go
+++ b/signserver/respid.go
@@ -1,6 +1,7 @@
package signserver
//revive:disable
+
type RespID uint16
//go:generate stringer -type=RespID
diff --git a/signserver/session.go b/signserver/session.go
index 3eebeac9a..c749a79d1 100644
--- a/signserver/session.go
+++ b/signserver/session.go
@@ -3,104 +3,115 @@ package signserver
import (
"database/sql"
"encoding/hex"
- "fmt"
"net"
"sync"
"github.com/Andoryuuta/Erupe/network"
"github.com/Andoryuuta/byteframe"
+ "go.uber.org/zap"
)
// Session holds state for the sign server connection.
type Session struct {
sync.Mutex
+ logger *zap.Logger
sid int
server *Server
rawConn *net.Conn
cryptConn *network.CryptConn
}
-func (session *Session) fail() {
- session.server.Lock()
- delete(session.server.sessions, session.sid)
- session.server.Unlock()
+func (s *Session) fail() {
+ s.server.Lock()
+ delete(s.server.sessions, s.sid)
+ s.server.Unlock()
}
-func (session *Session) work() {
+func (s *Session) work() {
for {
- pkt, err := session.cryptConn.ReadPacket()
+ pkt, err := s.cryptConn.ReadPacket()
if err != nil {
- session.fail()
+ s.fail()
return
}
- err = session.handlePacket(pkt)
+ err = s.handlePacket(pkt)
if err != nil {
- session.fail()
+ s.fail()
return
}
}
}
-func (session *Session) handlePacket(pkt []byte) error {
+func (s *Session) handlePacket(pkt []byte) error {
+ sugar := s.logger.Sugar()
+
bf := byteframe.NewByteFrameFromBytes(pkt)
reqType := string(bf.ReadNullTerminatedBytes())
switch reqType {
case "DLTSKEYSIGN:100":
fallthrough
case "DSGN:100":
- session.handleDSGNRequest(bf)
- break
+ err := s.handleDSGNRequest(bf)
+ if err != nil {
+ return nil
+ }
case "DELETE:100":
loginTokenString := string(bf.ReadNullTerminatedBytes())
_ = loginTokenString
characterID := bf.ReadUint32()
- fmt.Printf("Got delete request for character ID: %v\n", characterID)
- fmt.Printf("remaining unknown data:\n%s\n", hex.Dump(bf.DataFromCurrent()))
+ sugar.Infof("Got delete request for character ID: %v\n", characterID)
+ sugar.Infof("remaining unknown data:\n%s\n", hex.Dump(bf.DataFromCurrent()))
default:
- fmt.Printf("Got unknown request type %s, data:\n%s\n", reqType, hex.Dump(bf.DataFromCurrent()))
+ sugar.Infof("Got unknown request type %s, data:\n%s\n", reqType, hex.Dump(bf.DataFromCurrent()))
}
return nil
}
-func (session *Session) handleDSGNRequest(bf *byteframe.ByteFrame) error {
+func (s *Session) handleDSGNRequest(bf *byteframe.ByteFrame) error {
+
reqUsername := string(bf.ReadNullTerminatedBytes())
reqPassword := string(bf.ReadNullTerminatedBytes())
reqUnk := string(bf.ReadNullTerminatedBytes())
- fmt.Printf("Got sign in request:\n\tUsername: %s\n\tPassword %s\n\tUnk: %s\n", reqUsername, reqPassword, reqUnk)
+
+ s.server.logger.Info(
+ "Got sign in request",
+ zap.String("reqUsername", reqUsername),
+ zap.String("reqPassword", reqPassword),
+ zap.String("reqUnk", reqUnk),
+ )
// TODO(Andoryuuta): remove plaintext password storage if this ever becomes more than a toy project.
var (
id int
password string
)
- err := session.server.db.QueryRow("SELECT id, password FROM users WHERE username = $1", reqUsername).Scan(&id, &password)
+ err := s.server.db.QueryRow("SELECT id, password FROM users WHERE username = $1", reqUsername).Scan(&id, &password)
var serverRespBytes []byte
switch {
case err == sql.ErrNoRows:
- fmt.Printf("No rows for username %s\n", reqUsername)
+ s.logger.Info("Account not found", zap.String("reqUsername", reqUsername))
serverRespBytes = makeSignInFailureResp(SIGN_EAUTH)
break
case err != nil:
serverRespBytes = makeSignInFailureResp(SIGN_EABORT)
- fmt.Println("Got error on SQL query!")
- fmt.Println(err)
+ s.logger.Warn("Got error on SQL query", zap.Error(err))
break
default:
if reqPassword == password {
- fmt.Println("Passwords match!")
- serverRespBytes = makeSignInResp(reqUsername)
+ s.logger.Info("Passwords match!")
+ serverRespBytes = s.makeSignInResp(reqUsername)
} else {
- fmt.Println("Passwords don't match!")
+ s.logger.Info("Passwords don't match!")
serverRespBytes = makeSignInFailureResp(SIGN_EPASS)
}
}
- err = session.cryptConn.SendPacket(serverRespBytes)
+ err = s.cryptConn.SendPacket(serverRespBytes)
if err != nil {
return err
}
diff --git a/signserver/sign_server.go b/signserver/sign_server.go
index 6654f16da..de6b60d5d 100644
--- a/signserver/sign_server.go
+++ b/signserver/sign_server.go
@@ -7,55 +7,81 @@ import (
"net"
"sync"
+ "github.com/Andoryuuta/Erupe/config"
"github.com/Andoryuuta/Erupe/network"
+ "go.uber.org/zap"
)
// Config struct allows configuring the server.
type Config struct {
- DB *sql.DB
- ListenAddr string
+ Logger *zap.Logger
+ DB *sql.DB
+ ErupeConfig *config.Config
}
// Server is a MHF sign server.
type Server struct {
sync.Mutex
- sid int
- sessions map[int]*Session
- db *sql.DB
- listenAddr string
- listener net.Listener
+ logger *zap.Logger
+ erupeConfig *config.Config
+ sid int
+ sessions map[int]*Session
+ db *sql.DB
+ listener net.Listener
+ isShuttingDown bool
}
// NewServer creates a new Server type.
func NewServer(config *Config) *Server {
s := &Server{
- sid: 0,
- sessions: make(map[int]*Session),
- db: config.DB,
- listenAddr: config.ListenAddr,
+ logger: config.Logger,
+ erupeConfig: config.ErupeConfig,
+ sid: 0,
+ sessions: make(map[int]*Session),
+ db: config.DB,
}
return s
}
// Start starts the server in a new goroutine.
func (s *Server) Start() error {
- l, err := net.Listen("tcp", s.listenAddr)
+ l, err := net.Listen("tcp", fmt.Sprintf(":%d", s.erupeConfig.Sign.Port))
if err != nil {
return err
}
s.listener = l
- //defer l.Close()
go s.acceptClients()
return nil
}
+// Shutdown exits the server gracefully.
+func (s *Server) Shutdown() {
+ s.logger.Debug("Shutting down")
+
+ s.Lock()
+ s.isShuttingDown = true
+ s.Unlock()
+
+ // This will cause the acceptor goroutine to error and exit gracefully.
+ s.listener.Close()
+}
+
func (s *Server) acceptClients() {
for {
conn, err := s.listener.Accept()
if err != nil {
- panic(err)
+ // Check if we are shutting down and exit gracefully if so.
+ s.Lock()
+ shutdown := s.isShuttingDown
+ s.Unlock()
+
+ if shutdown {
+ break
+ } else {
+ panic(err)
+ }
}
go s.handleConnection(s.sid, conn)
@@ -64,7 +90,7 @@ func (s *Server) acceptClients() {
}
func (s *Server) handleConnection(sid int, conn net.Conn) {
- fmt.Println("Got connection to sign server")
+ s.logger.Info("Got connection to sign server", zap.String("remoteaddr", conn.RemoteAddr().String()))
// Client initalizes the connection with a one-time buffer of 8 NULL bytes.
nullInit := make([]byte, 8)
@@ -75,15 +101,19 @@ func (s *Server) handleConnection(sid int, conn net.Conn) {
return
}
+ // Create a new session.
session := &Session{
+ logger: s.logger,
server: s,
rawConn: &conn,
cryptConn: network.NewCryptConn(conn),
}
+ // Add the session to the server's sessions map.
s.Lock()
s.sessions[sid] = session
s.Unlock()
+ // Do the session's work.
session.work()
}