github.com/osrg/gobgp/v3@v3.30.0/pkg/server/mrt.go (about)

     1  // Copyright (C) 2016-2021 Nippon Telegraph and Telephone Corporation.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    12  // implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package server
    17  
    18  import (
    19  	"bytes"
    20  	"fmt"
    21  	"os"
    22  	"time"
    23  
    24  	"github.com/osrg/gobgp/v3/internal/pkg/table"
    25  	"github.com/osrg/gobgp/v3/pkg/config/oc"
    26  	"github.com/osrg/gobgp/v3/pkg/log"
    27  	"github.com/osrg/gobgp/v3/pkg/packet/bgp"
    28  	"github.com/osrg/gobgp/v3/pkg/packet/mrt"
    29  )
    30  
    31  const (
    32  	minRotationInterval = 60
    33  	minDumpInterval     = 60
    34  )
    35  
    36  type mrtWriter struct {
    37  	dead             chan struct{}
    38  	s                *BgpServer
    39  	c                *oc.MrtConfig
    40  	file             *os.File
    41  	rotationInterval uint64
    42  	dumpInterval     uint64
    43  }
    44  
    45  func (m *mrtWriter) Stop() {
    46  	close(m.dead)
    47  }
    48  
    49  func (m *mrtWriter) loop() error {
    50  	ops := []watchOption{}
    51  	switch m.c.DumpType {
    52  	case oc.MRT_TYPE_UPDATES:
    53  		ops = append(ops, watchUpdate(false, "", ""))
    54  	case oc.MRT_TYPE_TABLE:
    55  		if len(m.c.TableName) > 0 {
    56  			ops = append(ops, watchTableName(m.c.TableName))
    57  		}
    58  	}
    59  	w := m.s.watch(ops...)
    60  	rotator := func() *time.Ticker {
    61  		if m.rotationInterval == 0 {
    62  			return &time.Ticker{}
    63  		}
    64  		return time.NewTicker(time.Second * time.Duration(m.rotationInterval))
    65  	}()
    66  	dump := func() *time.Ticker {
    67  		if m.dumpInterval == 0 {
    68  			return &time.Ticker{}
    69  		}
    70  		return time.NewTicker(time.Second * time.Duration(m.dumpInterval))
    71  	}()
    72  
    73  	defer func() {
    74  		if m.file != nil {
    75  			m.file.Close()
    76  		}
    77  		if m.rotationInterval != 0 {
    78  			rotator.Stop()
    79  		}
    80  		if m.dumpInterval != 0 {
    81  			dump.Stop()
    82  		}
    83  		w.Stop()
    84  	}()
    85  
    86  	for {
    87  		serialize := func(ev watchEvent) []*mrt.MRTMessage {
    88  			msg := make([]*mrt.MRTMessage, 0, 1)
    89  			switch e := ev.(type) {
    90  			case *watchEventUpdate:
    91  				if e.Init {
    92  					return nil
    93  				}
    94  				mp := mrt.NewBGP4MPMessage(e.PeerAS, e.LocalAS, 0, e.PeerAddress.String(), e.LocalAddress.String(), e.FourBytesAs, nil)
    95  				mp.BGPMessagePayload = e.Payload
    96  				isAddPath := e.Neighbor.IsAddPathReceiveEnabled(e.PathList[0].GetRouteFamily())
    97  				subtype := mrt.MESSAGE
    98  				switch {
    99  				case isAddPath && e.FourBytesAs:
   100  					subtype = mrt.MESSAGE_AS4_ADDPATH
   101  				case isAddPath:
   102  					subtype = mrt.MESSAGE_ADDPATH
   103  				case e.FourBytesAs:
   104  					subtype = mrt.MESSAGE_AS4
   105  				}
   106  				if bm, err := mrt.NewMRTMessage(uint32(e.Timestamp.Unix()), mrt.BGP4MP, subtype, mp); err != nil {
   107  					m.s.logger.Warn("Failed to create MRT BGP4MP message",
   108  						log.Fields{
   109  							"Topic":   "mrt",
   110  							"Data":    e,
   111  							"Error":   err,
   112  							"Subtype": subtype})
   113  				} else {
   114  					msg = append(msg, bm)
   115  				}
   116  			case *watchEventTable:
   117  				t := uint32(time.Now().Unix())
   118  
   119  				peers := make([]*mrt.Peer, 1, len(e.Neighbor)+1)
   120  				// Adding dummy Peer record for locally generated routes
   121  				peers[0] = mrt.NewPeer("0.0.0.0", "0.0.0.0", 0, true)
   122  				neighborMap := make(map[string]*oc.Neighbor)
   123  				for _, pconf := range e.Neighbor {
   124  					peers = append(peers, mrt.NewPeer(pconf.State.RemoteRouterId, pconf.State.NeighborAddress, pconf.Config.PeerAs, true))
   125  					neighborMap[pconf.State.NeighborAddress] = pconf
   126  				}
   127  
   128  				if bm, err := mrt.NewMRTMessage(t, mrt.TABLE_DUMPv2, mrt.PEER_INDEX_TABLE, mrt.NewPeerIndexTable(e.RouterID, "", peers)); err != nil {
   129  					m.s.logger.Warn("Failed to create MRT TABLE_DUMPv2 message",
   130  						log.Fields{
   131  							"Topic":   "mrt",
   132  							"Data":    e,
   133  							"Error":   err,
   134  							"Subtype": mrt.PEER_INDEX_TABLE})
   135  					break
   136  				} else {
   137  					msg = append(msg, bm)
   138  				}
   139  
   140  				idx := func(p *table.Path) uint16 {
   141  					for i, peer := range peers {
   142  						if peer.IpAddress.String() == p.GetSource().Address.String() {
   143  							return uint16(i)
   144  						}
   145  					}
   146  					return uint16(len(peers))
   147  				}
   148  
   149  				subtype := func(p *table.Path, isAddPath bool) mrt.MRTSubTypeTableDumpv2 {
   150  					t := mrt.RIB_GENERIC
   151  					switch p.GetRouteFamily() {
   152  					case bgp.RF_IPv4_UC:
   153  						t = mrt.RIB_IPV4_UNICAST
   154  					case bgp.RF_IPv4_MC:
   155  						t = mrt.RIB_IPV4_MULTICAST
   156  					case bgp.RF_IPv6_UC:
   157  						t = mrt.RIB_IPV6_UNICAST
   158  					case bgp.RF_IPv6_MC:
   159  						t = mrt.RIB_IPV6_MULTICAST
   160  					}
   161  					if isAddPath {
   162  						// Shift non-additional-path version to *_ADDPATH
   163  						t += 6
   164  					}
   165  					return t
   166  				}
   167  
   168  				seq := uint32(0)
   169  				appendTableDumpMsg := func(path *table.Path, entries []*mrt.RibEntry, isAddPath bool) {
   170  					st := subtype(path, isAddPath)
   171  					if bm, err := mrt.NewMRTMessage(t, mrt.TABLE_DUMPv2, st, mrt.NewRib(seq, path.GetNlri(), entries)); err != nil {
   172  						m.s.logger.Warn("Failed to create MRT TABLE_DUMPv2 message",
   173  							log.Fields{
   174  								"Topic":   "mrt",
   175  								"Data":    e,
   176  								"Error":   err,
   177  								"Subtype": st})
   178  					} else {
   179  						msg = append(msg, bm)
   180  						seq++
   181  					}
   182  				}
   183  				for _, pathList := range e.PathList {
   184  					entries := make([]*mrt.RibEntry, 0, len(pathList))
   185  					entriesAddPath := make([]*mrt.RibEntry, 0, len(pathList))
   186  					for _, path := range pathList {
   187  						isAddPath := false
   188  						if path.IsLocal() {
   189  							isAddPath = true
   190  						} else if neighbor, ok := neighborMap[path.GetSource().Address.String()]; ok {
   191  							isAddPath = neighbor.IsAddPathReceiveEnabled(path.GetRouteFamily())
   192  						}
   193  						if !isAddPath {
   194  							entries = append(entries, mrt.NewRibEntry(idx(path), uint32(path.GetTimestamp().Unix()), 0, path.GetPathAttrs(), false))
   195  						} else {
   196  							entriesAddPath = append(entriesAddPath, mrt.NewRibEntry(idx(path), uint32(path.GetTimestamp().Unix()), path.GetNlri().PathIdentifier(), path.GetPathAttrs(), true))
   197  						}
   198  					}
   199  					if len(entries) > 0 {
   200  						appendTableDumpMsg(pathList[0], entries, false)
   201  					}
   202  					if len(entriesAddPath) > 0 {
   203  						appendTableDumpMsg(pathList[0], entriesAddPath, true)
   204  					}
   205  				}
   206  			}
   207  			return msg
   208  		}
   209  
   210  		drain := func(ev watchEvent) {
   211  			events := make([]watchEvent, 0, 1+len(w.Event()))
   212  			if ev != nil {
   213  				events = append(events, ev)
   214  			}
   215  
   216  			for len(w.Event()) > 0 {
   217  				events = append(events, <-w.Event())
   218  			}
   219  
   220  			w := func(buf []byte) {
   221  				if _, err := m.file.Write(buf); err == nil {
   222  					m.file.Sync()
   223  				} else {
   224  					m.s.logger.Warn("Can't write to destination MRT file",
   225  						log.Fields{
   226  							"Topic": "mrt",
   227  							"Error": err})
   228  				}
   229  			}
   230  
   231  			var b bytes.Buffer
   232  			for _, e := range events {
   233  				for _, msg := range serialize(e) {
   234  					if buf, err := msg.Serialize(); err != nil {
   235  						m.s.logger.Warn("Failed to serialize event",
   236  							log.Fields{
   237  								"Topic": "mrt",
   238  								"Data":  e,
   239  								"Error": err})
   240  					} else {
   241  						b.Write(buf)
   242  						if b.Len() > 1*1000*1000 {
   243  							w(b.Bytes())
   244  							b.Reset()
   245  						}
   246  					}
   247  				}
   248  			}
   249  			if b.Len() > 0 {
   250  				w(b.Bytes())
   251  			}
   252  		}
   253  		rotate := func() {
   254  			m.file.Close()
   255  			file, err := mrtFileOpen(m.s.logger, m.c.FileName, m.rotationInterval)
   256  			if err == nil {
   257  				m.file = file
   258  			} else {
   259  				m.s.logger.Warn("can't rotate MRT file",
   260  					log.Fields{
   261  						"Topic": "mrt",
   262  						"Error": err})
   263  			}
   264  		}
   265  
   266  		select {
   267  		case <-m.dead:
   268  			drain(nil)
   269  			return nil
   270  		case e := <-w.Event():
   271  			drain(e)
   272  			if m.c.DumpType == oc.MRT_TYPE_TABLE && m.rotationInterval != 0 {
   273  				rotate()
   274  			}
   275  		case <-rotator.C:
   276  			if m.c.DumpType == oc.MRT_TYPE_UPDATES {
   277  				rotate()
   278  			} else {
   279  				w.Generate(watchEventTypeTable)
   280  			}
   281  		case <-dump.C:
   282  			w.Generate(watchEventTypeTable)
   283  		}
   284  	}
   285  }
   286  
   287  func mrtFileOpen(logger log.Logger, filename string, rInterval uint64) (*os.File, error) {
   288  	realname := filename
   289  	if rInterval != 0 {
   290  		realname = time.Now().Format(filename)
   291  	}
   292  	logger.Debug("Setting new MRT destination file",
   293  		log.Fields{
   294  			"Topic":            "mrt",
   295  			"Filename":         realname,
   296  			"RotationInterval": rInterval})
   297  
   298  	i := len(realname)
   299  	for i > 0 && os.IsPathSeparator(realname[i-1]) {
   300  		// skip trailing path separators
   301  		i--
   302  	}
   303  	j := i
   304  
   305  	for j > 0 && !os.IsPathSeparator(realname[j-1]) {
   306  		j--
   307  	}
   308  
   309  	if j > 0 {
   310  		if err := os.MkdirAll(realname[0:j-1], 0755); err != nil {
   311  			logger.Warn("can't create MRT destination directory",
   312  				log.Fields{
   313  					"Topic": "mrt",
   314  					"Error": err})
   315  			return nil, err
   316  		}
   317  	}
   318  
   319  	file, err := os.OpenFile(realname, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
   320  	if err != nil {
   321  		logger.Warn("can't create MRT destination file",
   322  			log.Fields{
   323  				"Topic": "mrt",
   324  				"Error": err})
   325  	}
   326  	return file, err
   327  }
   328  
   329  func newMrtWriter(s *BgpServer, c *oc.MrtConfig, rInterval, dInterval uint64) (*mrtWriter, error) {
   330  	file, err := mrtFileOpen(s.logger, c.FileName, rInterval)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  	m := mrtWriter{
   335  		s:                s,
   336  		c:                c,
   337  		file:             file,
   338  		rotationInterval: rInterval,
   339  		dumpInterval:     dInterval,
   340  	}
   341  	go m.loop()
   342  	return &m, nil
   343  }
   344  
   345  type mrtManager struct {
   346  	bgpServer *BgpServer
   347  	writer    map[string]*mrtWriter
   348  }
   349  
   350  func (m *mrtManager) enable(c *oc.MrtConfig) error {
   351  	if _, ok := m.writer[c.FileName]; ok {
   352  		return fmt.Errorf("%s already exists", c.FileName)
   353  	}
   354  
   355  	rInterval := c.RotationInterval
   356  	dInterval := c.DumpInterval
   357  
   358  	setRotationMin := func() {
   359  		if rInterval < minRotationInterval {
   360  			m.bgpServer.logger.Info("use minimum mrt rotation interval",
   361  				log.Fields{
   362  					"Topic":    "mrt",
   363  					"Interval": minRotationInterval})
   364  			rInterval = minRotationInterval
   365  		}
   366  	}
   367  
   368  	if c.DumpType == oc.MRT_TYPE_TABLE {
   369  		if rInterval == 0 {
   370  			if dInterval < minDumpInterval {
   371  				m.bgpServer.logger.Info("use minimum mrt dump interval",
   372  					log.Fields{
   373  						"Topic":    "mrt",
   374  						"Interval": minDumpInterval})
   375  				dInterval = minDumpInterval
   376  			}
   377  		} else if dInterval == 0 {
   378  			setRotationMin()
   379  		} else {
   380  			return fmt.Errorf("can't specify both intervals in the table dump type")
   381  		}
   382  	} else if c.DumpType == oc.MRT_TYPE_UPDATES {
   383  		// ignore the dump interval
   384  		dInterval = 0
   385  		if len(c.TableName) > 0 {
   386  			return fmt.Errorf("can't specify the table name with the update dump type")
   387  		}
   388  		setRotationMin()
   389  	}
   390  
   391  	w, err := newMrtWriter(m.bgpServer, c, rInterval, dInterval)
   392  	if err == nil {
   393  		m.writer[c.FileName] = w
   394  	}
   395  	return err
   396  }
   397  
   398  func (m *mrtManager) disable(c *oc.MrtConfig) error {
   399  	w, ok := m.writer[c.FileName]
   400  	if !ok {
   401  		return fmt.Errorf("%s doesn't exists", c.FileName)
   402  	}
   403  	w.Stop()
   404  	delete(m.writer, c.FileName)
   405  	return nil
   406  }
   407  
   408  func newMrtManager(s *BgpServer) *mrtManager {
   409  	return &mrtManager{
   410  		bgpServer: s,
   411  		writer:    make(map[string]*mrtWriter),
   412  	}
   413  }