github.com/elfadel/cilium@v1.6.12/pkg/datapath/prefilter/prefilter.go (about)

     1  // Copyright 2017-2018 Authors of Cilium
     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 implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package prefilter
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"net"
    21  	"os/exec"
    22  	"path"
    23  	"syscall"
    24  
    25  	"github.com/cilium/cilium/pkg/bpf"
    26  	"github.com/cilium/cilium/pkg/lock"
    27  	"github.com/cilium/cilium/pkg/logging"
    28  	"github.com/cilium/cilium/pkg/logging/logfields"
    29  	"github.com/cilium/cilium/pkg/maps/cidrmap"
    30  	"github.com/cilium/cilium/pkg/probe"
    31  )
    32  
    33  type preFilterMapType int
    34  
    35  const (
    36  	prefixesV4Dyn preFilterMapType = iota
    37  	prefixesV4Fix
    38  	prefixesV6Dyn
    39  	prefixesV6Fix
    40  	mapCount
    41  )
    42  
    43  const (
    44  	// Arbitrary chosen for now. We don't preallocate elements,
    45  	// so we could bump the limit if needed later on.
    46  	maxLKeys = 1024 * 64
    47  	maxHKeys = 1024 * 1024 * 20
    48  )
    49  
    50  type preFilterMaps [mapCount]*cidrmap.CIDRMap
    51  
    52  type preFilterConfig struct {
    53  	dyn4Enabled bool
    54  	dyn6Enabled bool
    55  	fix4Enabled bool
    56  	fix6Enabled bool
    57  }
    58  
    59  // PreFilter holds global info on related CIDR maps participating in prefilter
    60  type PreFilter struct {
    61  	maps     preFilterMaps
    62  	config   preFilterConfig
    63  	revision int64
    64  	mutex    lock.RWMutex
    65  }
    66  
    67  var (
    68  	log = logging.DefaultLogger.WithField(logfields.LogSubsys, "prefilter")
    69  )
    70  
    71  // WriteConfig dumps the configuration for the corresponding header file
    72  func (p *PreFilter) WriteConfig(fw io.Writer) {
    73  	p.mutex.RLock()
    74  	defer p.mutex.RUnlock()
    75  
    76  	fmt.Fprintf(fw, "#define CIDR4_HMAP_ELEMS %d\n", maxHKeys)
    77  	fmt.Fprintf(fw, "#define CIDR4_LMAP_ELEMS %d\n", maxLKeys)
    78  
    79  	fmt.Fprintf(fw, "#define CIDR4_HMAP_NAME %s\n", path.Base(p.maps[prefixesV4Fix].String()))
    80  	fmt.Fprintf(fw, "#define CIDR4_LMAP_NAME %s\n", path.Base(p.maps[prefixesV4Dyn].String()))
    81  	fmt.Fprintf(fw, "#define CIDR6_HMAP_NAME %s\n", path.Base(p.maps[prefixesV6Fix].String()))
    82  	fmt.Fprintf(fw, "#define CIDR6_LMAP_NAME %s\n", path.Base(p.maps[prefixesV6Dyn].String()))
    83  
    84  	if p.config.fix4Enabled {
    85  		fmt.Fprintf(fw, "#define CIDR4_FILTER\n")
    86  		if p.config.dyn4Enabled {
    87  			fmt.Fprintf(fw, "#define CIDR4_LPM_PREFILTER\n")
    88  		}
    89  	}
    90  	if p.config.fix6Enabled {
    91  		fmt.Fprintf(fw, "#define CIDR6_FILTER\n")
    92  		if p.config.dyn6Enabled {
    93  			fmt.Fprintf(fw, "#define CIDR6_LPM_PREFILTER\n")
    94  		}
    95  	}
    96  }
    97  
    98  func (p *PreFilter) dumpOneMap(which preFilterMapType, to []string) []string {
    99  	if p.maps[which] == nil {
   100  		return to
   101  	}
   102  	return p.maps[which].CIDRDump(to)
   103  }
   104  
   105  // Dump dumps revision and CIDRs as string slice of all participating maps
   106  func (p *PreFilter) Dump(to []string) ([]string, int64) {
   107  	p.mutex.RLock()
   108  	defer p.mutex.RUnlock()
   109  	for i := prefixesV4Dyn; i < mapCount; i++ {
   110  		to = p.dumpOneMap(i, to)
   111  	}
   112  	return to, p.revision
   113  }
   114  
   115  func (p *PreFilter) selectMap(ones, bits int) preFilterMapType {
   116  	if bits == net.IPv4len*8 {
   117  		if ones == bits {
   118  			return prefixesV4Fix
   119  		}
   120  		return prefixesV4Dyn
   121  	} else if bits == net.IPv6len*8 {
   122  		if ones == bits {
   123  			return prefixesV6Fix
   124  		}
   125  		return prefixesV6Dyn
   126  	} else {
   127  		return mapCount
   128  	}
   129  }
   130  
   131  // Insert inserts slice of CIDRs (doh!) for the latest revision
   132  func (p *PreFilter) Insert(revision int64, cidrs []net.IPNet) error {
   133  	var undoQueue []net.IPNet
   134  	var ret error
   135  
   136  	p.mutex.Lock()
   137  	defer p.mutex.Unlock()
   138  	if revision != 0 && p.revision != revision {
   139  		return fmt.Errorf("Latest revision is %d not %d", p.revision, revision)
   140  	}
   141  	for _, cidr := range cidrs {
   142  		ones, bits := cidr.Mask.Size()
   143  		which := p.selectMap(ones, bits)
   144  		if which == mapCount || p.maps[which] == nil {
   145  			ret = fmt.Errorf("No map enabled for CIDR string %s", cidr.String())
   146  			break
   147  		}
   148  		err := p.maps[which].InsertCIDR(cidr)
   149  		if err != nil {
   150  			ret = fmt.Errorf("Error inserting CIDR string %s: %s", cidr.String(), err)
   151  			break
   152  		} else {
   153  			undoQueue = append(undoQueue, cidr)
   154  		}
   155  	}
   156  	if ret == nil {
   157  		p.revision++
   158  		return ret
   159  	}
   160  	for _, cidr := range undoQueue {
   161  		ones, bits := cidr.Mask.Size()
   162  		which := p.selectMap(ones, bits)
   163  		p.maps[which].DeleteCIDR(cidr)
   164  	}
   165  	return ret
   166  }
   167  
   168  // Delete deletes slice of CIDRs (doh!) for the latest revision
   169  func (p *PreFilter) Delete(revision int64, cidrs []net.IPNet) error {
   170  	var undoQueue []net.IPNet
   171  	var ret error
   172  
   173  	p.mutex.Lock()
   174  	defer p.mutex.Unlock()
   175  	if revision != 0 && p.revision != revision {
   176  		return fmt.Errorf("Latest revision is %d not %d", p.revision, revision)
   177  	}
   178  	for _, cidr := range cidrs {
   179  		ones, bits := cidr.Mask.Size()
   180  		which := p.selectMap(ones, bits)
   181  		if which == mapCount || p.maps[which] == nil {
   182  			return fmt.Errorf("No map enabled for CIDR string %s", cidr.String())
   183  		}
   184  		// Lets check obvious cases first, so we don't need to painfully unroll
   185  		if p.maps[which].CIDRExists(cidr) == false {
   186  			return fmt.Errorf("No map entry for CIDR string %s", cidr.String())
   187  		}
   188  	}
   189  	for _, cidr := range cidrs {
   190  		ones, bits := cidr.Mask.Size()
   191  		which := p.selectMap(ones, bits)
   192  		err := p.maps[which].DeleteCIDR(cidr)
   193  		if err != nil {
   194  			ret = fmt.Errorf("Error deleting CIDR string %s: %s", cidr.String(), err)
   195  			break
   196  		} else {
   197  			undoQueue = append(undoQueue, cidr)
   198  		}
   199  	}
   200  	if ret == nil {
   201  		p.revision++
   202  		return ret
   203  	}
   204  	for _, cidr := range undoQueue {
   205  		ones, bits := cidr.Mask.Size()
   206  		which := p.selectMap(ones, bits)
   207  		p.maps[which].InsertCIDR(cidr)
   208  	}
   209  	return ret
   210  }
   211  
   212  func (p *PreFilter) initOneMap(which preFilterMapType) error {
   213  	var prefixdyn bool
   214  	var prefixlen int
   215  	var maxelems uint32
   216  	var path string
   217  	var err error
   218  	var skip bool
   219  
   220  	switch which {
   221  	case prefixesV4Dyn:
   222  		prefixlen = net.IPv4len * 8
   223  		prefixdyn = true
   224  		maxelems = maxLKeys
   225  		path = bpf.MapPath(cidrmap.MapName + "v4_dyn")
   226  		skip = p.config.dyn4Enabled == false
   227  	case prefixesV4Fix:
   228  		prefixlen = net.IPv4len * 8
   229  		prefixdyn = false
   230  		maxelems = maxHKeys
   231  		path = bpf.MapPath(cidrmap.MapName + "v4_fix")
   232  		skip = p.config.fix4Enabled == false
   233  	case prefixesV6Dyn:
   234  		prefixlen = net.IPv6len * 8
   235  		prefixdyn = true
   236  		maxelems = maxLKeys
   237  		path = bpf.MapPath(cidrmap.MapName + "v6_dyn")
   238  		skip = p.config.dyn6Enabled == false
   239  	case prefixesV6Fix:
   240  		prefixlen = net.IPv6len * 8
   241  		prefixdyn = false
   242  		maxelems = maxHKeys
   243  		path = bpf.MapPath(cidrmap.MapName + "v6_fix")
   244  		skip = p.config.fix4Enabled == false
   245  	}
   246  	if skip == false {
   247  		p.maps[which], _, err = cidrmap.OpenMapElems(path, prefixlen, prefixdyn, maxelems)
   248  		if err != nil {
   249  			return err
   250  		}
   251  	}
   252  	return nil
   253  }
   254  
   255  func (p *PreFilter) init() (*PreFilter, error) {
   256  	for i := prefixesV4Dyn; i < mapCount; i++ {
   257  		if err := p.initOneMap(i); err != nil {
   258  			return nil, err
   259  		}
   260  	}
   261  	return p, nil
   262  }
   263  
   264  // ProbePreFilter checks whether XDP mode is supported on given device
   265  func ProbePreFilter(device, mode string) error {
   266  	cmd := exec.Command("ip", "-force", "link", "set", "dev", device, mode, "off")
   267  	if err := cmd.Start(); err != nil {
   268  		return fmt.Errorf("Cannot run ip command: %v", err)
   269  	}
   270  	if err := cmd.Wait(); err != nil {
   271  		if exiterr, ok := err.(*exec.ExitError); ok {
   272  			if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
   273  				switch status.ExitStatus() {
   274  				case 2:
   275  					return fmt.Errorf("Mode %s not supported on device %s", mode, device)
   276  				default:
   277  					return fmt.Errorf("Prefilter not supported on OS")
   278  				}
   279  			}
   280  		} else {
   281  			return fmt.Errorf("Cannot wait for ip command: %v", err)
   282  		}
   283  	}
   284  	return nil
   285  }
   286  
   287  // NewPreFilter returns prefilter handle
   288  func NewPreFilter() (*PreFilter, error) {
   289  	haveLPM := probe.HaveFullLPM()
   290  	if !haveLPM {
   291  		log.Warning("Kernel too old for full LPM map support. Needs kernel 4.16 or higher. Only enabling /32 and /128 prefixes for prefilter.")
   292  	}
   293  	c := preFilterConfig{
   294  		dyn4Enabled: haveLPM,
   295  		dyn6Enabled: haveLPM,
   296  		fix4Enabled: true,
   297  		fix6Enabled: true,
   298  	}
   299  	p := &PreFilter{
   300  		revision: 1,
   301  		config:   c,
   302  	}
   303  	// Only needed here given we access pinned maps.
   304  	p.mutex.Lock()
   305  	defer p.mutex.Unlock()
   306  	return p.init()
   307  }