github.com/mholt/caddy-l4@v0.0.0-20241104153248-ec8fae209322/modules/l4wireguard/matcher.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 l4wireguard
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/binary"
    20  	"io"
    21  	"strconv"
    22  
    23  	"github.com/caddyserver/caddy/v2"
    24  	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
    25  
    26  	"github.com/mholt/caddy-l4/layer4"
    27  )
    28  
    29  func init() {
    30  	caddy.RegisterModule(&MatchWireGuard{})
    31  }
    32  
    33  // MatchWireGuard is able to match WireGuard connections.
    34  type MatchWireGuard struct {
    35  	// Zero may be used to match reserved zero bytes of Type field when
    36  	// they have non-zero values (e.g. for obfuscation purposes). E.g. it
    37  	// may be set to 4,285,988,864 (0xFF770000) in order to match custom
    38  	// handshake initiation messages starting with 0x010077FF byte sequence.
    39  	// Note: any non-zero value is a violation of the WireGuard protocol.
    40  	Zero uint32 `json:"zero,omitempty"`
    41  }
    42  
    43  // CaddyModule returns the Caddy module information.
    44  func (m *MatchWireGuard) CaddyModule() caddy.ModuleInfo {
    45  	return caddy.ModuleInfo{
    46  		ID:  "layer4.matchers.wireguard",
    47  		New: func() caddy.Module { return new(MatchWireGuard) },
    48  	}
    49  }
    50  
    51  // Match returns true if the connection looks like WireGuard.
    52  func (m *MatchWireGuard) Match(cx *layer4.Connection) (bool, error) {
    53  	// Read a number of bytes
    54  	buf := make([]byte, MessageInitiationBytesTotal+1)
    55  	n, err := io.ReadAtLeast(cx, buf, 1)
    56  	if err != nil {
    57  		return false, err
    58  	}
    59  
    60  	switch n {
    61  	case MessageInitiationBytesTotal: // This is a handshake initiation message
    62  		// Parse MessageInitiation
    63  		msg := &MessageInitiation{}
    64  		if err = msg.FromBytes(buf[:MessageInitiationBytesTotal]); err != nil {
    65  			return false, nil
    66  		}
    67  
    68  		// Validate MessageInitiation
    69  		if msg.Type != (m.Zero&ReservedZeroFilter)|MessageTypeInitiation {
    70  			return false, nil
    71  		}
    72  	case MessageTransportBytesMin: // This is a keepalive message (with empty content)
    73  		// Parse MessageTransport
    74  		msg := &MessageTransport{}
    75  		if err = msg.FromBytes(buf[:MessageTransportBytesMin]); err != nil {
    76  			return false, nil
    77  		}
    78  
    79  		// Validate MessageTransport
    80  		if msg.Type != (m.Zero&ReservedZeroFilter)|MessageTypeTransport {
    81  			return false, nil
    82  		}
    83  	default: // This is anything else, can also be a valid non-empty transport message
    84  		return false, nil
    85  	}
    86  
    87  	return true, nil
    88  }
    89  
    90  // Provision prepares m's internal structures.
    91  func (m *MatchWireGuard) Provision(_ caddy.Context) error {
    92  	return nil
    93  }
    94  
    95  // UnmarshalCaddyfile sets up the MatchWireGuard from Caddyfile tokens. Syntax:
    96  //
    97  //	wireguard [<zero>]
    98  func (m *MatchWireGuard) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
    99  	_, wrapper := d.Next(), d.Val() // consume wrapper name
   100  
   101  	// Only one same-line argument is supported
   102  	if d.CountRemainingArgs() > 1 {
   103  		return d.ArgErr()
   104  	}
   105  
   106  	if d.NextArg() {
   107  		val, err := strconv.ParseUint(d.Val(), 10, 32)
   108  		if err != nil {
   109  			return d.Errf("parsing %s zero: %v", wrapper, err)
   110  		}
   111  		m.Zero = uint32(val)
   112  	}
   113  
   114  	// No blocks are supported
   115  	if d.NextBlock(d.Nesting()) {
   116  		return d.Errf("malformed %s option: blocks are not supported", wrapper)
   117  	}
   118  
   119  	return nil
   120  
   121  }
   122  
   123  // MessageInitiation is the first message
   124  // which the initiator sends to the responder.
   125  type MessageInitiation struct {
   126  	Type      uint32
   127  	Sender    uint32
   128  	Ephemeral [32]uint8
   129  	Static    [32 + Poly1305TagSize]uint8
   130  	Timestamp [12 + Poly1305TagSize]uint8
   131  	MAC1      [16]uint8
   132  	MAC2      [16]uint8
   133  }
   134  
   135  func (msg *MessageInitiation) FromBytes(src []byte) error {
   136  	buf := bytes.NewBuffer(src)
   137  	if err := binary.Read(buf, MessageBytesOrder, &msg.Type); err != nil {
   138  		return err
   139  	}
   140  	if err := binary.Read(buf, MessageBytesOrder, &msg.Sender); err != nil {
   141  		return err
   142  	}
   143  	if err := binary.Read(buf, MessageBytesOrder, &msg.Ephemeral); err != nil {
   144  		return err
   145  	}
   146  	if err := binary.Read(buf, MessageBytesOrder, &msg.Static); err != nil {
   147  		return err
   148  	}
   149  	if err := binary.Read(buf, MessageBytesOrder, &msg.Timestamp); err != nil {
   150  		return err
   151  	}
   152  	if err := binary.Read(buf, MessageBytesOrder, &msg.MAC1); err != nil {
   153  		return err
   154  	}
   155  	if err := binary.Read(buf, MessageBytesOrder, &msg.MAC2); err != nil {
   156  		return err
   157  	}
   158  	return nil
   159  }
   160  
   161  func (msg *MessageInitiation) ToBytes() ([]byte, error) {
   162  	dst := bytes.NewBuffer(make([]byte, 0, MessageInitiationBytesTotal))
   163  	if err := binary.Write(dst, MessageBytesOrder, &msg.Type); err != nil {
   164  		return nil, err
   165  	}
   166  	if err := binary.Write(dst, MessageBytesOrder, &msg.Sender); err != nil {
   167  		return nil, err
   168  	}
   169  	if err := binary.Write(dst, MessageBytesOrder, &msg.Ephemeral); err != nil {
   170  		return nil, err
   171  	}
   172  	if err := binary.Write(dst, MessageBytesOrder, &msg.Static); err != nil {
   173  		return nil, err
   174  	}
   175  	if err := binary.Write(dst, MessageBytesOrder, &msg.Timestamp); err != nil {
   176  		return nil, err
   177  	}
   178  	if err := binary.Write(dst, MessageBytesOrder, &msg.MAC1); err != nil {
   179  		return nil, err
   180  	}
   181  	if err := binary.Write(dst, MessageBytesOrder, &msg.MAC2); err != nil {
   182  		return nil, err
   183  	}
   184  	return dst.Bytes(), nil
   185  }
   186  
   187  // MessageTransport is the message which the initiator and
   188  // the responder exchange after a successful handshake.
   189  type MessageTransport struct {
   190  	Type     uint32
   191  	Receiver uint32
   192  	Counter  uint64
   193  	Content  []uint8
   194  }
   195  
   196  func (msg *MessageTransport) FromBytes(src []byte) error {
   197  	buf := bytes.NewBuffer(src)
   198  	if err := binary.Read(buf, MessageBytesOrder, &msg.Type); err != nil {
   199  		return err
   200  	}
   201  	if err := binary.Read(buf, MessageBytesOrder, &msg.Receiver); err != nil {
   202  		return err
   203  	}
   204  	if err := binary.Read(buf, MessageBytesOrder, &msg.Counter); err != nil {
   205  		return err
   206  	}
   207  	if buf.Len() > 0 {
   208  		msg.Content = append(msg.Content, buf.Bytes()...)
   209  	}
   210  	return nil
   211  }
   212  
   213  func (msg *MessageTransport) ToBytes() ([]byte, error) {
   214  	dst := bytes.NewBuffer(make([]byte, 0, MessageTransportBytesMin-Poly1305TagSize+len(msg.Content)))
   215  	if err := binary.Write(dst, MessageBytesOrder, &msg.Type); err != nil {
   216  		return nil, err
   217  	}
   218  	if err := binary.Write(dst, MessageBytesOrder, &msg.Receiver); err != nil {
   219  		return nil, err
   220  	}
   221  	if err := binary.Write(dst, MessageBytesOrder, &msg.Counter); err != nil {
   222  		return nil, err
   223  	}
   224  	return append(dst.Bytes(), msg.Content...), nil
   225  }
   226  
   227  // Interface guards
   228  var (
   229  	_ caddy.Provisioner     = (*MatchWireGuard)(nil)
   230  	_ caddyfile.Unmarshaler = (*MatchWireGuard)(nil)
   231  	_ layer4.ConnMatcher    = (*MatchWireGuard)(nil)
   232  )
   233  
   234  var (
   235  	MessageBytesOrder = binary.LittleEndian
   236  )
   237  
   238  // Refs:
   239  //
   240  //	https://www.wireguard.com/protocol/
   241  //	https://www.wireguard.com/papers/wireguard.pdf
   242  //	https://github.com/pirate/wireguard-docs
   243  //	https://github.com/WireGuard/wireguard-go/blob/master/device/noise-protocol.go
   244  const (
   245  	Poly1305TagSize int = 16
   246  
   247  	MessageInitiationBytesTotal  int = 148
   248  	MessageResponseBytesTotal    int = 92
   249  	MessageCookieReplyBytesTotal int = 64
   250  	MessageTransportBytesMin     int = 32
   251  
   252  	MessageTypeInitiation  uint32 = 1
   253  	MessageTypeResponse    uint32 = 2
   254  	MessageTypeCookieReply uint32 = 3
   255  	MessageTypeTransport   uint32 = 4
   256  
   257  	ReservedZeroFilter = ^(uint32(0)) >> 8 << 8
   258  )