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 }