github.com/bgpfix/bgpfix@v0.2.0/speaker/speaker.go (about)

     1  // Package speaker provides a very basic BGP speaker.
     2  package speaker
     3  
     4  import (
     5  	"context"
     6  	"sync/atomic"
     7  	"time"
     8  
     9  	"github.com/bgpfix/bgpfix/af"
    10  	"github.com/bgpfix/bgpfix/caps"
    11  	"github.com/bgpfix/bgpfix/msg"
    12  	"github.com/bgpfix/bgpfix/pipe"
    13  	"github.com/rs/zerolog"
    14  )
    15  
    16  // Speaker represents a basic BGP speaker for single-threaded use
    17  type Speaker struct {
    18  	*zerolog.Logger
    19  
    20  	ctx    context.Context
    21  	cancel context.CancelCauseFunc
    22  
    23  	pipe *pipe.Pipe  // attached BGP pipe
    24  	in   *pipe.Input // input for our messages
    25  	up   *pipe.Line  // TX line
    26  	down *pipe.Line  // RX line
    27  
    28  	Options Options     // options; do not modify after Attach()
    29  	opened  atomic.Bool // true iff OPEN already sent
    30  }
    31  
    32  // NewSpeaker returns a new Speaker. Call Speaker.Attach() next.
    33  func NewSpeaker(ctx context.Context) *Speaker {
    34  	s := &Speaker{}
    35  	s.ctx, s.cancel = context.WithCancelCause(ctx)
    36  	s.Options = DefaultOptions
    37  	return s
    38  }
    39  
    40  // Attach attaches the speaker to given pipe input.
    41  // Must not be called more than once.
    42  func (s *Speaker) Attach(p *pipe.Pipe, dst msg.Dir) error {
    43  	s.pipe = p
    44  	s.in = p.AddInput(dst)
    45  	s.up = p.LineFor(dst)
    46  	s.down = p.LineFor(dst.Flip())
    47  
    48  	// process options
    49  	opts := &s.Options
    50  	if opts.Logger != nil {
    51  		s.Logger = opts.Logger
    52  	} else {
    53  		l := zerolog.Nop()
    54  		s.Logger = &l
    55  	}
    56  
    57  	// attach
    58  	po := &s.pipe.Options
    59  	po.OnStart(s.onStart)                    // when the pipe starts
    60  	po.OnEstablished(s.onEstablished)        // when session is established
    61  	po.OnMsg(s.onOpen, s.down.Dir, msg.OPEN) // on OPEN for us
    62  
    63  	return nil
    64  }
    65  
    66  // onStart sends our OPEN message, if the speaker is not passive.
    67  func (s *Speaker) onStart(ev *pipe.Event) bool {
    68  	if !s.Options.Passive {
    69  		s.sendOpen(nil)
    70  	}
    71  	return false // unregister the handler
    72  }
    73  
    74  // onEstablished starts periodic KEEPALIVE sender
    75  func (s *Speaker) onEstablished(ev *pipe.Event) bool {
    76  	// load last OPENs
    77  	up, down := s.up.Open.Load(), s.down.Open.Load()
    78  	if up == nil || down == nil {
    79  		return false // what?!
    80  	}
    81  
    82  	// start keepaliver with common hold time
    83  	ht := min(up.HoldTime, down.HoldTime)
    84  	if ht > 0 {
    85  		go s.keepaliver(int64(ht))
    86  	}
    87  
    88  	return false // unregister the handler
    89  }
    90  
    91  func (s *Speaker) onOpen(m *msg.Msg) bool {
    92  	// TODO: validate received OPEN - drop if wrong caps / other params
    93  
    94  	// send our OPEN (nop if we did that already)
    95  	s.sendOpen(&m.Open)
    96  
    97  	// confirm the received OPEN is OK
    98  	s.sendKeepalive()
    99  
   100  	return true
   101  }
   102  
   103  func (s *Speaker) sendOpen(ro *msg.Open) {
   104  	if s.opened.Swap(true) {
   105  		return // already done
   106  	}
   107  
   108  	// local and remote OPENs
   109  	o := &s.pipe.GetMsg().Use(msg.OPEN).Open // our OPEN
   110  	if ro == nil {
   111  		ro = s.down.Open.Load()
   112  	}
   113  
   114  	// set caps from pipe and local options
   115  	opts := &s.Options
   116  	o.Caps.SetFrom(s.pipe.Caps)
   117  	o.Caps.SetFrom(opts.LocalCaps)
   118  
   119  	o.Identifier = opts.LocalId
   120  	if !o.Identifier.IsValid() && ro != nil {
   121  		o.Identifier = ro.Identifier.Prev()
   122  	}
   123  
   124  	if opts.LocalASN >= 0 {
   125  		o.SetASN(opts.LocalASN) // will add AS4
   126  	} else if opts.LocalASN < 0 && ro != nil {
   127  		o.SetASN(ro.GetASN())
   128  	} else {
   129  		o.SetASN(0)
   130  	}
   131  
   132  	if opts.LocalHoldTime >= 0 {
   133  		o.HoldTime = uint16(opts.LocalHoldTime)
   134  	} else {
   135  		o.HoldTime = msg.OPEN_HOLDTIME
   136  	}
   137  	if o.HoldTime > 0 && o.HoldTime < 3 {
   138  		o.HoldTime = 3 // correct
   139  	}
   140  
   141  	// FIXME: add real capabilities
   142  	o.Caps.Use(caps.CAP_EXTENDED_MESSAGE)
   143  	o.Caps.Use(caps.CAP_ROUTE_REFRESH)
   144  	if mp, ok := o.Caps.Use(caps.CAP_MP).(*caps.MP); ok {
   145  		mp.Add(af.AFI_IPV4, af.SAFI_UNICAST)
   146  		mp.Add(af.AFI_IPV4, af.SAFI_FLOWSPEC)
   147  
   148  		mp.Add(af.AFI_IPV6, af.SAFI_UNICAST)
   149  		mp.Add(af.AFI_IPV6, af.SAFI_FLOWSPEC)
   150  	}
   151  
   152  	// queue for sending
   153  	s.in.WriteMsg(o.Msg)
   154  }
   155  
   156  func (s *Speaker) sendKeepalive() {
   157  	m := s.pipe.GetMsg().Use(msg.KEEPALIVE)
   158  	s.in.WriteMsg(m)
   159  }
   160  
   161  // keepaliver sends a KEEPALIVE message, and keeps sending them to respect the hold time.
   162  func (s *Speaker) keepaliver(negotiated int64) {
   163  	var (
   164  		ticker    = time.NewTicker(time.Second)
   165  		now_ts    int64 // UNIX timestamp now
   166  		last_up   int64 // UNIX timestamp when we last sent something to peer
   167  		last_down int64 // UNIX timestamp when we last received something from peer
   168  	)
   169  
   170  	if negotiated < 3 {
   171  		negotiated = 3
   172  	}
   173  
   174  	for {
   175  		// wait 1s
   176  		select {
   177  		case <-s.ctx.Done():
   178  			ticker.Stop()
   179  			return
   180  		case now := <-ticker.C:
   181  			now_ts = now.Unix()
   182  		}
   183  
   184  		// remote timeout?
   185  		last_down = max(s.down.LastAlive.Load(), s.down.LastUpdate.Load(), last_down)
   186  		if delay := now_ts - last_down; delay > negotiated {
   187  			last_down = now_ts
   188  			s.Warn().Msg("remote hold timer expired")
   189  			s.pipe.Event(EVENT_PEER_TIMEOUT, delay)
   190  		}
   191  
   192  		// local timeout?
   193  		last_up = max(s.up.LastAlive.Load(), s.up.LastUpdate.Load(), last_up)
   194  		if delay := now_ts - last_up; delay >= negotiated/3 {
   195  			last_up = now_ts
   196  			s.sendKeepalive()
   197  		}
   198  	}
   199  }