Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix warnings, possible linter failures and typos #223

Merged
merged 14 commits into from
Aug 8, 2024
Merged
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,29 +31,29 @@ This app works similarly to the `http` app. You define servers, and each server
Current matchers:

- **layer4.matchers.http** - matches connections that start with HTTP requests. In addition, any [`http.matchers` modules](https://caddyserver.com/docs/modules/) can be used for matching on HTTP-specific properties of requests, such as header or path. Note that only the first request of each connection can be used for matching.
- **layer4.matchers.tls** - matches connections that start with TLS handshakes. In addition, any [`tls.handshake_match` modules](https://caddyserver.com/docs/modules/) can be used for matching on TLS-specific properties of the ClientHello, such as ServerName (SNI).
- **layer4.matchers.ssh** - matches connections that look like SSH connections.
- **layer4.matchers.postgres** - matches connections that look like Postgres connections.
- **layer4.matchers.remote_ip** - matches connections based on remote IP (or CIDR range).
- **layer4.matchers.local_ip** - matches connections based on local IP (or CIDR range).
- **layer4.matchers.not** - matches connections that aren't matched by inner matcher sets.
- **layer4.matchers.postgres** - matches connections that look like Postgres connections.
- **layer4.matchers.proxy_protocol** - matches connections that start with [HAPROXY proxy protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt).
- **layer4.matchers.rdp** - matches connections that look like [RDP](https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-RDPBCGR/%5BMS-RDPBCGR%5D.pdf).
- **layer4.matchers.regexp** - matches connections that have the first packet bytes matching a regular expression.
- **layer4.matchers.remote_ip** - matches connections based on remote IP (or CIDR range).
- **layer4.matchers.socks4** - matches connections that look like [SOCKSv4](https://www.openssh.com/txt/socks4.protocol).
- **layer4.matchers.socks5** - matches connections that look like [SOCKSv5](https://www.rfc-editor.org/rfc/rfc1928.html).
- **layer4.matchers.ssh** - matches connections that look like SSH connections.
- **layer4.matchers.tls** - matches connections that start with TLS handshakes. In addition, any [`tls.handshake_match` modules](https://caddyserver.com/docs/modules/) can be used for matching on TLS-specific properties of the ClientHello, such as ServerName (SNI).
- **layer4.matchers.xmpp** - matches connections that look like [XMPP](https://xmpp.org/about/technology-overview/).

Current handlers:

- **layer4.handlers.echo** - An echo server.
- **layer4.handlers.proxy** - Powerful layer 4 proxy, capable of multiple upstreams (with load balancing and health checks) and establishing new TLS connections to backends. Optionally supports sending the [HAProxy proxy protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt).
- **layer4.handlers.proxy_protocol** - Accepts the [HAPROXY proxy protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) on the receiving side.
- **layer4.handlers.socks5** - Handles [SOCKSv5](https://www.rfc-editor.org/rfc/rfc1928.html) proxy protocol connections.
- **layer4.handlers.subroute** - Implements recursion logic, i.e. allows to match and handle already matched connections.
- **layer4.handlers.tee** - Branches the handling of a connection into a concurrent handler chain.
- **layer4.handlers.throttle** - Throttle connections to simulate slowness and latency.
- **layer4.handlers.tls** - TLS termination.
- **layer4.handlers.proxy_protocol** - Accepts the [HAPROXY proxy protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) on the receiving side.
- **layer4.handlers.socks5** - Handles [SOCKSv5](https://www.rfc-editor.org/rfc/rfc1928.html) proxy protocol connections.

Like the `http` app, some handlers are "terminal" meaning that they don't call the next handler in the chain. For example: `echo` and `proxy` are terminal handlers because they consume the client's input.

Expand All @@ -74,7 +74,7 @@ Alternatively, to hack on the plugin code, you can clone it down, then build and

## Writing config

Since this app does not support Caddyfile (yet?), you will have to use Caddy's native JSON format to configure it. I highly recommend [this caddy-json-schema plugin by @abiosoft](https://github.com/abiosoft/caddy-json-schema) which can give you auto-complete and documentation right in your editor as you write your config!
This app supports Caddyfile, but you may also use Caddy's native JSON format to configure it. I highly recommend [this caddy-json-schema plugin by @abiosoft](https://github.com/abiosoft/caddy-json-schema) which can give you auto-complete and documentation right in your editor as you write your config!

See below for some examples to help you get started.

Expand Down
1 change: 1 addition & 0 deletions integration/caddyfile_adapt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"

"github.com/caddyserver/caddy/v2/caddytest"

_ "github.com/mholt/caddy-l4"
)

Expand Down
10 changes: 5 additions & 5 deletions layer4/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
)

func init() {
caddy.RegisterModule(App{})
caddy.RegisterModule(&App{})
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registering a module doesn't require an allocation, it just needs an example value.

(Same with other module(s) below)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for the value/pointer receiver warnings, my approach is as follows. We do need a pointer receiver in Provision functions, so that fields could be updated in a struct. We don’t have functions where a value receiver is required, so that no field would be updated unintentionally. Golang allows structs to have both value and pointer receivers, but it’s recommended to avoid mixing them for a single struct - that’s what the warning basically says. Thus, my suggestion is to use pointer receivers everywhere unless value receivers are strictly required.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the module registration is never the same instance being used for provisioning. Those are separate. I'm not sure what linter you're using, but it's definitely overly aggressive on that. I don't think I agree with that rule.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I’m talking about is written in the golang official docs. E.g. see https://go.dev/tour/methods/8, paragraph 5. It’s a general rule to have either value or pointer receivers for all struct functions, but not both.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module is now registered here with & just because CaddyModule function has a pointer receiver instead of a value receiver it used to have. My tests show this way of module registration works fine.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's interesting... and yeah, I mean, it'll work... it's just unusual in terms of Caddy convention.

(I think our JSON docs generator handles pointer values here correctly when it does its static analysis.)

Typically what I do is determine whether a pointer or value receiver is the correct/conservative thing to use, and choose that. If the value isn't used, or if a copy is permissible (no values from the sync package, for example, in the struct), I prefer a value receiver, I think this avoids an allocation? I'm not sure. But it also makes nil pointer derefs impossible which is nice.

Anyway... I dunno. Still feels weird to use a pointer when it's not needed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we have basically 2 options here:

  1. revert the "mixed value and pointer receivers" fix and keep both types of receivers for various structs of caddy-l4;
  2. accept this fix and use only pointer receivers because we must have a pointer receiver in Provision functions.

If there is more support for the first option, I will adjust this PR.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So after asking teh Twitter what they do, the results are really mixed. Many use only pointer receivers (consistency, avoids bugs). A good number only use pointers if that is most appropriate (self-documenting code, mutable structs, etc).

I am fine with the change as-is I guess :) Thanks!

}

// App is a Caddy app that operates closest to layer 4 of the OSI model.
Expand All @@ -40,7 +40,7 @@ type App struct {
}

// CaddyModule returns the Caddy module information.
func (App) CaddyModule() caddy.ModuleInfo {
func (*App) CaddyModule() caddy.ModuleInfo {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, this doesn't need to be a pointer. Maybe it doesn't matter. I don't think I'm used to using pointers for the Module registration stuff. 🤷‍♂️

return caddy.ModuleInfo{
ID: "layer4",
New: func() caddy.Module { return new(App) },
Expand Down Expand Up @@ -76,11 +76,11 @@ func (a *App) Start() error {
case net.Listener:
a.listeners = append(a.listeners, ln)
lnAddr = caddy.JoinNetworkAddress(ln.Addr().Network(), ln.Addr().String(), "")
go s.serve(ln)
go func() { _ = s.serve(ln) }()
case net.PacketConn:
a.packetConns = append(a.packetConns, ln)
lnAddr = caddy.JoinNetworkAddress(ln.LocalAddr().Network(), ln.LocalAddr().String(), "")
go s.servePacket(ln)
go func() { _ = s.servePacket(ln) }()
}
s.logger.Debug("listening", zap.String("address", lnAddr))
}
Expand All @@ -90,7 +90,7 @@ func (a *App) Start() error {
}

// Stop stops the servers and closes all listeners.
func (a App) Stop() error {
func (a *App) Stop() error {
for _, pc := range a.packetConns {
err := pc.Close()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion layer4/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ func (cx *Connection) GetVar(key string) interface{} {
}

// MatchingBytes returns all bytes currently available for matching. This is only intended for reading.
// Do not write into the slice. It's a view of the internal buffer and you will likely mess up the connection.
// Do not write into the slice. It's a view of the internal buffer, and you will likely mess up the connection.
func (cx *Connection) MatchingBytes() []byte {
return cx.buf[cx.offset:]
}
Expand Down
10 changes: 5 additions & 5 deletions layer4/connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ import (

func TestConnection_FreezeAndUnfreeze(t *testing.T) {
in, out := net.Pipe()
defer in.Close()
defer out.Close()
defer func() { _ = in.Close() }()
defer func() { _ = out.Close() }()

cx := WrapConnection(out, []byte{}, zap.NewNop())
defer cx.Close()
defer func() { _ = cx.Close() }()

matcherData := []byte("foo")
consumeData := []byte("bar")

buf := make([]byte, len(matcherData))

go func() {
in.Write(matcherData)
in.Write(consumeData)
_, _ = in.Write(matcherData)
_, _ = in.Write(consumeData)
}()

// prefetch like server handler would
Expand Down
15 changes: 8 additions & 7 deletions layer4/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
)

func init() {
caddy.RegisterModule(ListenerWrapper{})
caddy.RegisterModule(&ListenerWrapper{})
}

// ListenerWrapper is a Caddy module that wraps App as a listener wrapper, it doesn't support udp.
Expand All @@ -33,7 +33,7 @@ type ListenerWrapper struct {
}

// CaddyModule returns the Caddy module information.
func (ListenerWrapper) CaddyModule() caddy.ModuleInfo {
func (*ListenerWrapper) CaddyModule() caddy.ModuleInfo {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't finished my review yet but does this actually solve a problem? I guess I don't care either way in the cases where this isn't even used (there's not even a variable named for it) but I guess I just am curious.

return caddy.ModuleInfo{
ID: "caddy.listeners.layer4",
New: func() caddy.Module { return new(ListenerWrapper) },
Expand Down Expand Up @@ -126,7 +126,8 @@ type listener struct {
func (l *listener) loop() {
for {
conn, err := l.Listener.Accept()
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
var nerr net.Error
if errors.As(err, &nerr) && nerr.Temporary() {
l.logger.Error("temporary error accepting connection", zap.Error(err))
continue
}
Expand All @@ -145,7 +146,7 @@ func (l *listener) loop() {
close(l.connChan)
}()
for conn := range l.connChan {
conn.Close()
_ = conn.Close()
}
}

Expand All @@ -156,8 +157,8 @@ func (l *listener) handle(conn net.Conn) {
var err error
defer func() {
l.wg.Done()
if err != errHijacked {
conn.Close()
if !errors.Is(err, errHijacked) {
_ = conn.Close()
}
}()

Expand All @@ -171,7 +172,7 @@ func (l *listener) handle(conn net.Conn) {
start := time.Now()
err = l.compiledRoute.Handle(cx)
duration := time.Since(start)
if err != nil && err != errHijacked {
if err != nil && !errors.Is(err, errHijacked) {
l.logger.Error("handling connection", zap.Error(err))
}

Expand Down
34 changes: 17 additions & 17 deletions layer4/matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ import (
)

func init() {
caddy.RegisterModule(MatchRemoteIP{})
caddy.RegisterModule(MatchLocalIP{})
caddy.RegisterModule(MatchNot{})
caddy.RegisterModule(&MatchRemoteIP{})
caddy.RegisterModule(&MatchLocalIP{})
caddy.RegisterModule(&MatchNot{})
}

// ConnMatcher is a type that can match a connection.
Expand Down Expand Up @@ -83,14 +83,14 @@ type MatcherSets []MatcherSet
// AnyMatch returns true if the connection matches any of the matcher sets
// in mss or if there are no matchers, in which case the request always
// matches. Any error terminates matching.
func (mss MatcherSets) AnyMatch(cx *Connection) (matched bool, err error) {
for _, m := range mss {
func (mss *MatcherSets) AnyMatch(cx *Connection) (matched bool, err error) {
for _, m := range *mss {
matched, err = m.Match(cx)
if matched || err != nil {
return
}
}
matched = len(mss) == 0
matched = len(*mss) == 0
return
}

Expand All @@ -117,7 +117,7 @@ type MatchRemoteIP struct {
}

// CaddyModule returns the Caddy module information.
func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo {
func (*MatchRemoteIP) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "layer4.matchers.remote_ip",
New: func() caddy.Module { return new(MatchRemoteIP) },
Expand All @@ -134,7 +134,7 @@ func (m *MatchRemoteIP) Provision(_ caddy.Context) (err error) {
}

// Match returns true if the connection is from one of the designated IP ranges.
func (m MatchRemoteIP) Match(cx *Connection) (bool, error) {
func (m *MatchRemoteIP) Match(cx *Connection) (bool, error) {
clientIP, err := m.getRemoteIP(cx)
if err != nil {
return false, fmt.Errorf("getting remote IP: %v", err)
Expand All @@ -147,7 +147,7 @@ func (m MatchRemoteIP) Match(cx *Connection) (bool, error) {
return false, nil
}

func (m MatchRemoteIP) getRemoteIP(cx *Connection) (netip.Addr, error) {
func (m *MatchRemoteIP) getRemoteIP(cx *Connection) (netip.Addr, error) {
remote := cx.Conn.RemoteAddr().String()

ipStr, _, err := net.SplitHostPort(remote)
Expand All @@ -164,7 +164,7 @@ func (m MatchRemoteIP) getRemoteIP(cx *Connection) (netip.Addr, error) {

// UnmarshalCaddyfile sets up the MatchRemoteIP from Caddyfile tokens. Syntax:
//
// ip <ranges...>
// remote_ip <ranges...>
func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
_, wrapper := d.Next(), d.Val() // consume wrapper name

Expand Down Expand Up @@ -198,15 +198,15 @@ type MatchLocalIP struct {
}

// CaddyModule returns the Caddy module information.
func (MatchLocalIP) CaddyModule() caddy.ModuleInfo {
func (*MatchLocalIP) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "layer4.matchers.local_ip",
New: func() caddy.Module { return new(MatchLocalIP) },
}
}

// Provision parses m's IP ranges, either from IP or CIDR expressions.
func (m *MatchLocalIP) Provision(ctx caddy.Context) error {
func (m *MatchLocalIP) Provision(_ caddy.Context) error {
ipnets, err := ParseNetworks(m.Ranges)
if err != nil {
return err
Expand All @@ -216,7 +216,7 @@ func (m *MatchLocalIP) Provision(ctx caddy.Context) error {
}

// Match returns true if the connection is from one of the designated IP ranges.
func (m MatchLocalIP) Match(cx *Connection) (bool, error) {
func (m *MatchLocalIP) Match(cx *Connection) (bool, error) {
localIP, err := m.getLocalIP(cx)
if err != nil {
return false, fmt.Errorf("getting local IP: %v", err)
Expand All @@ -229,7 +229,7 @@ func (m MatchLocalIP) Match(cx *Connection) (bool, error) {
return false, nil
}

func (m MatchLocalIP) getLocalIP(cx *Connection) (netip.Addr, error) {
func (m *MatchLocalIP) getLocalIP(cx *Connection) (netip.Addr, error) {
remote := cx.Conn.LocalAddr().String()

ipStr, _, err := net.SplitHostPort(remote)
Expand Down Expand Up @@ -300,7 +300,7 @@ type MatchNot struct {
}

// CaddyModule implements caddy.Module.
func (MatchNot) CaddyModule() caddy.ModuleInfo {
func (*MatchNot) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "layer4.matchers.not",
New: func() caddy.Module { return new(MatchNot) },
Expand All @@ -315,7 +315,7 @@ func (m *MatchNot) UnmarshalJSON(data []byte) error {

// MarshalJSON satisfies json.Marshaler by marshaling
// m's raw matcher sets.
func (m MatchNot) MarshalJSON() ([]byte, error) {
func (m *MatchNot) MarshalJSON() ([]byte, error) {
return json.Marshal(m.MatcherSetsRaw)
}

Expand All @@ -338,7 +338,7 @@ func (m *MatchNot) Provision(ctx caddy.Context) error {
// Match returns true if r matches m. Since this matcher negates
// the embedded matchers, false is returned if any of its matcher
// sets return true.
func (m MatchNot) Match(r *Connection) (bool, error) {
func (m *MatchNot) Match(r *Connection) (bool, error) {
for _, ms := range m.MatcherSets {
match, err := ms.Match(r)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion layer4/matchers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type provisionableMatcher interface {
}

func provision(in provisionableMatcher) ConnMatcher {
in.Provision(caddy.Context{})
_ = in.Provision(caddy.Context{})
return in
}
func TestNotMatcher(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion layer4/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type Route struct {
// Matchers define the conditions upon which to execute the handlers.
// All matchers within the same set must match, and at least one set
// must match; in other words, matchers are AND'ed together within a
// set, but multiple sets are OR'ed together. No matchers matches all.
// set, but multiple sets are OR'ed together. No matchers match all.
MatcherSetsRaw []caddy.ModuleMap `json:"match,omitempty" caddy:"namespace=layer4.matchers"`

// Handlers define the behavior for handling the stream. They are
Expand Down
6 changes: 3 additions & 3 deletions layer4/routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ func TestMatchingTimeoutWorks(t *testing.T) {
}))

in, out := net.Pipe()
defer in.Close()
defer out.Close()
defer func() { _ = in.Close() }()
defer func() { _ = out.Close() }()

cx := WrapConnection(out, []byte{}, zap.NewNop())
defer cx.Close()
defer func() { _ = cx.Close() }()

err = compiledRoutes.Handle(cx)
if err != nil {
Expand Down
Loading
Loading