Maintenance: improve stream merging

This commit is contained in:
Quentin McGaw
2021-01-26 04:17:22 +00:00
parent 937d09f1c3
commit a243d48fb1
13 changed files with 321 additions and 248 deletions

View File

@@ -3,16 +3,14 @@ package openvpn
import (
"context"
"fmt"
"io"
"strings"
"github.com/qdm12/gluetun/internal/constants"
)
func (c *configurator) Start(ctx context.Context) (stdout io.ReadCloser, waitFn func() error, err error) {
func (c *configurator) Start(ctx context.Context) (stdoutLines, stderrLines chan string, waitError chan error, err error) {
c.logger.Info("starting openvpn")
stdout, _, waitFn, err = c.commander.Start(ctx, "openvpn", "--config", string(constants.OpenVPNConf))
return stdout, waitFn, err
return c.commander.Start(ctx, "openvpn", "--config", string(constants.OpenVPNConf))
}
func (c *configurator) Version(ctx context.Context) (string, error) {

100
internal/openvpn/logs.go Normal file
View File

@@ -0,0 +1,100 @@
package openvpn
import (
"strings"
"sync"
"github.com/fatih/color"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/logging"
)
func (l *looper) collectLines(wg *sync.WaitGroup, stdout, stderr <-chan string) {
defer wg.Done()
var line string
var ok, errLine bool
for {
errLine = false
select {
case line, ok = <-stdout:
case line, ok = <-stderr:
errLine = true
}
if !ok {
return
}
line, level := processLogLine(line)
if len(line) == 0 {
continue // filtered out
}
if errLine {
level = logging.ErrorLevel
}
switch level {
case logging.DebugLevel:
l.logger.Debug(line)
case logging.InfoLevel:
l.logger.Info(line)
case logging.WarnLevel:
l.logger.Warn(line)
case logging.ErrorLevel:
l.logger.Error(line)
}
if strings.Contains(line, "Initialization Sequence Completed") {
l.tunnelReady <- struct{}{}
}
}
}
func processLogLine(s string) (filtered string, level logging.Level) {
for _, ignored := range []string{
"WARNING: you are using user/group/chroot/setcon without persist-tun -- this may cause restarts to fail",
"NOTE: UID/GID downgrade will be delayed because of --client, --pull, or --up-delay",
} {
if s == ignored {
return "", ""
}
}
switch {
case strings.HasPrefix(s, "NOTE: "):
filtered = strings.TrimPrefix(s, "NOTE: ")
level = logging.InfoLevel
case strings.HasPrefix(s, "WARNING: "):
filtered = strings.TrimPrefix(s, "WARNING: ")
level = logging.WarnLevel
case strings.HasPrefix(s, "Options error: "):
filtered = strings.TrimPrefix(s, "Options error: ")
level = logging.ErrorLevel
case s == "Initialization Sequence Completed":
return color.HiGreenString(s), logging.InfoLevel
case s == "AUTH: Received control message: AUTH_FAILED":
filtered = s + `
Your credentials might be wrong 🤨
💡 If you use Private Internet Access, check https://github.com/qdm12/gluetun/issues/265
`
level = logging.ErrorLevel
case strings.Contains(s, "TLS Error: TLS key negotiation failed to occur within 60 seconds (check your network connectivity)"):
s += `
🚒🚒🚒🚒🚒🚨🚨🚨🚨🚨🚨🚒🚒🚒🚒🚒
That error usually happens because either:
1. The VPN server IP address you are trying to connect to is no longer valid 🔌
Update your server information using https://github.com/qdm12/gluetun/wiki/Updating-Servers
2. The VPN server crashed 💥, try changing your VPN servers filtering options such as REGION
3. Your Internet connection is not working 🤯, ensure it works
4. Something else ➡️ https://github.com/qdm12/gluetun/issues/new/choose
`
default:
filtered = s
level = logging.InfoLevel
}
filtered = constants.ColorOpenvpn().Sprintf(filtered)
return filtered, level
}

View File

@@ -0,0 +1,57 @@
package openvpn
import (
"testing"
"github.com/qdm12/golibs/logging"
"github.com/stretchr/testify/assert"
)
func Test_processLogLine(t *testing.T) {
t.Parallel()
tests := map[string]struct {
s string
filtered string
level logging.Level
}{
"empty string": {"", "", logging.InfoLevel},
"random string": {"asdasqdb", "asdasqdb", logging.InfoLevel},
"openvpn unknown": {
"message",
"message",
logging.InfoLevel},
"openvpn note": {
"NOTE: message",
"message",
logging.InfoLevel},
"openvpn warning": {
"WARNING: message",
"message",
logging.WarnLevel},
"openvpn options error": {
"Options error: message",
"message",
logging.ErrorLevel},
"openvpn ignored message": {
"NOTE: UID/GID downgrade will be delayed because of --client, --pull, or --up-delay",
"",
""},
"openvpn success": {
"Initialization Sequence Completed",
"Initialization Sequence Completed",
logging.InfoLevel},
"openvpn auth failed": {
"AUTH: Received control message: AUTH_FAILED",
"AUTH: Received control message: AUTH_FAILED\n\nYour credentials might be wrong 🤨\n\n💡 If you use Private Internet Access, check https://github.com/qdm12/gluetun/issues/265\n\n", //nolint:lll
logging.ErrorLevel},
}
for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
filtered, level := processLogLine(tc.s)
assert.Equal(t, tc.filtered, filtered)
assert.Equal(t, tc.level, level)
})
}
}

