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

     1  // Copyright 2018-2019 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 loader
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net"
    21  	"os"
    22  	"path"
    23  
    24  	"github.com/cilium/cilium/pkg/datapath"
    25  	"github.com/cilium/cilium/pkg/datapath/linux/route"
    26  	"github.com/cilium/cilium/pkg/defaults"
    27  	"github.com/cilium/cilium/pkg/elf"
    28  	"github.com/cilium/cilium/pkg/logging"
    29  	"github.com/cilium/cilium/pkg/logging/logfields"
    30  	"github.com/cilium/cilium/pkg/option"
    31  
    32  	"github.com/sirupsen/logrus"
    33  	"github.com/vishvananda/netlink"
    34  )
    35  
    36  var (
    37  	Subsystem = "datapath-loader"
    38  	log       = logging.DefaultLogger.WithField(logfields.LogSubsys, Subsystem)
    39  )
    40  
    41  const (
    42  	symbolFromEndpoint = "from-container"
    43  	symbolToEndpoint   = "to-container"
    44  
    45  	dirIngress = "ingress"
    46  	dirEgress  = "egress"
    47  )
    48  
    49  // endpoint provides access to endpoint information that is necessary to
    50  // compile and load the datapath.
    51  type endpoint interface {
    52  	datapath.EndpointConfiguration
    53  	InterfaceName() string
    54  	Logger(subsystem string) *logrus.Entry
    55  	StateDir() string
    56  	MapPath() string
    57  }
    58  
    59  func upsertEndpointRoute(ep endpoint, ip net.IPNet) error {
    60  	endpointRoute := route.Route{
    61  		Prefix: ip,
    62  		Device: ep.InterfaceName(),
    63  		Scope:  netlink.SCOPE_LINK,
    64  	}
    65  
    66  	_, err := route.Upsert(endpointRoute)
    67  	return err
    68  }
    69  
    70  func removeEndpointRoute(ep endpoint, ip net.IPNet) error {
    71  	return route.Delete(route.Route{
    72  		Prefix: ip,
    73  		Device: ep.InterfaceName(),
    74  		Scope:  netlink.SCOPE_LINK,
    75  	})
    76  }
    77  
    78  func reloadDatapath(ctx context.Context, ep endpoint, dirs *directoryInfo) error {
    79  	// Replace the current program
    80  	objPath := path.Join(dirs.Output, endpointObj)
    81  	if ep.HasIpvlanDataPath() {
    82  		if err := graftDatapath(ctx, ep.MapPath(), objPath, symbolFromEndpoint); err != nil {
    83  			scopedLog := ep.Logger(Subsystem).WithFields(logrus.Fields{
    84  				logfields.Path: objPath,
    85  			})
    86  			// Don't log an error here if the context was canceled or timed out;
    87  			// this log message should only represent failures with respect to
    88  			// loading the program.
    89  			if ctx.Err() == nil {
    90  				scopedLog.WithError(err).Warn("JoinEP: Failed to load program")
    91  			}
    92  			return err
    93  		}
    94  	} else {
    95  		if err := replaceDatapath(ctx, ep.InterfaceName(), objPath, symbolFromEndpoint, dirIngress); err != nil {
    96  			scopedLog := ep.Logger(Subsystem).WithFields(logrus.Fields{
    97  				logfields.Path: objPath,
    98  				logfields.Veth: ep.InterfaceName(),
    99  			})
   100  			// Don't log an error here if the context was canceled or timed out;
   101  			// this log message should only represent failures with respect to
   102  			// loading the program.
   103  			if ctx.Err() == nil {
   104  				scopedLog.WithError(err).Warn("JoinEP: Failed to load program")
   105  			}
   106  			return err
   107  		}
   108  
   109  		if ep.RequireEgressProg() {
   110  			if err := replaceDatapath(ctx, ep.InterfaceName(), objPath, symbolToEndpoint, dirEgress); err != nil {
   111  				scopedLog := ep.Logger(Subsystem).WithFields(logrus.Fields{
   112  					logfields.Path: objPath,
   113  					logfields.Veth: ep.InterfaceName(),
   114  				})
   115  				// Don't log an error here if the context was canceled or timed out;
   116  				// this log message should only represent failures with respect to
   117  				// loading the program.
   118  				if ctx.Err() == nil {
   119  					scopedLog.WithError(err).Warn("JoinEP: Failed to load program")
   120  				}
   121  				return err
   122  			}
   123  		}
   124  	}
   125  
   126  	if ep.RequireEndpointRoute() {
   127  		if ip := ep.IPv4Address(); ip.IsSet() {
   128  			upsertEndpointRoute(ep, *ip.IPNet(32))
   129  		}
   130  
   131  		if ip := ep.IPv6Address(); ip.IsSet() {
   132  			upsertEndpointRoute(ep, *ip.IPNet(128))
   133  		}
   134  	}
   135  
   136  	return nil
   137  }
   138  
   139  func compileAndLoad(ctx context.Context, ep endpoint, dirs *directoryInfo, stats *SpanStat) error {
   140  	debug := option.Config.BPFCompilationDebug
   141  	stats.bpfCompilation.Start()
   142  	err := compileDatapath(ctx, dirs, debug, ep.Logger(Subsystem))
   143  	stats.bpfCompilation.End(err == nil)
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	stats.bpfLoadProg.Start()
   149  	err = reloadDatapath(ctx, ep, dirs)
   150  	stats.bpfLoadProg.End(err == nil)
   151  	return err
   152  }
   153  
   154  // CompileAndLoad compiles the BPF datapath programs for the specified endpoint
   155  // and loads it onto the interface associated with the endpoint.
   156  //
   157  // Expects the caller to have created the directory at the path ep.StateDir().
   158  func CompileAndLoad(ctx context.Context, ep endpoint, stats *SpanStat) error {
   159  	if ep == nil {
   160  		log.Fatalf("LoadBPF() doesn't support non-endpoint load")
   161  	}
   162  
   163  	dirs := directoryInfo{
   164  		Library: option.Config.BpfDir,
   165  		Runtime: option.Config.StateDir,
   166  		State:   ep.StateDir(),
   167  		Output:  ep.StateDir(),
   168  	}
   169  	return compileAndLoad(ctx, ep, &dirs, stats)
   170  }
   171  
   172  // CompileOrLoad loads the BPF datapath programs for the specified endpoint.
   173  //
   174  // In contrast with CompileAndLoad(), it attempts to find a pre-compiled
   175  // template datapath object to use, to avoid a costly compile operation.
   176  // Only if there is no existing template that has the same configuration
   177  // parameters as the specified endpoint, this function will compile a new
   178  // template for this configuration.
   179  //
   180  // This function will block if the cache does not contain an entry for the
   181  // same EndpointConfiguration and multiple goroutines attempt to concurrently
   182  // CompileOrLoad with the same configuration parameters. When the first
   183  // goroutine completes compilation of the template, all other CompileOrLoad
   184  // invocations will be released.
   185  func CompileOrLoad(ctx context.Context, ep endpoint, stats *SpanStat) error {
   186  	templatePath, _, err := templateCache.fetchOrCompile(ctx, ep, stats)
   187  	if err != nil {
   188  		return err
   189  	}
   190  
   191  	template, err := elf.Open(templatePath)
   192  	if err != nil {
   193  		return err
   194  	}
   195  	defer template.Close()
   196  
   197  	symPath := path.Join(ep.StateDir(), defaults.TemplatePath)
   198  	if _, err := os.Stat(symPath); err == nil {
   199  		if err = os.RemoveAll(symPath); err != nil {
   200  			return &os.PathError{
   201  				Op:   "Failed to remove old symlink",
   202  				Path: symPath,
   203  				Err:  err,
   204  			}
   205  		}
   206  	} else if !os.IsNotExist(err) {
   207  		return &os.PathError{
   208  			Op:   "Failed to locate symlink",
   209  			Path: symPath,
   210  			Err:  err,
   211  		}
   212  	}
   213  	if err := os.Symlink(templatePath, symPath); err != nil {
   214  		return &os.PathError{
   215  			Op:   fmt.Sprintf("Failed to create symlink to %s", templatePath),
   216  			Path: symPath,
   217  			Err:  err,
   218  		}
   219  	}
   220  
   221  	stats.bpfWriteELF.Start()
   222  	dstPath := path.Join(ep.StateDir(), endpointObj)
   223  	opts, strings := ELFSubstitutions(ep)
   224  	if err = template.Write(dstPath, opts, strings); err != nil {
   225  		stats.bpfWriteELF.End(err == nil)
   226  		return err
   227  	}
   228  	stats.bpfWriteELF.End(err == nil)
   229  
   230  	return ReloadDatapath(ctx, ep, stats)
   231  }
   232  
   233  func ReloadDatapath(ctx context.Context, ep endpoint, stats *SpanStat) (err error) {
   234  	dirs := directoryInfo{
   235  		Library: option.Config.BpfDir,
   236  		Runtime: option.Config.StateDir,
   237  		State:   ep.StateDir(),
   238  		Output:  ep.StateDir(),
   239  	}
   240  	stats.bpfLoadProg.Start()
   241  	err = reloadDatapath(ctx, ep, &dirs)
   242  	stats.bpfLoadProg.End(err == nil)
   243  	return err
   244  }
   245  
   246  // Unload removes the datapath specific program aspects
   247  func Unload(ep endpoint) {
   248  	if ep.RequireEndpointRoute() {
   249  		if ip := ep.IPv4Address(); ip.IsSet() {
   250  			removeEndpointRoute(ep, *ip.IPNet(32))
   251  		}
   252  
   253  		if ip := ep.IPv6Address(); ip.IsSet() {
   254  			removeEndpointRoute(ep, *ip.IPNet(128))
   255  		}
   256  	}
   257  }