Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BufferChannel for fsm incoming/outgoing channels. #2869

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,684 changes: 2,016 additions & 1,668 deletions api/gobgp.pb.go

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions api/gobgp.proto
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,9 @@ message PeerConf {
bool replace_peer_asn = 14;
bool admin_down = 15;
bool send_software_version = 16;
uint32 incoming_channel_timeout = 17;
ChannelConfig incoming_channel = 18;
ChannelConfig outgoing_channel = 19;
}

message PeerGroupConf {
Expand All @@ -634,6 +637,9 @@ message PeerGroupConf {
bool route_flap_damping = 8;
uint32 send_community = 9;
bool send_software_version = 10;
uint32 incoming_channel_timeout = 11;
ChannelConfig incoming_channel = 12;
ChannelConfig outgoing_channel = 13;
}

message PeerGroupState {
Expand Down Expand Up @@ -695,6 +701,9 @@ message PeerState {
repeated google.protobuf.Any remote_cap = 18;
repeated google.protobuf.Any local_cap = 19;
string router_id = 20;
uint64 incoming_channel_dropped = 21;
ChannelState incoming_channel = 22;
ChannelState outgoing_channel = 23;
}

message Messages {
Expand Down Expand Up @@ -1152,3 +1161,19 @@ message SetLogLevelRequest {
}
Level level = 1;
}

enum ChannelType { INFINITE = 0; BUFFER = 1; }

message ChannelConfig {
ChannelType type = 1;
uint64 size = 2;
}

message ChannelState {
uint64 in = 1;
uint64 notifications = 2;
uint64 collected = 3;
uint64 rewritten = 4;
uint64 retries = 5;
uint64 out = 6;
}
33 changes: 33 additions & 0 deletions cmd/gobgp/neighbor.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,39 @@ func showNeighbor(args []string) error {
fmt.Printf(" Hold time is %d, keepalive interval is %d seconds\n", int(p.Timers.State.NegotiatedHoldTime), int(p.Timers.State.KeepaliveInterval))
fmt.Printf(" Configured hold time is %d, keepalive interval is %d seconds\n", int(p.Timers.Config.HoldTime), int(p.Timers.Config.KeepaliveInterval))

incomingChannelType := "Infinite"
if p.Conf.IncomingChannel.Type == api.ChannelType_BUFFER {
incomingChannelType = "Buffer"
}
fmt.Printf(" IncomingChannel: %s\n", incomingChannelType)
if p.State != nil && p.State.IncomingChannel != nil {
if incomingChannelType == "Buffer" {
fmt.Printf(" In: %d\n", p.State.IncomingChannel.In)
fmt.Printf(" Notifications: %d\n", p.State.IncomingChannel.Notifications)
fmt.Printf(" Collected: %d\n", p.State.IncomingChannel.Collected)
fmt.Printf(" Rewritten: %d\n", p.State.IncomingChannel.Rewritten)
fmt.Printf(" Retries: %d\n", p.State.IncomingChannel.Retries)
fmt.Printf(" Out: %d\n", p.State.IncomingChannel.Out)
}
}
fmt.Printf(" Dropped: %d\n", p.State.IncomingChannelDropped)

outgoingChannelType := "Infinite"
if p.Conf.OutgoingChannel.Type == api.ChannelType_BUFFER {
outgoingChannelType = "Buffer"
}
fmt.Printf(" OutgoingChannel: %s\n", outgoingChannelType)
if p.State != nil && p.State.OutgoingChannel != nil {
if outgoingChannelType == "Buffer" {
fmt.Printf(" In: %d\n", p.State.OutgoingChannel.In)
fmt.Printf(" Notifications: %d\n", p.State.OutgoingChannel.Notifications)
fmt.Printf(" Collected: %d\n", p.State.OutgoingChannel.Collected)
fmt.Printf(" Rewritten: %d\n", p.State.OutgoingChannel.Rewritten)
fmt.Printf(" Retries: %d\n", p.State.OutgoingChannel.Retries)
fmt.Printf(" Out: %d\n", p.State.OutgoingChannel.Out)
}
}

elems := make([]string, 0, 3)
if as := p.Conf.AllowOwnAsn; as > 0 {
elems = append(elems, fmt.Sprintf("Allow Own AS: %d", as))
Expand Down
219 changes: 219 additions & 0 deletions internal/pkg/channels/buffer_channel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package channels

import (
"sync/atomic"

api "github.com/osrg/gobgp/v3/api"
"github.com/osrg/gobgp/v3/internal/pkg/table"
"github.com/osrg/gobgp/v3/pkg/packet/bgp"
)

const (
// default input channel size.
// specifies a buffer size for input channel.
defaultInSize = 64
)

type BufferMessageInterface interface {
PathList() []*table.Path
SetPathList(pathList []*table.Path)
}

type pathKey struct {
rf bgp.RouteFamily
nlri string
pathIdentifier uint32
}

type BufferChannel struct {
input chan any
output chan any

in atomic.Uint64
notifications atomic.Uint64
collected atomic.Uint64
rewritten atomic.Uint64
retries atomic.Uint64
out atomic.Uint64
}

type bufferChannel struct {
parent *BufferChannel

pathIdxs map[pathKey]int
last BufferMessageInterface
}

func NewBufferChannel(inSize int) *BufferChannel {
bc := &BufferChannel{
output: make(chan any),
}

if inSize == 0 {
// if inSize not set, use default value
inSize = defaultInSize
}
bc.input = make(chan any, inSize)

ibc := &bufferChannel{
parent: bc,
pathIdxs: make(map[pathKey]int),
}

go ibc.serve()
return bc
}

func (bc *BufferChannel) In() chan<- any {
return bc.input
}

func (bc *BufferChannel) Out() <-chan any {
return bc.output
}

func (bc *BufferChannel) Stats() *api.ChannelState {
return &api.ChannelState{
In: bc.in.Load(),
Notifications: bc.notifications.Load(),
Collected: bc.collected.Load(),
Rewritten: bc.rewritten.Load(),
Retries: bc.retries.Load(),
Out: bc.out.Load(),
}
}

func (bc *BufferChannel) Clean() {
bc.Close()
// drain all remaining items
for range bc.Out() {
}
}

func (bc *BufferChannel) Close() {
close(bc.input)
}

func (bc *bufferChannel) serve() {
for {
var out chan any
if bc.last != nil {
out = bc.parent.output
}

select {
case elem, open := <-bc.parent.input:
if !open {
close(bc.parent.output)
return
}

bc.onInput(elem)
case out <- bc.last:
bc.parent.out.Add(1)

clear(bc.pathIdxs)
bc.last = nil
}
}
}

func (bc *bufferChannel) onInput(anyElem any) {
bc.parent.in.Add(1)

elem, ok := anyElem.(BufferMessageInterface)
if !ok || len(elem.PathList()) == 0 {
// pass not BufferChannel's element or notification to output with blocking channel
bc.parent.notifications.Add(1)

if bc.last != nil {
bc.parent.output <- bc.last
bc.parent.out.Add(1)

clear(bc.pathIdxs)
bc.last = nil
}

bc.parent.output <- anyElem
bc.parent.out.Add(1)
return
}

if bc.last != nil {
bc.collect(elem)
return
}

select {
case bc.parent.output <- elem:
// done
bc.parent.out.Add(1)
default:
// try output later
bc.parent.retries.Add(1)

bc.collect(elem)
}
}

func (bc *bufferChannel) collect(elem BufferMessageInterface) {
bc.parent.collected.Add(1)

pathList := elem.PathList()

if bc.last == nil {
// first

for idx, path := range pathList {
if path == nil || path.IsEOR() {
continue
}

key := pathKey{
rf: path.GetRouteFamily(),
pathIdentifier: path.GetNlri().PathIdentifier(),
nlri: table.TableKey(path.GetNlri()),
}

bc.pathIdxs[key] = idx
}
} else {
// merge

nextPathsList := bc.last.PathList()

for _, path := range pathList {
if path == nil {
continue
}

if path.IsEOR() {
nextPathsList = append(nextPathsList, path)
continue
}

key := pathKey{
rf: path.GetRouteFamily(),
pathIdentifier: path.GetNlri().PathIdentifier(),
nlri: table.TableKey(path.GetNlri()),
}

idx, ok := bc.pathIdxs[key]
if !ok {
// new path

bc.pathIdxs[key] = len(nextPathsList)
nextPathsList = append(nextPathsList, path)
} else {
// rewrite path
bc.parent.rewritten.Add(1)

nextPathsList[idx] = path
}
}

elem.SetPathList(nextPathsList)
}

bc.last = elem
}
Loading