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 }