View File

@@ -14,7 +14,6 @@ import (
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/routing"
"github.com/qdm12/gluetun/internal/settings"
"github.com/qdm12/golibs/command"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/os"
)
@@ -45,7 +44,7 @@ type looper struct {
logger, pfLogger logging.Logger
client *http.Client
openFile os.OpenFileFunc
streamMerger command.StreamMerger
tunnelReady chan<- struct{}
cancel context.CancelFunc
// Internal channels and locks
loopLock sync.Mutex
@@ -63,7 +62,7 @@ func NewLooper(settings settings.OpenVPN,
username string, puid, pgid int, allServers models.AllServers,
conf Configurator, fw firewall.Configurator, routing routing.Routing,
logger logging.Logger, client *http.Client, openFile os.OpenFileFunc,
streamMerger command.StreamMerger, cancel context.CancelFunc) Looper {
tunnelReady chan<- struct{}, cancel context.CancelFunc) Looper {
return &looper{
state: state{
status: constants.Stopped,
@@ -80,7 +79,7 @@ func NewLooper(settings settings.OpenVPN,
pfLogger: logger.WithPrefix("port forwarding: "),
client: client,
openFile: openFile,
streamMerger: streamMerger,
tunnelReady: tunnelReady,
cancel: cancel,
start: make(chan struct{}),
running: make(chan models.LoopStatus),
@@ -144,7 +143,7 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
openvpnCtx, openvpnCancel := context.WithCancel(context.Background())
stream, waitFn, err := l.conf.Start(openvpnCtx)
stdoutLines, stderrLines, waitError, err := l.conf.Start(openvpnCtx)
if err != nil {
openvpnCancel()
l.signalCrashedStatus()
@@ -152,6 +151,9 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
continue
}
wg.Add(1)
go l.collectLines(wg, stdoutLines, stderrLines)
// Needs the stream line from main.go to know when the tunnel is up
go func(ctx context.Context) {
for {
@@ -166,13 +168,6 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
}
}(openvpnCtx)
go l.streamMerger.Merge(openvpnCtx, stream, command.MergeName("openvpn"))
waitError := make(chan error)
go func() {
err := waitFn() // blocking
waitError <- err
}()
if l.crashed {
l.crashed = false
l.backoffTime = defaultBackoffTime
@@ -189,6 +184,8 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
openvpnCancel()
<-waitError
close(waitError)
close(stdoutLines)
close(stderrLines)
return
case <-l.stop:
l.logger.Info("stopping")
@@ -207,6 +204,8 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
}
}
close(waitError)
close(stdoutLines)
close(stderrLines)
openvpnCancel() // just for the linter
}
}

View File

@@ -2,7 +2,6 @@ package openvpn
import (
"context"
"io"
"github.com/qdm12/gluetun/internal/unix"
"github.com/qdm12/golibs/command"
@@ -15,7 +14,8 @@ type Configurator interface {
WriteAuthFile(user, password string, puid, pgid int) error
CheckTUN() error
CreateTUN() error
Start(ctx context.Context) (stdout io.ReadCloser, waitFn func() error, err error)
Start(ctx context.Context) (stdoutLines, stderrLines chan string,
waitError chan error, err error)
}
type configurator struct {