From 73d406ccd6f6e2a29bc6586974b9ce288f8636fe Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Mon, 13 Mar 2017 23:52:28 +1000 Subject: [PATCH 1/6] logger: Don't output control chars on log files --- irc/logger/logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irc/logger/logger.go b/irc/logger/logger.go index 0fea2911..c77b2b5f 100644 --- a/irc/logger/logger.go +++ b/irc/logger/logger.go @@ -212,7 +212,7 @@ func (logger *singleLogger) Log(level Level, logType string, messageParts ...str sep := grey(":") fullStringFormatted := fmt.Sprintf("%s %s %s %s %s %s ", timeGrey(time.Now().UTC().Format("2006-01-02T15:04:05Z")), sep, levelDisplay, sep, section(logType), sep) - fullStringRaw := fmt.Sprintf("%s : %s : %s : ", time.Now().UTC().Format("2006-01-02T15:04:05Z"), LogLevelDisplayNames[level], section(logType)) + fullStringRaw := fmt.Sprintf("%s : %s : %s : ", time.Now().UTC().Format("2006-01-02T15:04:05Z"), LogLevelDisplayNames[level], logType) for i, p := range messageParts { fullStringFormatted += p fullStringRaw += p From de4db1c6ef6478c6496209afe16fa6815be3a5eb Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Mon, 13 Mar 2017 23:53:21 +1000 Subject: [PATCH 2/6] socket: Start overhaul of sockets and writing --- irc/client.go | 1 + irc/socket.go | 70 ++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/irc/client.go b/irc/client.go index 31abd3b9..b56a1768 100644 --- a/irc/client.go +++ b/irc/client.go @@ -75,6 +75,7 @@ type Client struct { func NewClient(server *Server, conn net.Conn, isTLS bool) *Client { now := time.Now() socket := NewSocket(conn) + go socket.RunSocketWriter() client := &Client{ atime: now, authorized: server.password == nil, diff --git a/irc/socket.go b/irc/socket.go index 2764151c..3c76649e 100644 --- a/irc/socket.go +++ b/irc/socket.go @@ -10,9 +10,11 @@ import ( "crypto/tls" "encoding/hex" "errors" + "fmt" "io" "net" "strings" + "sync" "time" ) @@ -27,23 +29,25 @@ type Socket struct { Closed bool conn net.Conn reader *bufio.Reader + + lineToSendExists chan bool + linesToSend []string + linesToSendMutex sync.Mutex } // NewSocket returns a new Socket. func NewSocket(conn net.Conn) Socket { return Socket{ - conn: conn, - reader: bufio.NewReader(conn), + conn: conn, + reader: bufio.NewReader(conn), + lineToSendExists: make(chan bool), } } // Close stops a Socket from being able to send/receive any more data. func (socket *Socket) Close() { - if socket.Closed { - return - } socket.Closed = true - socket.conn.Close() + // socket will close once all data has been sent } // CertFP returns the fingerprint of the certificate provided by the client. @@ -104,15 +108,57 @@ func (socket *Socket) Write(data string) error { return io.EOF } - // write data - _, err := socket.conn.Write([]byte(data)) - if err != nil { - socket.Close() - return err - } + socket.linesToSendMutex.Lock() + socket.linesToSend = append(socket.linesToSend, data) + socket.linesToSendMutex.Unlock() + go socket.fillLineToSendExists() + return nil } +// fillLineToSendExists only exists because you can't goroutine single statements. +func (socket *Socket) fillLineToSendExists() { + socket.lineToSendExists <- true +} + +// RunSocketWriter starts writing messages to the outgoing socket. +func (socket *Socket) RunSocketWriter() { + var errOut bool + for { + // wait for new lines + select { + case <-socket.lineToSendExists: + socket.linesToSendMutex.Lock() + + // get data + data := socket.linesToSend[0] + if len(socket.linesToSend) > 1 { + socket.linesToSend = socket.linesToSend[1:] + } else { + socket.linesToSend = []string{} + } + + // write data + _, err := socket.conn.Write([]byte(data)) + if err != nil { + errOut = true + fmt.Println(err.Error()) + break + } + socket.linesToSendMutex.Unlock() + } + if errOut { + // error out, bad stuff happened + break + } + } + //TODO(dan): empty socket.lineToSendExists queue + socket.conn.Close() + if !socket.Closed { + socket.Closed = true + } +} + // WriteLine writes the given line out of Socket. func (socket *Socket) WriteLine(line string) error { return socket.Write(line + "\r\n") From f29a5f0e70566344b5d30dc51dc172e660ebcdb2 Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Tue, 14 Mar 2017 08:12:39 +1000 Subject: [PATCH 3/6] socket: Very initial SendQ limit --- irc/client.go | 2 +- irc/config.go | 9 +++++++++ irc/server.go | 14 ++++++++++++++ irc/socket.go | 18 +++++++++++++++++- oragono.yaml | 3 +++ 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/irc/client.go b/irc/client.go index b56a1768..c199739d 100644 --- a/irc/client.go +++ b/irc/client.go @@ -74,7 +74,7 @@ type Client struct { // NewClient returns a client with all the appropriate info setup. func NewClient(server *Server, conn net.Conn, isTLS bool) *Client { now := time.Now() - socket := NewSocket(conn) + socket := NewSocket(conn, server.MaxSendQBytes) go socket.RunSocketWriter() client := &Client{ atime: now, diff --git a/irc/config.go b/irc/config.go index 395c8d54..ac66cb7f 100644 --- a/irc/config.go +++ b/irc/config.go @@ -14,6 +14,8 @@ import ( "strings" "time" + "code.cloudfoundry.org/bytefmt" + "github.com/DanielOaks/oragono/irc/custime" "github.com/DanielOaks/oragono/irc/logger" "gopkg.in/yaml.v2" @@ -171,6 +173,8 @@ type Config struct { RestAPI RestAPIConfig `yaml:"rest-api"` CheckIdent bool `yaml:"check-ident"` MOTD string + MaxSendQString string `yaml:"max-sendq"` + MaxSendQBytes uint64 ConnectionLimits ConnectionLimitsConfig `yaml:"connection-limits"` ConnectionThrottle ConnectionThrottleConfig `yaml:"connection-throttling"` } @@ -433,5 +437,10 @@ func LoadConfig(filename string) (config *Config, err error) { } config.Logging = newLogConfigs + config.Server.MaxSendQBytes, err = bytefmt.ToBytes(config.Server.MaxSendQString) + if err != nil { + return nil, fmt.Errorf("Could not parse maximum SendQ size (make sure it only contains whole numbers): %s", err.Error()) + } + return config, nil } diff --git a/irc/server.go b/irc/server.go index 8d4466ba..2c8238fe 100644 --- a/irc/server.go +++ b/irc/server.go @@ -104,6 +104,7 @@ type Server struct { listeners map[string]ListenerInterface listenerUpdateMutex sync.Mutex logger *logger.Manager + MaxSendQBytes uint64 monitoring map[string][]Client motdLines []string name string @@ -211,6 +212,7 @@ func NewServer(configFilename string, config *Config, logger *logger.Manager) (* }, listeners: make(map[string]ListenerInterface), logger: logger, + MaxSendQBytes: config.Server.MaxSendQBytes, monitoring: make(map[string][]Client), name: config.Server.Name, nameCasefolded: casefoldedName, @@ -1413,6 +1415,18 @@ func (server *Server) rehash() error { accountReg := NewAccountRegistration(config.Accounts.Registration) server.accountRegistration = &accountReg + // set new sendqueue size + if config.Server.MaxSendQBytes != server.MaxSendQBytes { + server.MaxSendQBytes = config.Server.MaxSendQBytes + + // update on all clients + server.clients.ByNickMutex.RLock() + for _, sClient := range server.clients.ByNick { + sClient.socket.MaxSendQBytes = config.Server.MaxSendQBytes + } + server.clients.ByNickMutex.RUnlock() + } + // set RPL_ISUPPORT oldISupportList := server.isupport server.setISupport() diff --git a/irc/socket.go b/irc/socket.go index 3c76649e..37c7cd0f 100644 --- a/irc/socket.go +++ b/irc/socket.go @@ -30,16 +30,19 @@ type Socket struct { conn net.Conn reader *bufio.Reader + MaxSendQBytes uint64 + lineToSendExists chan bool linesToSend []string linesToSendMutex sync.Mutex } // NewSocket returns a new Socket. -func NewSocket(conn net.Conn) Socket { +func NewSocket(conn net.Conn, maxSendQBytes uint64) Socket { return Socket{ conn: conn, reader: bufio.NewReader(conn), + MaxSendQBytes: maxSendQBytes, lineToSendExists: make(chan bool), } } @@ -130,6 +133,19 @@ func (socket *Socket) RunSocketWriter() { case <-socket.lineToSendExists: socket.linesToSendMutex.Lock() + // check sendq + var sendQBytes uint64 + for _, line := range socket.linesToSend { + sendQBytes += uint64(len(line)) + if socket.MaxSendQBytes < sendQBytes { + break + } + } + if socket.MaxSendQBytes < sendQBytes { + socket.conn.Write([]byte("\r\nERROR :SendQ Exceeded\r\n")) + break + } + // get data data := socket.linesToSend[0] if len(socket.linesToSend) > 1 { diff --git a/oragono.yaml b/oragono.yaml index d9eec794..5d8cbf7f 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -65,6 +65,9 @@ server: # if you change the motd, you should move it to ircd.motd motd: oragono.motd + # maximum length of clients' sendQ in bytes + max-sendq: 16k + # maximum number of connections per subnet connection-limits: # whether to throttle limits or not From c3be2d0d46217e11fa9dc51a692694c81fb97692 Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Thu, 23 Mar 2017 12:07:23 +1000 Subject: [PATCH 4/6] socket: Fixup sending code so we can support more connections --- irc/socket.go | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/irc/socket.go b/irc/socket.go index 37c7cd0f..e4e4806d 100644 --- a/irc/socket.go +++ b/irc/socket.go @@ -50,7 +50,12 @@ func NewSocket(conn net.Conn, maxSendQBytes uint64) Socket { // Close stops a Socket from being able to send/receive any more data. func (socket *Socket) Close() { socket.Closed = true - // socket will close once all data has been sent + + // 'send data' to force close loop to happen + socket.linesToSendMutex.Lock() + socket.linesToSend = append(socket.linesToSend, "") + socket.linesToSendMutex.Unlock() + go socket.fillLineToSendExists() } // CertFP returns the fingerprint of the certificate provided by the client. @@ -155,12 +160,21 @@ func (socket *Socket) RunSocketWriter() { } // write data - _, err := socket.conn.Write([]byte(data)) - if err != nil { - errOut = true - fmt.Println(err.Error()) + if 0 < len(data) { + _, err := socket.conn.Write([]byte(data)) + if err != nil { + errOut = true + fmt.Println(err.Error()) + break + } + } + + // check if we're closed + if socket.Closed { + socket.linesToSendMutex.Unlock() break } + socket.linesToSendMutex.Unlock() } if errOut { From f5536d794591619f9f905c22508805a890e6e2dd Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Thu, 23 Mar 2017 12:12:39 +1000 Subject: [PATCH 5/6] socket: Add a simple println when SendQ exceeded, config comment update --- irc/socket.go | 1 + oragono.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/irc/socket.go b/irc/socket.go index e4e4806d..95952c40 100644 --- a/irc/socket.go +++ b/irc/socket.go @@ -148,6 +148,7 @@ func (socket *Socket) RunSocketWriter() { } if socket.MaxSendQBytes < sendQBytes { socket.conn.Write([]byte("\r\nERROR :SendQ Exceeded\r\n")) + fmt.Println("SendQ exceeded, disconnected client") break } diff --git a/oragono.yaml b/oragono.yaml index 5d8cbf7f..f176d1e0 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -66,6 +66,7 @@ server: motd: oragono.motd # maximum length of clients' sendQ in bytes + # this should be big enough to hold /LIST and HELP replies max-sendq: 16k # maximum number of connections per subnet From 03fc6f767ec840484da2beea7db4a2e0a74efb8b Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Thu, 23 Mar 2017 12:17:31 +1000 Subject: [PATCH 6/6] changelog: Note new socket handling --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8941e94..17d97523 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,9 @@ New release of Oragono! * Added draft IRCv3 capability [draft/sts](http://ircv3.net/specs/core/sts-3.3.html). ### Changed -* Logging is now much better and useful. -* Can now specify years, months and days (e.g. `1y12m30d`) with DLINE and KLINE. +* `DLINE` and `KLINE` now let you specify years, months and days (e.g. `1y12m30d`) in durations. +* Logging is now much more useful, displays colours and can log to disk. +* Socket handling has been rewritten, which means we should support more connections more effectively (thanks dp- for the suggestion!). ### Removed