github.com/cilium/cilium@v1.16.2/pkg/recorder/recorder.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package recorder
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"net"
    10  	"sort"
    11  	"strconv"
    12  
    13  	"github.com/sirupsen/logrus"
    14  
    15  	"github.com/cilium/cilium/api/v1/models"
    16  	"github.com/cilium/cilium/pkg/bpf"
    17  	"github.com/cilium/cilium/pkg/byteorder"
    18  	"github.com/cilium/cilium/pkg/cidr"
    19  	"github.com/cilium/cilium/pkg/common"
    20  	"github.com/cilium/cilium/pkg/datapath/types"
    21  	"github.com/cilium/cilium/pkg/lock"
    22  	"github.com/cilium/cilium/pkg/maps/recorder"
    23  	"github.com/cilium/cilium/pkg/option"
    24  	"github.com/cilium/cilium/pkg/u8proto"
    25  )
    26  
    27  type ID uint16
    28  
    29  // +k8s:deepcopy-gen=true
    30  type RecorderTuple struct {
    31  	SrcPrefix cidr.CIDR
    32  	SrcPort   uint16
    33  	DstPrefix cidr.CIDR
    34  	DstPort   uint16
    35  	Proto     u8proto.U8proto
    36  }
    37  
    38  // +k8s:deepcopy-gen=true
    39  type RecorderMask struct {
    40  	srcMask net.IPMask
    41  	srcPort uint16
    42  	dstMask net.IPMask
    43  	dstPort uint16
    44  	proto   u8proto.U8proto
    45  }
    46  
    47  // +k8s:deepcopy-gen=true
    48  type RecInfo struct {
    49  	ID      ID
    50  	CapLen  uint16
    51  	Filters []RecorderTuple
    52  }
    53  
    54  // +k8s:deepcopy-gen=true
    55  type RecMask struct {
    56  	users int
    57  	prio  int
    58  	mask  RecorderMask
    59  }
    60  
    61  type recQueue struct {
    62  	ri  *RecInfo
    63  	add []*RecorderTuple
    64  	del []*RecorderTuple
    65  }
    66  
    67  type Recorder struct {
    68  	lock.RWMutex
    69  	logger  logrus.FieldLogger
    70  	recByID map[ID]*RecInfo
    71  	recMask map[string]*RecMask
    72  	queue   recQueue
    73  	ctx     context.Context
    74  	loader  types.Loader
    75  }
    76  
    77  func newRecorder(ctx context.Context, logger logrus.FieldLogger, loader types.Loader) *Recorder {
    78  	return &Recorder{
    79  		recByID: map[ID]*RecInfo{},
    80  		recMask: map[string]*RecMask{},
    81  		queue: recQueue{
    82  			add: []*RecorderTuple{},
    83  			del: []*RecorderTuple{},
    84  		},
    85  		ctx:    ctx,
    86  		logger: logger,
    87  		loader: loader,
    88  	}
    89  }
    90  
    91  // enableRecorder initializes the main recorder infrastructure once upon agent
    92  // bootstrap for tracking tuple insertions and masks that need to be pushed
    93  // down into the BPF datapath. Given we currently do not support restore
    94  // functionality, it also flushes prior existing recorder objects from the
    95  // BPF maps.
    96  func (r *Recorder) enableRecorder() error {
    97  	maps := []*bpf.Map{}
    98  	if option.Config.EnableIPv4 {
    99  		t := &recorder.CaptureWcard4{}
   100  		maps = append(maps, t.Map())
   101  	}
   102  	if option.Config.EnableIPv6 {
   103  		t := &recorder.CaptureWcard6{}
   104  		maps = append(maps, t.Map())
   105  	}
   106  	for _, m := range maps {
   107  		if err := m.OpenOrCreate(); err != nil {
   108  			return err
   109  		}
   110  		if err := m.DeleteAll(); err != nil {
   111  			return err
   112  		}
   113  	}
   114  
   115  	return nil
   116  }
   117  
   118  func convertTupleToMask(t RecorderTuple) RecorderMask {
   119  	m := RecorderMask{
   120  		srcMask: make([]byte, len(t.SrcPrefix.Mask)),
   121  		dstMask: make([]byte, len(t.DstPrefix.Mask)),
   122  	}
   123  	if t.SrcPort != 0 {
   124  		m.srcPort = 0xffff
   125  	}
   126  	if t.DstPort != 0 {
   127  		m.dstPort = 0xffff
   128  	}
   129  	if t.Proto != 0 {
   130  		m.proto = 0xff
   131  	}
   132  	copy(m.srcMask, t.SrcPrefix.Mask)
   133  	copy(m.dstMask, t.DstPrefix.Mask)
   134  	return m
   135  }
   136  
   137  func countMaskOnes(m RecorderMask) int {
   138  	ones := 0
   139  	onesSrc, _ := m.srcMask.Size()
   140  	onesDst, _ := m.dstMask.Size()
   141  	ones += onesSrc + onesDst
   142  	if m.srcPort == 0xffff {
   143  		ones += 16
   144  	}
   145  	if m.dstPort == 0xffff {
   146  		ones += 16
   147  	}
   148  	if m.proto == 0xff {
   149  		ones += 8
   150  	}
   151  	return ones
   152  }
   153  
   154  func hashMask(x *RecorderMask) string {
   155  	return fmt.Sprintf("%s/%s/%x/%x/%x",
   156  		x.srcMask.String(), x.dstMask.String(),
   157  		int(x.srcPort), int(x.dstPort), int(x.proto))
   158  }
   159  
   160  func hashTuple(x *RecorderTuple) string {
   161  	return fmt.Sprintf("%s/%s/%x/%x/%x",
   162  		x.SrcPrefix.String(), x.DstPrefix.String(),
   163  		int(x.SrcPort), int(x.DstPort), int(x.Proto))
   164  }
   165  
   166  func (t *RecorderTuple) isIPv4() bool {
   167  	_, bits := t.SrcPrefix.Mask.Size()
   168  	return bits == 32
   169  }
   170  
   171  func (m *RecorderMask) isIPv4() bool {
   172  	_, bits := m.srcMask.Size()
   173  	return bits == 32
   174  }
   175  
   176  func (m *RecorderMask) genMacroSpec() string {
   177  	onesSrc, _ := m.srcMask.Size()
   178  	onesDst, _ := m.dstMask.Size()
   179  
   180  	spec := "{"
   181  	if m.isIPv4() {
   182  		spec += fmt.Sprintf(".daddr=__constant_htonl(0x%s),", m.dstMask.String())
   183  		spec += fmt.Sprintf(".saddr=__constant_htonl(0x%s),", m.srcMask.String())
   184  	} else {
   185  		spec += fmt.Sprintf(".daddr={.addr={%s}},", common.GoArray2C(m.dstMask))
   186  		spec += fmt.Sprintf(".saddr={.addr={%s}},", common.GoArray2C(m.srcMask))
   187  	}
   188  	spec += fmt.Sprintf(".dmask=%d,", onesDst)
   189  	spec += fmt.Sprintf(".smask=%d,", onesSrc)
   190  	spec += fmt.Sprintf(".dport=%#x,", m.dstPort)
   191  	spec += fmt.Sprintf(".sport=%#x,", m.srcPort)
   192  	spec += fmt.Sprintf(".nexthdr=%#x,", uint8(m.proto))
   193  	spec += "},"
   194  	return spec
   195  }
   196  
   197  func (r *Recorder) orderedMaskSets() ([]*RecMask, []*RecMask) {
   198  	ordered4 := []*RecMask{}
   199  	ordered6 := []*RecMask{}
   200  	for _, m := range r.recMask {
   201  		if m.mask.isIPv4() {
   202  			ordered4 = append(ordered4, m)
   203  		} else {
   204  			ordered6 = append(ordered6, m)
   205  		}
   206  	}
   207  	sort.Slice(ordered4, func(i, j int) bool {
   208  		return ordered4[i].prio > ordered4[j].prio
   209  	})
   210  	sort.Slice(ordered6, func(i, j int) bool {
   211  		return ordered6[i].prio > ordered6[j].prio
   212  	})
   213  	return ordered4, ordered6
   214  }
   215  
   216  func (r *Recorder) triggerDatapathRegenerate() error {
   217  	var masks4, masks6 string
   218  	extraCArgs := []string{}
   219  	if len(r.recMask) == 0 {
   220  		extraCArgs = append(extraCArgs, "-Dcapture_enabled=0")
   221  	} else {
   222  		extraCArgs = append(extraCArgs, "-Dcapture_enabled=1")
   223  		ordered4, ordered6 := r.orderedMaskSets()
   224  		if option.Config.EnableIPv4 {
   225  			masks4 = "-DPREFIX_MASKS4="
   226  			if len(ordered4) == 0 {
   227  				masks4 += " "
   228  			} else {
   229  				for _, m := range ordered4 {
   230  					masks4 += m.mask.genMacroSpec()
   231  				}
   232  			}
   233  			extraCArgs = append(extraCArgs, masks4)
   234  		}
   235  		if option.Config.EnableIPv6 {
   236  			masks6 = "-DPREFIX_MASKS6="
   237  			if len(ordered6) == 0 {
   238  				masks6 += " "
   239  			} else {
   240  				for _, m := range ordered6 {
   241  					masks6 += m.mask.genMacroSpec()
   242  				}
   243  			}
   244  			extraCArgs = append(extraCArgs, masks6)
   245  		}
   246  	}
   247  	err := r.loader.ReinitializeXDP(r.ctx, extraCArgs)
   248  	if err != nil {
   249  		r.logger.WithError(err).Warnf("Failed to regenerate datapath with masks: %s / %s",
   250  			masks4, masks6)
   251  	}
   252  	return err
   253  }
   254  
   255  func recorderTupleToMapTuple4(ri *RecInfo, t *RecorderTuple) (*recorder.CaptureWcard4, *recorder.CaptureRule4) {
   256  	onesSrc, _ := t.SrcPrefix.Mask.Size()
   257  	onesDst, _ := t.DstPrefix.Mask.Size()
   258  
   259  	k := &recorder.CaptureWcard4{
   260  		NextHdr:  uint8(t.Proto),
   261  		DestMask: uint8(onesDst),
   262  		SrcMask:  uint8(onesSrc),
   263  	}
   264  	k.DestPort = byteorder.HostToNetwork16(t.DstPort)
   265  	k.SrcPort = byteorder.HostToNetwork16(t.SrcPort)
   266  	copy(k.DestAddr[:], t.DstPrefix.IP.To4()[:])
   267  	copy(k.SrcAddr[:], t.SrcPrefix.IP.To4()[:])
   268  	v := &recorder.CaptureRule4{
   269  		RuleId: uint16(ri.ID),
   270  		CapLen: uint32(ri.CapLen),
   271  	}
   272  	return k, v
   273  }
   274  
   275  func recorderTupleToMapTuple6(ri *RecInfo, t *RecorderTuple) (*recorder.CaptureWcard6, *recorder.CaptureRule6) {
   276  	onesSrc, _ := t.SrcPrefix.Mask.Size()
   277  	onesDst, _ := t.DstPrefix.Mask.Size()
   278  
   279  	k := &recorder.CaptureWcard6{
   280  		NextHdr:  uint8(t.Proto),
   281  		DestMask: uint8(onesDst),
   282  		SrcMask:  uint8(onesSrc),
   283  	}
   284  	k.DestPort = byteorder.HostToNetwork16(t.DstPort)
   285  	k.SrcPort = byteorder.HostToNetwork16(t.SrcPort)
   286  	copy(k.DestAddr[:], t.DstPrefix.IP.To16()[:])
   287  	copy(k.SrcAddr[:], t.SrcPrefix.IP.To16()[:])
   288  	v := &recorder.CaptureRule6{
   289  		RuleId: uint16(ri.ID),
   290  		CapLen: uint32(ri.CapLen),
   291  	}
   292  	return k, v
   293  }
   294  
   295  func recorderTupleToMapTuple(ri *RecInfo, t *RecorderTuple) (recorder.RecorderKey, recorder.RecorderEntry) {
   296  	var k recorder.RecorderKey
   297  	var v recorder.RecorderEntry
   298  	if t.isIPv4() {
   299  		k, v = recorderTupleToMapTuple4(ri, t)
   300  	} else {
   301  		k, v = recorderTupleToMapTuple6(ri, t)
   302  	}
   303  	return k, v
   304  }
   305  
   306  func (r *Recorder) triggerMapUpsert(ri *RecInfo, t *RecorderTuple) error {
   307  	k, v := recorderTupleToMapTuple(ri, t)
   308  	return k.Map().Update(k, v)
   309  }
   310  
   311  func (r *Recorder) triggerMapDelete(ri *RecInfo, t *RecorderTuple) error {
   312  	k, _ := recorderTupleToMapTuple(ri, t)
   313  	return k.Map().Delete(k)
   314  }
   315  
   316  func (r *Recorder) applyDatapath(regen bool) error {
   317  	for _, e := range r.queue.add {
   318  		r.triggerMapUpsert(r.queue.ri, e)
   319  	}
   320  	r.queue.add = []*RecorderTuple{}
   321  	for _, e := range r.queue.del {
   322  		r.triggerMapDelete(r.queue.ri, e)
   323  	}
   324  	r.queue.del = []*RecorderTuple{}
   325  	r.queue.ri = nil
   326  	if regen {
   327  		r.logger.Debugf("Recorder Masks: %v", r.recMask)
   328  		// If datapath masks did not change, then there is of course
   329  		// also no need to trigger a regeneration since map updates
   330  		// suffice (which is also much faster).
   331  		return r.triggerDatapathRegenerate()
   332  	}
   333  	return nil
   334  }
   335  
   336  func queuePurge(q []*RecorderTuple, i int) []*RecorderTuple {
   337  	q[i] = q[len(q)-1]
   338  	q[len(q)-1] = nil
   339  	return q[:len(q)-1]
   340  }
   341  
   342  func (r *Recorder) queueAddDatapathFilter(ri *RecInfo, i int) {
   343  	if r.queue.ri == nil {
   344  		r.queue.ri = ri
   345  	}
   346  	r.queue.add = append(r.queue.add, &ri.Filters[i])
   347  }
   348  
   349  func (r *Recorder) queueDelDatapathFilter(ri *RecInfo, i int) {
   350  	if r.queue.ri == nil {
   351  		r.queue.ri = ri
   352  	}
   353  	filter := &ri.Filters[i]
   354  	hash := hashTuple(filter)
   355  	// If the recorder updated an existing filter element which sits
   356  	// in both queues, then we do not need any change in the BPF data
   357  	// path, and can avoid temporary recorder disruption. Hence, add/del
   358  	// queues strictly only ever contain entries that need change.
   359  	for i, e := range r.queue.add {
   360  		if hashTuple(e) == hash {
   361  			if r.queue.ri.CapLen == ri.CapLen {
   362  				r.queue.add = queuePurge(r.queue.add, i)
   363  			}
   364  			return
   365  		}
   366  	}
   367  	r.queue.del = append(r.queue.del, filter)
   368  }
   369  
   370  func (r *Recorder) deleteRecInfoLocked(ri *RecInfo, withID bool) bool {
   371  	triggerRegen := false
   372  	for i, filter := range ri.Filters {
   373  		mask := convertTupleToMask(filter)
   374  		maskHash := hashMask(&mask)
   375  		if rm, found := r.recMask[maskHash]; found {
   376  			rm.users--
   377  			if rm.users == 0 {
   378  				delete(r.recMask, maskHash)
   379  				triggerRegen = true
   380  			}
   381  		}
   382  		r.queueDelDatapathFilter(ri, i)
   383  	}
   384  	if withID {
   385  		delete(r.recByID, ri.ID)
   386  	}
   387  	return triggerRegen
   388  }
   389  
   390  // DeleteRecorder will delete an existing recorder object based on its unique
   391  // identifier. If needed, it will also update datapath maps to purge the
   392  // recorder filters from the BPF maps as well as triggering a reinitialization
   393  // of the XDP datapath if the mask set has changed.
   394  func (r *Recorder) DeleteRecorder(id ID) (bool, error) {
   395  	r.Lock()
   396  	defer r.Unlock()
   397  	if recInfo, found := r.recByID[id]; found {
   398  		return true, r.applyDatapath(r.deleteRecInfoLocked(recInfo, true))
   399  	}
   400  	return false, nil
   401  }
   402  
   403  func (r *Recorder) createRecInfoLocked(ri *RecInfo, withID bool) bool {
   404  	if withID {
   405  		r.recByID[ri.ID] = ri
   406  	}
   407  	triggerRegen := false
   408  	for i, filter := range ri.Filters {
   409  		mask := convertTupleToMask(filter)
   410  		maskHash := hashMask(&mask)
   411  		if rm, found := r.recMask[maskHash]; found {
   412  			rm.users++
   413  		} else {
   414  			ones := countMaskOnes(mask)
   415  			rm := &RecMask{
   416  				users: 1,
   417  				mask:  mask,
   418  				prio:  ones,
   419  			}
   420  			r.recMask[maskHash] = rm
   421  			triggerRegen = true
   422  		}
   423  		r.queueAddDatapathFilter(ri, i)
   424  	}
   425  	return triggerRegen
   426  }
   427  
   428  func (r *Recorder) updateRecInfoLocked(riNew, riOld *RecInfo) error {
   429  	triggerRegen := false
   430  	if r.createRecInfoLocked(riNew, true) {
   431  		triggerRegen = true
   432  	}
   433  	if r.deleteRecInfoLocked(riOld, false) {
   434  		triggerRegen = true
   435  	}
   436  	return r.applyDatapath(triggerRegen)
   437  }
   438  
   439  // UpsertRecorder will create a new or update an existing recorder object
   440  // based on its unique identifier. If needed, it will also update datapath
   441  // maps to insert new or remove obsolete recorder filters from the BPF maps
   442  // as well as triggering a reinitialization of the XDP datapath if the mask
   443  // set has changed.
   444  func (r *Recorder) UpsertRecorder(recInfoNew *RecInfo) (bool, error) {
   445  	if !option.Config.EnableRecorder {
   446  		return false, fmt.Errorf("Ignoring recorder request due to --%s being disabled in agent",
   447  			option.EnableRecorder)
   448  	}
   449  	recInfoCpy := recInfoNew.DeepCopy()
   450  	r.Lock()
   451  	defer r.Unlock()
   452  	if recInfoCur, found := r.recByID[recInfoCpy.ID]; found {
   453  		return false, r.updateRecInfoLocked(recInfoCpy, recInfoCur)
   454  	} else {
   455  		return true, r.applyDatapath(r.createRecInfoLocked(recInfoCpy, true))
   456  	}
   457  }
   458  
   459  func (r *Recorder) retrieveRecorderLocked(id ID) (*RecInfo, error) {
   460  	if recInfo, found := r.recByID[id]; found {
   461  		return recInfo.DeepCopy(), nil
   462  	} else {
   463  		return nil, fmt.Errorf("Recorder id %d not found", int(id))
   464  	}
   465  }
   466  
   467  // RetrieveRecorder will return an existing recorder object based on its
   468  // unique identifier. The returned object is a deep copy of the original
   469  // one tracked by the recorder infrastructure, so it can be freely changed
   470  // without affecting the original recorder object.
   471  func (r *Recorder) RetrieveRecorder(id ID) (*RecInfo, error) {
   472  	r.RLock()
   473  	defer r.RUnlock()
   474  	return r.retrieveRecorderLocked(id)
   475  }
   476  
   477  // RetrieveRecorderSet will return a list of all existing recorder objects.
   478  func (r *Recorder) RetrieveRecorderSet() []*RecInfo {
   479  	recList := make([]*RecInfo, 0, len(r.recByID))
   480  	r.RLock()
   481  	defer r.RUnlock()
   482  	for id := range r.recByID {
   483  		rec, _ := r.retrieveRecorderLocked(id)
   484  		recList = append(recList, rec)
   485  	}
   486  	return recList
   487  }
   488  
   489  // RetrieveRecorderMaskSet will return a list of all existing recorder masks.
   490  func (r *Recorder) RetrieveRecorderMaskSet() []*RecMask {
   491  	recMaskList := make([]*RecMask, 0, len(r.recMask))
   492  	r.RLock()
   493  	defer r.RUnlock()
   494  	for _, mask := range r.recMask {
   495  		maskCpy := mask.DeepCopy()
   496  		recMaskList = append(recMaskList, maskCpy)
   497  	}
   498  	return recMaskList
   499  }
   500  
   501  func ModelToRecorder(mo *models.RecorderSpec) (*RecInfo, error) {
   502  	if mo.ID == nil {
   503  		return nil, fmt.Errorf("Recorder model ID must be defined")
   504  	}
   505  	ri := &RecInfo{
   506  		ID:      ID(*mo.ID),
   507  		CapLen:  uint16(mo.CaptureLength),
   508  		Filters: []RecorderTuple{},
   509  	}
   510  	for _, mf := range mo.Filters {
   511  		f := RecorderTuple{}
   512  		ipDst, prefix, err := net.ParseCIDR(mf.DstPrefix)
   513  		if err != nil {
   514  			return nil, err
   515  		}
   516  		f.DstPrefix = *cidr.NewCIDR(prefix)
   517  		ipSrc, prefix, err := net.ParseCIDR(mf.SrcPrefix)
   518  		if err != nil {
   519  			return nil, err
   520  		}
   521  		f.SrcPrefix = *cidr.NewCIDR(prefix)
   522  		if (ipDst.To4() == nil) != (ipSrc.To4() == nil) {
   523  			return nil, fmt.Errorf("Recorder source (%s) and destination (%s) prefix must be same protocol version",
   524  				f.SrcPrefix, f.DstPrefix)
   525  		}
   526  		if !option.Config.EnableIPv4 && ipDst.To4() != nil ||
   527  			!option.Config.EnableIPv6 && ipDst.To4() == nil {
   528  			return nil, fmt.Errorf("Recorder source (%s) and destination (%s) prefix not supported by agent config",
   529  				f.SrcPrefix, f.DstPrefix)
   530  		}
   531  		port, err := strconv.ParseUint(mf.DstPort, 10, 16)
   532  		if err != nil {
   533  			return nil, err
   534  		}
   535  		f.DstPort = uint16(port)
   536  		port, err = strconv.ParseUint(mf.SrcPort, 10, 16)
   537  		if err != nil {
   538  			return nil, err
   539  		}
   540  		f.SrcPort = uint16(port)
   541  		switch mf.Protocol {
   542  		case models.RecorderFilterProtocolTCP:
   543  			f.Proto = u8proto.TCP
   544  		case models.RecorderFilterProtocolUDP:
   545  			f.Proto = u8proto.UDP
   546  		case models.RecorderFilterProtocolSCTP:
   547  			f.Proto = u8proto.SCTP
   548  		case models.RecorderFilterProtocolANY:
   549  			f.Proto = u8proto.ANY
   550  		default:
   551  			return nil, fmt.Errorf("Recorder protocol %s not supported by backend",
   552  				mf.Protocol)
   553  		}
   554  		ri.Filters = append(ri.Filters, f)
   555  	}
   556  	return ri, nil
   557  }
   558  
   559  func RecorderToModel(ri *RecInfo) (*models.RecorderSpec, error) {
   560  	id := int64(ri.ID)
   561  	mo := &models.RecorderSpec{
   562  		ID:            &id,
   563  		Filters:       []*models.RecorderFilter{},
   564  		CaptureLength: int64(ri.CapLen),
   565  	}
   566  	for _, rf := range ri.Filters {
   567  		mf := &models.RecorderFilter{}
   568  		mf.DstPrefix = rf.DstPrefix.String()
   569  		mf.SrcPrefix = rf.SrcPrefix.String()
   570  		mf.DstPort = fmt.Sprintf("%d", int(rf.DstPort))
   571  		mf.SrcPort = fmt.Sprintf("%d", int(rf.SrcPort))
   572  		switch rf.Proto {
   573  		case u8proto.TCP:
   574  			mf.Protocol = models.RecorderFilterProtocolTCP
   575  		case u8proto.UDP:
   576  			mf.Protocol = models.RecorderFilterProtocolUDP
   577  		case u8proto.SCTP:
   578  			mf.Protocol = models.RecorderFilterProtocolSCTP
   579  		case u8proto.ANY:
   580  			mf.Protocol = models.RecorderFilterProtocolANY
   581  		default:
   582  			return nil, fmt.Errorf("Recorder protocol %d not supported by model",
   583  				int(rf.Proto))
   584  		}
   585  		mo.Filters = append(mo.Filters, mf)
   586  	}
   587  	return mo, nil
   588  }
   589  
   590  func RecorderMaskToModel(rm *RecMask) *models.RecorderMaskSpec {
   591  	mo := &models.RecorderMaskSpec{
   592  		Users:    int64(rm.users),
   593  		Priority: int64(rm.prio),
   594  	}
   595  	mo.DstPrefixMask = rm.mask.dstMask.String()
   596  	mo.SrcPrefixMask = rm.mask.srcMask.String()
   597  	mo.DstPortMask = fmt.Sprintf("%x", int(rm.mask.dstPort))
   598  	mo.SrcPortMask = fmt.Sprintf("%x", int(rm.mask.srcPort))
   599  	mo.ProtocolMask = fmt.Sprintf("%x", int(rm.mask.proto))
   600  	return mo
   601  }