gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/tools/xdp/cmd/redirect_host.go (about) 1 // Copyright 2023 The gVisor Authors. 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 cmd 16 17 import ( 18 "bytes" 19 "context" 20 _ "embed" 21 "errors" 22 "fmt" 23 "log" 24 "net" 25 "os" 26 "path/filepath" 27 28 "github.com/cilium/ebpf" 29 "github.com/cilium/ebpf/link" 30 "github.com/google/subcommands" 31 "gvisor.dev/gvisor/runsc/flag" 32 ) 33 34 // bpffsDirName is the path at which BPFFS is expected to be mounted. 35 const bpffsDirPath = "/sys/fs/bpf/" 36 37 // RedirectPinDir returns the directory to which eBPF objects will be pinned 38 // when xdp_loader is run against iface. 39 func RedirectPinDir(iface string) string { 40 return filepath.Join(bpffsDirPath, iface) 41 } 42 43 // RedirectMapPath returns the path where the eBPF map will be pinned when 44 // xdp_loader is run against iface. 45 func RedirectMapPath(iface string) string { 46 return filepath.Join(RedirectPinDir(iface), "redirect_ip_map") 47 } 48 49 // RedirectProgramPath returns the path where the eBPF program will be pinned 50 // when xdp_loader is run against iface. 51 func RedirectProgramPath(iface string) string { 52 return filepath.Join(RedirectPinDir(iface), "redirect_program") 53 } 54 55 // RedirectLinkPath returns the path where the eBPF link will be pinned when 56 // xdp_loader is run against iface. 57 func RedirectLinkPath(iface string) string { 58 return filepath.Join(RedirectPinDir(iface), "redirect_link") 59 } 60 61 //go:embed bpf/redirect_host_ebpf.o 62 var redirectProgram []byte 63 64 // RedirectHostCommand is a subcommand for redirecting incoming packets based 65 // on a pinned eBPF map. It redirects all non-SSH traffic to a single AF_XDP 66 // socket. 67 type RedirectHostCommand struct { 68 device string 69 deviceIndex int 70 unpin bool 71 } 72 73 // Name implements subcommands.Command.Name. 74 func (*RedirectHostCommand) Name() string { 75 return "redirect" 76 } 77 78 // Synopsis implements subcommands.Command.Synopsis. 79 func (*RedirectHostCommand) Synopsis() string { 80 return "Redirect incoming packets to an AF_XDP socket. Pins eBPF objects in /sys/fs/bpf/<interface name>/." 81 } 82 83 // Usage implements subcommands.Command.Usage. 84 func (*RedirectHostCommand) Usage() string { 85 return "redirect {-device <device> | -device-idx <device index>} [--unpin]" 86 } 87 88 // SetFlags implements subcommands.Command.SetFlags. 89 func (rc *RedirectHostCommand) SetFlags(fs *flag.FlagSet) { 90 fs.StringVar(&rc.device, "device", "", "which device to attach to") 91 fs.IntVar(&rc.deviceIndex, "device-idx", 0, "which device to attach to") 92 fs.BoolVar(&rc.unpin, "unpin", false, "unpin the map and program instead of pinning new ones; useful to reset state") 93 } 94 95 // Execute implements subcommands.Command.Execute. 96 func (rc *RedirectHostCommand) Execute(context.Context, *flag.FlagSet, ...any) subcommands.ExitStatus { 97 if err := rc.execute(); err != nil { 98 fmt.Printf("%v\n", err) 99 return subcommands.ExitFailure 100 } 101 return subcommands.ExitSuccess 102 } 103 104 func (rc *RedirectHostCommand) execute() error { 105 iface, err := getIface(rc.device, rc.deviceIndex) 106 if err != nil { 107 return fmt.Errorf("%v", err) 108 } 109 110 return installProgramAndMap(installProgramAndMapOpts{ 111 program: redirectProgram, 112 iface: iface, 113 unpin: rc.unpin, 114 pinDir: RedirectPinDir(iface.Name), 115 mapPath: RedirectMapPath(iface.Name), 116 programPath: RedirectProgramPath(iface.Name), 117 linkPath: RedirectLinkPath(iface.Name), 118 }) 119 } 120 121 type installProgramAndMapOpts struct { 122 program []byte 123 iface *net.Interface 124 unpin bool 125 pinDir string 126 mapPath string 127 programPath string 128 linkPath string 129 } 130 131 func installProgramAndMap(opts installProgramAndMapOpts) error { 132 // User just wants to unpin things. 133 if opts.unpin { 134 return unpin(opts.mapPath, opts.programPath, opts.linkPath) 135 } 136 137 // Load into the kernel. 138 spec, err := ebpf.LoadCollectionSpecFromReader(bytes.NewReader(opts.program)) 139 if err != nil { 140 return fmt.Errorf("failed to load spec: %v", err) 141 } 142 143 var objects struct { 144 Program *ebpf.Program `ebpf:"xdp_prog"` 145 SockMap *ebpf.Map `ebpf:"sock_map"` 146 } 147 if err := spec.LoadAndAssign(&objects, nil); err != nil { 148 return fmt.Errorf("failed to load program: %v", err) 149 } 150 defer func() { 151 if err := objects.Program.Close(); err != nil { 152 log.Printf("failed to close program: %v", err) 153 } 154 if err := objects.SockMap.Close(); err != nil { 155 log.Printf("failed to close sock map: %v", err) 156 } 157 }() 158 159 attachedLink, cleanup, err := attach(objects.Program, opts.iface) 160 if err != nil { 161 return fmt.Errorf("failed to attach: %v", err) 162 } 163 defer cleanup() 164 165 // Create directory /sys/fs/bpf/<device name>/. 166 if err := os.Mkdir(opts.pinDir, 0700); err != nil && !os.IsExist(err) { 167 return fmt.Errorf("failed to create directory for pinning at %s: %v", opts.pinDir, err) 168 } 169 170 // Pin the map at /sys/fs/bpf/<device name>/ip_map. 171 if err := objects.SockMap.Pin(opts.mapPath); err != nil { 172 return fmt.Errorf("failed to pin map at %s", opts.mapPath) 173 } 174 log.Printf("Pinned map at %s", opts.mapPath) 175 176 // Pin the program at /sys/fs/bpf/<device name>/program. 177 if err := objects.Program.Pin(opts.programPath); err != nil { 178 return fmt.Errorf("failed to pin program at %s", opts.programPath) 179 } 180 log.Printf("Pinned program at %s", opts.programPath) 181 182 // Make everything persistent by pinning the link. Otherwise, the XDP 183 // program would detach when this process exits. 184 if err := attachedLink.Pin(opts.linkPath); err != nil { 185 return fmt.Errorf("failed to pin link at %s", opts.linkPath) 186 } 187 log.Printf("Pinned link at %s", opts.linkPath) 188 189 return nil 190 } 191 192 func unpin(mapPath, programPath, linkPath string) error { 193 // Try to unpin both the map and program even if only one is found. 194 mapErr := func() error { 195 pinnedMap, err := ebpf.LoadPinnedMap(mapPath, nil) 196 if err != nil { 197 return fmt.Errorf("failed to load pinned map at %s for unpinning: %v", mapPath, err) 198 } 199 if err := pinnedMap.Unpin(); err != nil { 200 return fmt.Errorf("failed to unpin map %s: %v", mapPath, err) 201 } 202 log.Printf("Unpinned map at %s", mapPath) 203 return nil 204 }() 205 programErr := func() error { 206 pinnedProgram, err := ebpf.LoadPinnedProgram(programPath, nil) 207 if err != nil { 208 return fmt.Errorf("failed to load pinned program at %s for unpinning: %v", programPath, err) 209 } 210 if err := pinnedProgram.Unpin(); err != nil { 211 return fmt.Errorf("failed to unpin program %s: %v", programPath, err) 212 } 213 log.Printf("Unpinned program at %s", programPath) 214 return nil 215 }() 216 linkErr := func() error { 217 pinnedLink, err := link.LoadPinnedLink(linkPath, nil) 218 if err != nil { 219 return fmt.Errorf("failed to load pinned link at %s for unpinning: %v", linkPath, err) 220 } 221 if err := pinnedLink.Unpin(); err != nil { 222 return fmt.Errorf("failed to unpin link %s: %v", linkPath, err) 223 } 224 log.Printf("Unpinned link at %s", linkPath) 225 return nil 226 }() 227 return errors.Join(mapErr, programErr, linkErr) 228 }