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  }