44 "bytes"
55 "context"
66 "fmt"
7+ "net/netip"
78
89 "github.com/AdguardTeam/golibs/errors"
910 "github.com/AdguardTeam/golibs/logutil/slogutil"
@@ -274,24 +275,27 @@ func (iface *dhcpInterfaceV6) handleRenew(
274275 // handler does.
275276 //
276277 // See RFC 9915 Section 18.3.4.
277- resp .Options = iface .newRenewRespOpts (fd , req , cliID , layers.DHCPv6Option {})
278+ resp .Options = iface .newUpdateRespOpts (fd , req , cliID , layers.DHCPv6Option {})
278279
279280 return respond6 (fd , resp )
280281 }
281282
282- ianaOpt := iface .ianaForRenew (ctx , req , iana , fd .ether .SrcMAC )
283- resp .Options = iface .newRenewRespOpts (fd , req , cliID , ianaOpt )
283+ ianaOpt := iface .ianaForUpdate (ctx , req , iana , fd .ether .SrcMAC )
284+ resp .Options = iface .newUpdateRespOpts (fd , req , cliID , ianaOpt )
284285
285286 return respond6 (fd , resp )
286287}
287288
288289// handleRebind handles messages of type REBIND. req must not be nil and must
289- // be a valid DHCPv6 message of type REBIND. fd must be valid.
290+ // be a valid DHCPv6 message of type REBIND, fd must be valid.
290291//
291- // TODO(e.burkov): Implement. This is a stub for now.
292+ // TODO(e.burkov): The current implementation rebinds only the first valid
293+ // IA_NA option. It does not verify that the addresses in the IA match the
294+ // stored lease, since clients are identified by MAC address rather than
295+ // DUID+IAID.
292296func (iface * dhcpInterfaceV6 ) handleRebind (
293297 ctx context.Context ,
294- _ * frameData6 ,
298+ fd * frameData6 ,
295299 req * layers.DHCPv6 ,
296300) (err error ) {
297301 cliID , err := clientIDNoServer (req .Options )
@@ -302,31 +306,54 @@ func (iface *dhcpInterfaceV6) handleRebind(
302306 l := iface .common .logger
303307 l .DebugContext (ctx , "handling message" , "type" , req .MsgType , "cli_id" , cliID )
304308
305- return nil
309+ iface .common .indexMu .Lock ()
310+ defer iface .common .indexMu .Unlock ()
311+
312+ resp := & layers.DHCPv6 {
313+ MsgType : layers .DHCPv6MsgTypeReply ,
314+ TransactionID : req .TransactionID ,
315+ }
316+
317+ iana , ok := iface .firstIANA (ctx , req )
318+ if ! ok {
319+ // With no IA_NA options and no requested addresses there's nothing to
320+ // rebind. Respond with no IA options similarly to how the Renew
321+ // handler does.
322+ //
323+ // See RFC 9915 Section 18.3.5.
324+ resp .Options = iface .newUpdateRespOpts (fd , req , cliID , layers.DHCPv6Option {})
325+
326+ return respond6 (fd , resp )
327+ }
328+
329+ ianaOpt := iface .ianaForUpdate (ctx , req , iana , fd .ether .SrcMAC )
330+ resp .Options = iface .newUpdateRespOpts (fd , req , cliID , ianaOpt )
331+
332+ return respond6 (fd , resp )
306333}
307334
308335// handleInfo handles messages of type INFORMATION-REQUEST. req must not be nil
309336// and must be a valid DHCPv6 message of type INFORMATION-REQUEST. fd must be
310337// valid.
311338//
312- // TODO(e.burkov): Implement. This is a stub for now.
339+ // TODO(e.burkov): The current implementation does not handle relay-forwarded
340+ // INFORMATION-REQUEST messages.
313341func (iface * dhcpInterfaceV6 ) handleInfo (
314342 ctx context.Context ,
315343 fd * frameData6 ,
316344 req * layers.DHCPv6 ,
317345) (err error ) {
318- if srvID , ok := findOption6 (req .Options , layers .DHCPv6OptServerID ); ok {
319- if ! bytes .Equal (srvID , fd .duidData ) {
320- return fmt .Errorf (
321- "dhcpv6: server id: got %v, want %v: %w" ,
322- srvID ,
323- fd .duidData ,
324- errors .ErrNotEqual ,
325- )
326- }
346+ srvID , ok := findOption6 (req .Options , layers .DHCPv6OptServerID )
347+ if ok && ! bytes .Equal (srvID , fd .duidData ) {
348+ return fmt .Errorf (
349+ "dhcpv6: server id: got %v, want %v: %w" ,
350+ srvID ,
351+ fd .duidData ,
352+ errors .ErrNotEqual ,
353+ )
327354 }
328355
329- _ , ok : = findOption6 (req .Options , layers .DHCPv6OptIANA )
356+ _ , ok = findOption6 (req .Options , layers .DHCPv6OptIANA )
330357 if ok {
331358 return fmt .Errorf ("dhcpv6: %s: ia option: %w" , req .MsgType , errors .ErrUnexpectedValue )
332359 }
@@ -339,13 +366,20 @@ func (iface *dhcpInterfaceV6) handleInfo(
339366 l := iface .common .logger
340367 l .DebugContext (ctx , "handling message" , "type" , req .MsgType )
341368
342- return nil
369+ resp := & layers.DHCPv6 {
370+ MsgType : layers .DHCPv6MsgTypeReply ,
371+ TransactionID : req .TransactionID ,
372+ Options : iface .newInfoRespOpts (fd , req ),
373+ }
374+
375+ return respond6 (fd , resp )
343376}
344377
345378// handleRelease handles messages of type RELEASE. req must not be nil and must
346379// be a valid DHCPv6 message of type RELEASE. fd must be valid.
347380//
348- // TODO(e.burkov): Implement. This is a stub for now.
381+ // TODO(e.burkov): Verify all IA_NA options instead of handling only the first
382+ // one.
349383func (iface * dhcpInterfaceV6 ) handleRelease (
350384 ctx context.Context ,
351385 fd * frameData6 ,
@@ -359,7 +393,43 @@ func (iface *dhcpInterfaceV6) handleRelease(
359393 l := iface .common .logger
360394 l .DebugContext (ctx , "handling message" , "type" , req .MsgType , "cli_id" , cliID )
361395
362- return nil
396+ resp := & layers.DHCPv6 {
397+ MsgType : layers .DHCPv6MsgTypeReply ,
398+ TransactionID : req .TransactionID ,
399+ }
400+
401+ iaid , ip := iface .ipForRelease (ctx , req )
402+ if ip == (netip.Addr {}) {
403+ resp .Options = iface .newUpdateRespOpts (fd , req , cliID , layers.DHCPv6Option {})
404+
405+ return respond6 (fd , resp )
406+ }
407+
408+ key := macToKey (fd .ether .SrcMAC )
409+
410+ iface .common .indexMu .Lock ()
411+ defer iface .common .indexMu .Unlock ()
412+
413+ lease , hasLease := iface .common .leases [key ]
414+ if ! hasLease || lease .IP != ip {
415+ respIANA := newIANAWithStatus (iaid , layers .DHCPv6StatusCodeNoBinding )
416+ resp .Options = iface .newUpdateRespOpts (fd , req , cliID , respIANA )
417+
418+ return respond6 (fd , resp )
419+ }
420+
421+ err = iface .common .index .remove (ctx , lease , iface .common )
422+ if err != nil {
423+ l .ErrorContext (ctx , "removing lease" , slogutil .KeyError , err )
424+
425+ respIANA := newIANAWithStatus (iaid , layers .DHCPv6StatusCodeNoBinding )
426+ resp .Options = iface .newUpdateRespOpts (fd , req , cliID , respIANA )
427+ } else {
428+ respIANA := & IANAOption {ID : iaid }
429+ resp .Options = iface .newUpdateRespOpts (fd , req , cliID , respIANA .Encode ())
430+ }
431+
432+ return respond6 (fd , resp )
363433}
364434
365435// handleDecline handles messages of type DECLINE. req must not be nil and must
0 commit comments