diff --git a/config.go b/config.go index f26cd1b66..109b99400 100644 --- a/config.go +++ b/config.go @@ -184,7 +184,7 @@ type Config struct { //nolint:dupl // InsecureSkipVerifyHello, if true and when acting as server, allow client to // skip hello verify phase and receive ServerHello after initial ClientHello. - // This have implication on DoS attack resistance. + // This has implications on DoS attack resistance. InsecureSkipVerifyHello bool // ConnectionIDGenerator generates connection identifiers that should be @@ -227,6 +227,16 @@ type Config struct { //nolint:dupl // message is sent from a server. The returned handshake message replaces the original message. CertificateRequestMessageHook func(handshake.MessageCertificateRequest) handshake.Message + // outboundHandshakePacketInterceptor is an optional callback that can be set to + // intercept outgoing raw handshake packets. It is called with the raw packet bytes + // and a boolean flag specifying if this is the last packet of a flight. + // The interceptor can decide to drop the packet by returning true. + outboundHandshakePacketInterceptor func(packet []byte, end bool) bool + // inboundHandshakePacketNotifier is an optional callback that can be set to + // receive notifications about incoming raw handshake packets. It is called with + // the raw packet bytes after the packet has been processed. + inboundHandshakePacketNotifier func(packet []byte) + // OnConnectionAttempt is fired Whenever a connection attempt is made, // the server or application can call this callback function. // The callback function can then implement logic to handle the connection attempt, such as logging the attempt, diff --git a/conn.go b/conn.go index e08151f4e..93785de2d 100644 --- a/conn.go +++ b/conn.go @@ -89,6 +89,7 @@ type Conn struct { reading chan struct{} handshakeRecv chan recvHandshakeState + inboundPacketInject chan addrPkt cancelHandshaker func() cancelHandshakeReader func() @@ -96,6 +97,11 @@ type Conn struct { replayProtectionWindow uint + // Allows intercepting and rerouting outgoing handshake packets. + outboundHandshakePacketInterceptor func(packet []byte, end bool) bool + // Allows getting notified about incoming handshake packets. + inboundHandshakePacketNotifier func(packet []byte) + handshakeConfig *handshakeConfig } @@ -234,12 +240,16 @@ func createConn( reading: make(chan struct{}, 1), handshakeRecv: make(chan recvHandshakeState), + inboundPacketInject: make(chan addrPkt), closed: closer.NewCloser(), cancelHandshaker: func() {}, cancelHandshakeReader: func() {}, replayProtectionWindow: uint(replayProtectionWindow), //nolint:gosec // G115 + outboundHandshakePacketInterceptor: config.outboundHandshakePacketInterceptor, + inboundHandshakePacketNotifier: config.inboundHandshakePacketNotifier, + state: State{ isClient: isClient, }, @@ -533,14 +543,15 @@ func (c *Conn) RemoteSRTPMasterKeyIdentifier() ([]byte, bool) { return c.state.remoteSRTPMasterKeyIdentifier, true } -func (c *Conn) writePackets(ctx context.Context, pkts []*packet) error { +//nolint:cyclop +func (c *Conn) writeHandshakePackets(ctx context.Context, pkts []*packet) error { c.lock.Lock() defer c.lock.Unlock() var rawPackets [][]byte - for _, pkt := range pkts { - if dtlsHandshake, ok := pkt.record.Content.(*handshake.Handshake); ok { + dtlsHandshake, ok := pkt.record.Content.(*handshake.Handshake) + if ok { // Not true for change cipher spec. handshakeRaw, err := pkt.record.Marshal() if err != nil { return err @@ -571,6 +582,39 @@ func (c *Conn) writePackets(ctx context.Context, pkts []*packet) error { rawPackets = append(rawPackets, rawPacket) } } + + if len(rawPackets) == 0 { + return nil + } + compactedRawPackets := c.compactRawPackets(rawPackets) + + for idx, compactedRawPacket := range compactedRawPackets { + if c.outboundHandshakePacketInterceptor != nil { + if c.outboundHandshakePacketInterceptor(compactedRawPacket, idx == len(compactedRawPackets)-1) { + continue + } + } + if _, err := c.nextConn.WriteToContext(ctx, compactedRawPacket, c.rAddr); err != nil { + return netError(err) + } + } + + return nil +} + +func (c *Conn) writePackets(ctx context.Context, pkts []*packet) error { + c.lock.Lock() + defer c.lock.Unlock() + + var rawPackets [][]byte + + for _, pkt := range pkts { + rawPacket, err := c.processPacket(pkt) + if err != nil { + return err + } + rawPackets = append(rawPackets, rawPacket) + } if len(rawPackets) == 0 { return nil } @@ -797,20 +841,61 @@ var poolReadBuffer = sync.Pool{ //nolint:gochecknoglobals }, } -func (c *Conn) readAndBuffer(ctx context.Context) error { //nolint:cyclop - bufptr, ok := poolReadBuffer.Get().(*[]byte) - if !ok { - return errFailedToAccessPoolReadBuffer +func (c *Conn) InjectInboundPacket(p []byte, rAddr net.Addr) { + c.inboundPacketInject <- addrPkt{rAddr, p} +} + +func (c *Conn) nextPacket(ctx context.Context) ([]byte, net.Addr, error) { + type readResult struct { + data []byte + rAddr net.Addr + err error } - defer poolReadBuffer.Put(bufptr) + readCh := make(chan readResult, 1) + + go func() { + bufptr, ok := poolReadBuffer.Get().(*[]byte) + if !ok { + readCh <- readResult{err: errFailedToAccessPoolReadBuffer} - b := *bufptr - i, rAddr, err := c.nextConn.ReadFromContext(ctx, b) + return + } + b := *bufptr + + i, rAddr, err := c.nextConn.ReadFromContext(ctx, b) + if err != nil { + readCh <- readResult{err: err} + poolReadBuffer.Put(bufptr) + + return + } + + data := make([]byte, i) + copy(data, b[:i]) + poolReadBuffer.Put(bufptr) + + readCh <- readResult{ + data: data, + rAddr: rAddr, + } + }() + select { + case p := <-c.inboundPacketInject: + return p.data, p.rAddr, nil + case p := <-readCh: + return p.data, p.rAddr, p.err + case <-ctx.Done(): + return nil, nil, ctx.Err() + } +} + +func (c *Conn) readAndBuffer(ctx context.Context) error { //nolint:cyclop + data, rAddr, err := c.nextPacket(ctx) if err != nil { return netError(err) } - pkts, err := recordlayer.ContentAwareUnpackDatagram(b[:i], len(c.state.getLocalConnectionID())) + pkts, err := recordlayer.ContentAwareUnpackDatagram(data, len(c.state.getLocalConnectionID())) if err != nil { return err } @@ -841,6 +926,11 @@ func (c *Conn) readAndBuffer(ctx context.Context) error { //nolint:cyclop } } if hasHandshake { + if c.inboundHandshakePacketNotifier != nil { + // Would it be useful to know this was injected? + // Should this work on a copy? + c.inboundHandshakePacketNotifier(data) + } s := recvHandshakeState{ done: make(chan struct{}), isRetransmit: isRetransmit, @@ -848,7 +938,7 @@ func (c *Conn) readAndBuffer(ctx context.Context) error { //nolint:cyclop select { case c.handshakeRecv <- s: // If the other party may retransmit the flight, - // we should respond even if it not a new message. + // we should respond even if it is not a new message. <-s.done case <-c.fsm.Done(): } diff --git a/conn_test.go b/conn_test.go index e7e33b05b..be8967cd2 100644 --- a/conn_test.go +++ b/conn_test.go @@ -168,7 +168,7 @@ func TestSequenceNumberOverflow(t *testing.T) { atomic.StoreUint64(&ca.state.localSequenceNumber[0], recordlayer.MaxSequenceNumber+1) // Try to send handshake packet. - werr := ca.writePackets(ctx, []*packet{ + werr := ca.writeHandshakePackets(ctx, []*packet{ { record: &recordlayer.RecordLayer{ Header: recordlayer.Header{ @@ -3500,3 +3500,162 @@ func TestCloseWithoutHandshake(t *testing.T) { assert.NoError(t, err) assert.NoError(t, server.Close()) } + +func TestOutboundInterceptor(t *testing.T) { + defer test.CheckRoutines(t)() + defer test.TimeOut(time.Second * 10).Stop() + + ca, cb := dpipe.Pipe() + serverCert, err := selfsign.GenerateSelfSigned() + assert.NoError(t, err) + + var client *Conn + server, err := Server(dtlsnet.PacketConnFromConn(cb), cb.RemoteAddr(), &Config{ + Certificates: []tls.Certificate{serverCert}, + outboundHandshakePacketInterceptor: func(packet []byte, end bool) bool { + client.InjectInboundPacket(packet, ca.RemoteAddr()) + + return true + }, + InsecureSkipVerify: true, + InsecureSkipVerifyHello: true, + }) + assert.NoError(t, err) + + go func() { + _ = server.Handshake() + }() + + clientCert, err := selfsign.GenerateSelfSigned() + assert.NoError(t, err) + + client, err = Client(dtlsnet.PacketConnFromConn(ca), ca.RemoteAddr(), &Config{ + Certificates: []tls.Certificate{clientCert}, + outboundHandshakePacketInterceptor: func(packet []byte, end bool) bool { + server.InjectInboundPacket(packet, cb.RemoteAddr()) + + return true + }, + InsecureSkipVerify: true, + InsecureSkipVerifyHello: true, + }) + assert.NoError(t, err) + + assert.NoError(t, client.Handshake()) + assert.NoError(t, server.Close()) + assert.NoError(t, client.Close()) +} + +func TestOutboundInterceptorSmallMtuFlush(t *testing.T) { + defer test.CheckRoutines(t)() + defer test.TimeOut(time.Second * 10).Stop() + + ca, cb := dpipe.Pipe() + serverCert, err := selfsign.GenerateSelfSigned() + assert.NoError(t, err) + + var client *Conn + serverPackets, serverFlights := 0, 0 + server, err := Server(dtlsnet.PacketConnFromConn(cb), cb.RemoteAddr(), &Config{ + Certificates: []tls.Certificate{serverCert}, + outboundHandshakePacketInterceptor: func(packet []byte, end bool) bool { + serverPackets++ + if end { + serverFlights++ + } + client.InjectInboundPacket(packet, ca.RemoteAddr()) + + return true + }, + InsecureSkipVerify: true, + InsecureSkipVerifyHello: true, + MTU: 400, + }) + assert.NoError(t, err) + + go func() { + _ = server.Handshake() + }() + + clientCert, err := selfsign.GenerateSelfSigned() + assert.NoError(t, err) + + clientPackets, clientFlights := 0, 0 + client, err = Client(dtlsnet.PacketConnFromConn(ca), ca.RemoteAddr(), &Config{ + Certificates: []tls.Certificate{clientCert}, + outboundHandshakePacketInterceptor: func(packet []byte, end bool) bool { + clientPackets++ + if end { + clientFlights++ + } + server.InjectInboundPacket(packet, cb.RemoteAddr()) + + return true + }, + InsecureSkipVerify: true, + InsecureSkipVerifyHello: true, + MTU: 500, + }) + assert.NoError(t, err) + + assert.NoError(t, client.Handshake()) + assert.NoError(t, server.Close()) + assert.NoError(t, client.Close()) + assert.Equal(t, 2, clientPackets) + assert.Equal(t, 2, clientFlights) + assert.Equal(t, 4, serverPackets) + assert.Equal(t, 2, serverFlights) +} + +func TestInboundNotifier(t *testing.T) { + defer test.CheckRoutines(t)() + defer test.TimeOut(time.Second * 10).Stop() + + ca, cb := dpipe.Pipe() + serverCert, err := selfsign.GenerateSelfSigned() + assert.NoError(t, err) + + var inboundHandshakePackets [][]byte + server, err := Server(dtlsnet.PacketConnFromConn(cb), cb.RemoteAddr(), &Config{ + Certificates: []tls.Certificate{serverCert}, + inboundHandshakePacketNotifier: func(packet []byte) { + data := make([]byte, len(packet)) + copy(data, packet) + inboundHandshakePackets = append(inboundHandshakePackets, data) + }, + InsecureSkipVerify: true, + InsecureSkipVerifyHello: true, + }) + assert.NoError(t, err) + + go func() { + _ = server.Handshake() + }() + + clientCert, err := selfsign.GenerateSelfSigned() + assert.NoError(t, err) + + var outboundHandshakePackets [][]byte + client, err := Client(dtlsnet.PacketConnFromConn(ca), ca.RemoteAddr(), &Config{ + Certificates: []tls.Certificate{clientCert}, + outboundHandshakePacketInterceptor: func(packet []byte, end bool) bool { + data := make([]byte, len(packet)) + copy(data, packet) + outboundHandshakePackets = append(outboundHandshakePackets, data) + + return false + }, + InsecureSkipVerify: true, + InsecureSkipVerifyHello: true, + }) + assert.NoError(t, err) + + assert.NoError(t, client.Handshake()) + assert.NoError(t, server.Close()) + assert.NoError(t, client.Close()) + assert.Equal(t, len(inboundHandshakePackets), len(outboundHandshakePackets)) + + for i := range inboundHandshakePackets { + assert.Equal(t, inboundHandshakePackets[i], outboundHandshakePackets[i]) + } +} diff --git a/errors.go b/errors.go index 0db0de679..a6fc2cdab 100644 --- a/errors.go +++ b/errors.go @@ -202,6 +202,14 @@ var ( Err: errors.New("client hello message hook option requires a non-nil function"), } //nolint:err113 + errNilOutboundHandshakePacketInterceptor = &FatalError{ + Err: errors.New("outbound handshake packet interceptor option requires a non-nil function"), + } + //nolint:err113 + errNilInboundHandshakePacketNotifier = &FatalError{ + Err: errors.New("inbound handshake packet notifier option requires a non-nil function"), + } + //nolint:err113 errNilGetCertificate = &FatalError{Err: errors.New("get certificate option requires a non-nil callback")} //nolint:err113 errNilServerHelloMessageHook = &FatalError{ diff --git a/flight1handler_test.go b/flight1handler_test.go index 5393459fb..045dca483 100644 --- a/flight1handler_test.go +++ b/flight1handler_test.go @@ -21,11 +21,14 @@ type flight1TestMockFlightConn struct{} func (f *flight1TestMockFlightConn) notify(context.Context, alert.Level, alert.Description) error { return nil } -func (f *flight1TestMockFlightConn) writePackets(context.Context, []*packet) error { return nil } -func (f *flight1TestMockFlightConn) recvHandshake() <-chan recvHandshakeState { return nil } -func (f *flight1TestMockFlightConn) setLocalEpoch(uint16) {} -func (f *flight1TestMockFlightConn) handleQueuedPackets(context.Context) error { return nil } -func (f *flight1TestMockFlightConn) sessionKey() []byte { return nil } + +func (f *flight1TestMockFlightConn) writeHandshakePackets(context.Context, []*packet) error { + return nil +} +func (f *flight1TestMockFlightConn) recvHandshake() <-chan recvHandshakeState { return nil } +func (f *flight1TestMockFlightConn) setLocalEpoch(uint16) {} +func (f *flight1TestMockFlightConn) handleQueuedPackets(context.Context) error { return nil } +func (f *flight1TestMockFlightConn) sessionKey() []byte { return nil } type flight1TestMockCipherSuite struct { ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256 diff --git a/flight4handler_test.go b/flight4handler_test.go index d570ca3ea..8d3d470a3 100644 --- a/flight4handler_test.go +++ b/flight4handler_test.go @@ -24,11 +24,14 @@ type flight4TestMockFlightConn struct{} func (f *flight4TestMockFlightConn) notify(context.Context, alert.Level, alert.Description) error { return nil } -func (f *flight4TestMockFlightConn) writePackets(context.Context, []*packet) error { return nil } -func (f *flight4TestMockFlightConn) recvHandshake() <-chan recvHandshakeState { return nil } -func (f *flight4TestMockFlightConn) setLocalEpoch(uint16) {} -func (f *flight4TestMockFlightConn) handleQueuedPackets(context.Context) error { return nil } -func (f *flight4TestMockFlightConn) sessionKey() []byte { return nil } + +func (f *flight4TestMockFlightConn) writeHandshakePackets(context.Context, []*packet) error { + return nil +} +func (f *flight4TestMockFlightConn) recvHandshake() <-chan recvHandshakeState { return nil } +func (f *flight4TestMockFlightConn) setLocalEpoch(uint16) {} +func (f *flight4TestMockFlightConn) handleQueuedPackets(context.Context) error { return nil } +func (f *flight4TestMockFlightConn) sessionKey() []byte { return nil } type flight4TestMockCipherSuite struct { ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256 diff --git a/handshaker.go b/handshaker.go index 279ab14c7..9f6cf09ce 100644 --- a/handshaker.go +++ b/handshaker.go @@ -140,7 +140,7 @@ type handshakeConfig struct { type flightConn interface { notify(ctx context.Context, level alert.Level, desc alert.Description) error - writePackets(context.Context, []*packet) error + writeHandshakePackets(context.Context, []*packet) error recvHandshake() <-chan recvHandshakeState setLocalEpoch(epoch uint16) handleQueuedPackets(context.Context) error @@ -264,7 +264,7 @@ func (s *handshakeFSM) prepare(ctx context.Context, conn flightConn) (handshakeS func (s *handshakeFSM) send(ctx context.Context, c flightConn) (handshakeState, error) { // Send flights - if err := c.writePackets(ctx, s.flights); err != nil { + if err := c.writeHandshakePackets(ctx, s.flights); err != nil { return handshakeErrored, err } diff --git a/handshaker_test.go b/handshaker_test.go index 82e8cbc8c..2e858afcb 100644 --- a/handshaker_test.go +++ b/handshaker_test.go @@ -411,7 +411,7 @@ func (c *flightTestConn) notify(context.Context, alert.Level, alert.Description) return nil } -func (c *flightTestConn) writePackets(_ context.Context, pkts []*packet) error { //nolint:cyclop +func (c *flightTestConn) writeHandshakePackets(_ context.Context, pkts []*packet) error { //nolint:cyclop time.Sleep(c.delay) isRetransmit := false for _, pkt := range pkts { diff --git a/options.go b/options.go index a1fcb8dd5..d72fc676a 100644 --- a/options.go +++ b/options.go @@ -42,44 +42,46 @@ func defensiveCopy[T any](t ...T) []T { // dtlsConfig is the internal configuration structure. // This will eventually replace the exported Config struct. type dtlsConfig struct { //nolint:dupl - certificates []tls.Certificate - cipherSuites []CipherSuiteID - customCipherSuites func() []CipherSuite - signatureSchemes []tls.SignatureScheme - certificateSignatureSchemes []tls.SignatureScheme - srtpProtectionProfiles []SRTPProtectionProfile - srtpMasterKeyIdentifier []byte - clientAuth ClientAuthType - extendedMasterSecret ExtendedMasterSecretType - flightInterval time.Duration - disableRetransmitBackoff bool - psk PSKCallback - pskIdentityHint []byte - insecureSkipVerify bool - insecureHashes bool - verifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error - verifyConnection func(*State) error - rootCAs *x509.CertPool - clientCAs *x509.CertPool - serverName string - loggerFactory logging.LoggerFactory - mtu int - replayProtectionWindow int - keyLogWriter io.Writer - sessionStore SessionStore - supportedProtocols []string - ellipticCurves []elliptic.Curve - getCertificate func(*ClientHelloInfo) (*tls.Certificate, error) - getClientCertificate func(*CertificateRequestInfo) (*tls.Certificate, error) - insecureSkipVerifyHello bool - connectionIDGenerator func() []byte - paddingLengthGenerator func(uint) uint - helloRandomBytesGenerator func() [handshake.RandomBytesLength]byte - clientHelloMessageHook func(handshake.MessageClientHello) handshake.Message - serverHelloMessageHook func(handshake.MessageServerHello) handshake.Message - certificateRequestMessageHook func(handshake.MessageCertificateRequest) handshake.Message - onConnectionAttempt func(net.Addr) error - listenConfig net.ListenConfig + certificates []tls.Certificate + cipherSuites []CipherSuiteID + customCipherSuites func() []CipherSuite + signatureSchemes []tls.SignatureScheme + certificateSignatureSchemes []tls.SignatureScheme + srtpProtectionProfiles []SRTPProtectionProfile + srtpMasterKeyIdentifier []byte + clientAuth ClientAuthType + extendedMasterSecret ExtendedMasterSecretType + flightInterval time.Duration + disableRetransmitBackoff bool + psk PSKCallback + pskIdentityHint []byte + insecureSkipVerify bool + insecureHashes bool + verifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error + verifyConnection func(*State) error + rootCAs *x509.CertPool + clientCAs *x509.CertPool + serverName string + loggerFactory logging.LoggerFactory + mtu int + replayProtectionWindow int + keyLogWriter io.Writer + sessionStore SessionStore + supportedProtocols []string + ellipticCurves []elliptic.Curve + getCertificate func(*ClientHelloInfo) (*tls.Certificate, error) + getClientCertificate func(*CertificateRequestInfo) (*tls.Certificate, error) + insecureSkipVerifyHello bool + connectionIDGenerator func() []byte + paddingLengthGenerator func(uint) uint + helloRandomBytesGenerator func() [handshake.RandomBytesLength]byte + clientHelloMessageHook func(handshake.MessageClientHello) handshake.Message + serverHelloMessageHook func(handshake.MessageServerHello) handshake.Message + certificateRequestMessageHook func(handshake.MessageCertificateRequest) handshake.Message + onConnectionAttempt func(net.Addr) error + listenConfig net.ListenConfig + outboundHandshakePacketInterceptor func(packet []byte, end bool) bool + inboundHandshakePacketNotifier func(packet []byte) } // applyDefaults applies default values to the config. @@ -95,35 +97,37 @@ func (c *dtlsConfig) applyDefaults() { // All slice fields are copied to ensure immutability. func (c *dtlsConfig) toConfig() *Config { config := &Config{ - CustomCipherSuites: c.customCipherSuites, - ClientAuth: c.clientAuth, - ExtendedMasterSecret: c.extendedMasterSecret, - FlightInterval: c.flightInterval, - DisableRetransmitBackoff: c.disableRetransmitBackoff, - PSK: c.psk, - InsecureSkipVerify: c.insecureSkipVerify, - InsecureHashes: c.insecureHashes, - VerifyPeerCertificate: c.verifyPeerCertificate, - VerifyConnection: c.verifyConnection, - RootCAs: c.rootCAs, - ClientCAs: c.clientCAs, - ServerName: c.serverName, - LoggerFactory: c.loggerFactory, - MTU: c.mtu, - ReplayProtectionWindow: c.replayProtectionWindow, - KeyLogWriter: c.keyLogWriter, - SessionStore: c.sessionStore, - GetCertificate: c.getCertificate, - GetClientCertificate: c.getClientCertificate, - InsecureSkipVerifyHello: c.insecureSkipVerifyHello, - ConnectionIDGenerator: c.connectionIDGenerator, - PaddingLengthGenerator: c.paddingLengthGenerator, - HelloRandomBytesGenerator: c.helloRandomBytesGenerator, - ClientHelloMessageHook: c.clientHelloMessageHook, - ServerHelloMessageHook: c.serverHelloMessageHook, - CertificateRequestMessageHook: c.certificateRequestMessageHook, - OnConnectionAttempt: c.onConnectionAttempt, - listenConfig: c.listenConfig, + CustomCipherSuites: c.customCipherSuites, + ClientAuth: c.clientAuth, + ExtendedMasterSecret: c.extendedMasterSecret, + FlightInterval: c.flightInterval, + DisableRetransmitBackoff: c.disableRetransmitBackoff, + PSK: c.psk, + InsecureSkipVerify: c.insecureSkipVerify, + InsecureHashes: c.insecureHashes, + VerifyPeerCertificate: c.verifyPeerCertificate, + VerifyConnection: c.verifyConnection, + RootCAs: c.rootCAs, + ClientCAs: c.clientCAs, + ServerName: c.serverName, + LoggerFactory: c.loggerFactory, + MTU: c.mtu, + ReplayProtectionWindow: c.replayProtectionWindow, + KeyLogWriter: c.keyLogWriter, + SessionStore: c.sessionStore, + GetCertificate: c.getCertificate, + GetClientCertificate: c.getClientCertificate, + InsecureSkipVerifyHello: c.insecureSkipVerifyHello, + ConnectionIDGenerator: c.connectionIDGenerator, + PaddingLengthGenerator: c.paddingLengthGenerator, + HelloRandomBytesGenerator: c.helloRandomBytesGenerator, + ClientHelloMessageHook: c.clientHelloMessageHook, + ServerHelloMessageHook: c.serverHelloMessageHook, + CertificateRequestMessageHook: c.certificateRequestMessageHook, + OnConnectionAttempt: c.onConnectionAttempt, + listenConfig: c.listenConfig, + outboundHandshakePacketInterceptor: c.outboundHandshakePacketInterceptor, + inboundHandshakePacketNotifier: c.inboundHandshakePacketNotifier, } if len(c.certificates) > 0 { @@ -561,6 +565,32 @@ func WithClientHelloMessageHook(fn func(handshake.MessageClientHello) handshake. }) } +// WithOutboundHandshakePacketInterceptor allows installing an outbound handshake +// packet interceptor. +func WithOutboundHandshakePacketInterceptor(fn func(packet []byte, end bool) bool) Option { + return sharedOption(func(c *dtlsConfig) error { + if fn == nil { + return errNilOutboundHandshakePacketInterceptor + } + c.outboundHandshakePacketInterceptor = fn + + return nil + }) +} + +// WithInboundHandshakePacketNotifier allows installing an inbound handshake +// packet notifier. +func WithInboundHandshakePacketNotifier(fn func(packet []byte)) Option { + return sharedOption(func(c *dtlsConfig) error { + if fn == nil { + return errNilInboundHandshakePacketNotifier + } + c.inboundHandshakePacketNotifier = fn + + return nil + }) +} + // serverOnlyOption wraps an apply function for server-only options. type serverOnlyOption func(*dtlsConfig) error diff --git a/options_test.go b/options_test.go index 2683984a4..0872328ec 100644 --- a/options_test.go +++ b/options_test.go @@ -194,6 +194,22 @@ func TestNilCallbackOptionsReturnError(t *testing.T) { _, err = buildServerConfig(WithClientHelloMessageHook(nil)) require.ErrorIs(t, err, errNilClientHelloMessageHook) }) + + t.Run("NilOutboundHandshakePacketInterceptor", func(t *testing.T) { + _, err := buildClientConfig(WithOutboundHandshakePacketInterceptor(nil)) + require.ErrorIs(t, err, errNilOutboundHandshakePacketInterceptor) + + _, err = buildServerConfig(WithOutboundHandshakePacketInterceptor(nil)) + require.ErrorIs(t, err, errNilOutboundHandshakePacketInterceptor) + }) + + t.Run("NilInboundHandshakePacketNotifier", func(t *testing.T) { + _, err := buildClientConfig(WithInboundHandshakePacketNotifier(nil)) + require.ErrorIs(t, err, errNilInboundHandshakePacketNotifier) + + _, err = buildServerConfig(WithInboundHandshakePacketNotifier(nil)) + require.ErrorIs(t, err, errNilInboundHandshakePacketNotifier) + }) } // TestServerOnlyNilCallbackOptionsReturnError verifies server-only options