github.com/mholt/caddy-l4@v0.0.0-20241104153248-ec8fae209322/modules/l4dns/matcher_test.go (about)

     1  // Copyright 2024 VNXME
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package l4dns
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"io"
    21  	"net"
    22  	"testing"
    23  
    24  	"github.com/caddyserver/caddy/v2"
    25  	"go.uber.org/zap"
    26  
    27  	"github.com/mholt/caddy-l4/layer4"
    28  )
    29  
    30  func assertNoError(t *testing.T, err error) {
    31  	t.Helper()
    32  	if err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) {
    33  		t.Fatalf("Unexpected error: %s\n", err)
    34  	}
    35  }
    36  
    37  func Test_MatchDNS_Match(t *testing.T) {
    38  	type test struct {
    39  		matcher       *MatchDNS
    40  		data          []byte
    41  		shouldMatch   bool
    42  		shouldFakeTCP bool
    43  	}
    44  
    45  	tests := []test{
    46  		{matcher: &MatchDNS{}, data: []byte{}, shouldMatch: false},
    47  
    48  		{matcher: &MatchDNS{}, data: udpPacketAppleComA[:12], shouldMatch: false},
    49  		{matcher: &MatchDNS{}, data: udpPacketGoogleComA[:14], shouldMatch: false},
    50  		{matcher: &MatchDNS{}, data: tcpPacketAppleComA[:14], shouldMatch: false, shouldFakeTCP: true},
    51  		{matcher: &MatchDNS{}, data: tcpPacketGoogleComA[:16], shouldMatch: false, shouldFakeTCP: true},
    52  
    53  		{matcher: &MatchDNS{}, data: udpPacketAppleComA, shouldMatch: true},
    54  		{matcher: &MatchDNS{}, data: udpPacketGoogleComA, shouldMatch: true},
    55  		{matcher: &MatchDNS{}, data: tcpPacketAppleComA, shouldMatch: true, shouldFakeTCP: true},
    56  		{matcher: &MatchDNS{}, data: tcpPacketGoogleComA, shouldMatch: true, shouldFakeTCP: true},
    57  
    58  		{matcher: &MatchDNS{Allow: MatchDNSRules{&MatchDNSRule{Name: "example.com.", Type: "NS"}}},
    59  			data: tcpPacketExampleComA, shouldMatch: false, shouldFakeTCP: true},
    60  		{matcher: &MatchDNS{Allow: MatchDNSRules{&MatchDNSRule{Name: "example.com.", Type: "A", Class: "IN"}}},
    61  			data: tcpPacketExampleComA, shouldMatch: true, shouldFakeTCP: true},
    62  		{matcher: &MatchDNS{Allow: MatchDNSRules{&MatchDNSRule{TypeRegexp: "^(MX|NS)$"}}},
    63  			data: tcpPacketExampleComA, shouldMatch: false, shouldFakeTCP: true},
    64  		{matcher: &MatchDNS{Allow: MatchDNSRules{&MatchDNSRule{NameRegexp: "^(|[-0-9a-z]+\\.)example\\.com\\.$"}}},
    65  			data: tcpPacketExampleComA, shouldMatch: true, shouldFakeTCP: true},
    66  
    67  		{matcher: &MatchDNS{Deny: MatchDNSRules{&MatchDNSRule{Name: ".", Class: "IN"}}},
    68  			data: tcpPacketDotNS, shouldMatch: false, shouldFakeTCP: true},
    69  		{matcher: &MatchDNS{Deny: MatchDNSRules{&MatchDNSRule{Type: "A"}}},
    70  			data: tcpPacketDotNS, shouldMatch: true, shouldFakeTCP: true},
    71  
    72  		{matcher: &MatchDNS{
    73  			Allow: MatchDNSRules{&MatchDNSRule{Name: "example.com.", Type: "A"}},
    74  			Deny:  MatchDNSRules{&MatchDNSRule{Class: "IN"}},
    75  		}, data: tcpPacketExampleComA, shouldMatch: false, shouldFakeTCP: true},
    76  		{matcher: &MatchDNS{
    77  			Allow: MatchDNSRules{&MatchDNSRule{Name: "example.com.", Type: "NS"}},
    78  			Deny:  MatchDNSRules{&MatchDNSRule{Type: "MX"}},
    79  		}, data: tcpPacketExampleComA, shouldMatch: true, shouldFakeTCP: true},
    80  		{matcher: &MatchDNS{
    81  			Allow:       MatchDNSRules{&MatchDNSRule{Name: "example.com.", Type: "NS"}},
    82  			Deny:        MatchDNSRules{&MatchDNSRule{Type: "MX"}},
    83  			DefaultDeny: true,
    84  		}, data: tcpPacketExampleComA, shouldMatch: false, shouldFakeTCP: true},
    85  		{matcher: &MatchDNS{
    86  			Allow:       MatchDNSRules{&MatchDNSRule{Name: "example.com.", Type: "A"}},
    87  			Deny:        MatchDNSRules{&MatchDNSRule{Class: "IN"}},
    88  			PreferAllow: true,
    89  		}, data: tcpPacketExampleComA, shouldMatch: true, shouldFakeTCP: true},
    90  	}
    91  
    92  	ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
    93  	defer cancel()
    94  
    95  	for i, tc := range tests {
    96  		func() {
    97  			err := tc.matcher.Provision(ctx)
    98  			assertNoError(t, err)
    99  
   100  			in, out := net.Pipe()
   101  			defer func() {
   102  				_, _ = io.Copy(io.Discard, out)
   103  				_ = out.Close()
   104  			}()
   105  
   106  			if tc.shouldFakeTCP {
   107  				out = &fakeTCPConn{Conn: out}
   108  			}
   109  
   110  			cx := layer4.WrapConnection(out, []byte{}, zap.NewNop())
   111  			go func() {
   112  				_, err := in.Write(tc.data)
   113  				assertNoError(t, err)
   114  				_ = in.Close()
   115  			}()
   116  
   117  			matched, err := tc.matcher.Match(cx)
   118  			assertNoError(t, err)
   119  
   120  			if matched != tc.shouldMatch {
   121  				if tc.shouldMatch {
   122  					t.Fatalf("test %d: matcher did not match | %+v\n", i, tc.matcher)
   123  				} else {
   124  					t.Fatalf("test %d: matcher should not match | %+v\n", i, tc.matcher)
   125  				}
   126  			}
   127  		}()
   128  	}
   129  }
   130  
   131  type fakeTCPConn struct {
   132  	net.Conn
   133  }
   134  
   135  func (c *fakeTCPConn) LocalAddr() net.Addr {
   136  	return &net.TCPAddr{}
   137  }
   138  
   139  // Interface guard
   140  var _ net.Conn = (*fakeTCPConn)(nil)
   141  
   142  // Packet examples
   143  var tcpPacketAppleComA = []byte{
   144  	0, 27,
   145  	126, 193, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0,
   146  	5, 97, 112, 112, 108, 101, 3, 99, 111, 109, 0, 0, 1, 0, 1, // apple.com (A, IN)
   147  }
   148  var udpPacketAppleComA = []byte{
   149  	0, 7, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0,
   150  	5, 97, 112, 112, 108, 101, 3, 99, 111, 109, 0, 0, 1, 0, 1, // apple.com (A, IN)
   151  }
   152  var tcpPacketGoogleComA = []byte{
   153  	0, 28,
   154  	207, 90, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0,
   155  	6, 103, 111, 111, 103, 108, 101, 3, 99, 111, 109, 0, 0, 5, 0, 1, // google.com. (A, IN)
   156  }
   157  var udpPacketGoogleComA = []byte{
   158  	0, 11, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0,
   159  	6, 103, 111, 111, 103, 108, 101, 3, 99, 111, 109, 0, 0, 1, 0, 1, // google.com. (A, IN)
   160  }
   161  var tcpPacketExampleComA = []byte{
   162  	0, 29,
   163  	101, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0,
   164  	7, 101, 120, 97, 109, 112, 108, 101, 3, 99, 111, 109, 0, 0, 1, 0, 1, // example.com. (A, IN)
   165  }
   166  var tcpPacketDotNS = []byte{
   167  	0, 17,
   168  	213, 147, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0,
   169  	0, 0, 2, 0, 1, // . (NS, IN)
   170  }