github.com/osrg/gobgp@v2.0.0+incompatible/pkg/server/mrt.go (about)

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