add runtime code
This commit is contained in:
parent
0a5f457059
commit
51aa9cb67f
27
client.go
Normal file
27
client.go
Normal file
@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import "github.com/containers/podman/v5/libpod/define"
|
||||
|
||||
type clientInfo struct {
|
||||
OSArch string `json:"OS"`
|
||||
Provider string `json:"provider"`
|
||||
Version string `json:"version"`
|
||||
BuildOrigin string `json:"buildOrigin,omitempty" yaml:",omitempty"`
|
||||
}
|
||||
|
||||
func getClientInfo() (*clientInfo, error) {
|
||||
p, err := getProvider()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vinfo, err := define.GetVersion()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &clientInfo{
|
||||
OSArch: vinfo.OsArch,
|
||||
Provider: p,
|
||||
Version: vinfo.Version,
|
||||
BuildOrigin: vinfo.BuildOrigin,
|
||||
}, nil
|
||||
}
|
||||
15
client_supported.go
Normal file
15
client_supported.go
Normal file
@ -0,0 +1,15 @@
|
||||
//go:build amd64 || arm64
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/containers/podman/v5/pkg/machine/provider"
|
||||
)
|
||||
|
||||
func getProvider() (string, error) {
|
||||
p, err := provider.Get()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return p.VMType().String(), nil
|
||||
}
|
||||
33
early_init_linux.go
Normal file
33
early_init_linux.go
Normal file
@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func setRLimits() error {
|
||||
rlimits := new(syscall.Rlimit)
|
||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil {
|
||||
return fmt.Errorf("getting rlimits: %w", err)
|
||||
}
|
||||
rlimits.Cur = rlimits.Max
|
||||
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil {
|
||||
return fmt.Errorf("setting new rlimits: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setUMask() {
|
||||
// Be sure we can create directories with 0755 mode.
|
||||
syscall.Umask(0022)
|
||||
}
|
||||
|
||||
func earlyInitHook() {
|
||||
if err := setRLimits(); err != nil {
|
||||
logrus.Errorf("Failed to set rlimits: %v", err)
|
||||
}
|
||||
|
||||
setUMask()
|
||||
}
|
||||
6
lib/ebpf/c/common.h
Normal file
6
lib/ebpf/c/common.h
Normal file
@ -0,0 +1,6 @@
|
||||
#include <bpf/bpf_endian.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
#include "sockmap.h"
|
||||
#include "target_ip_map.h"
|
||||
76
lib/ebpf/c/contrack.bpf.c
Normal file
76
lib/ebpf/c/contrack.bpf.c
Normal file
@ -0,0 +1,76 @@
|
||||
#include "common.h"
|
||||
|
||||
enum filter_ret_code {
|
||||
// 不处理当前连接
|
||||
PASS = 0,
|
||||
// 处理当前连接
|
||||
STORE = 1,
|
||||
};
|
||||
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
static inline
|
||||
void store_sock_ops(struct bpf_sock_ops *skops)
|
||||
{
|
||||
struct sock_key key = {
|
||||
.dip = skops->remote_ip4,
|
||||
.sip = skops->local_ip4,
|
||||
.sport = bpf_htonl(skops->local_port), /* convert to network byte order */
|
||||
.dport = skops->remote_port,
|
||||
.family = skops->family,
|
||||
};
|
||||
|
||||
int ret = bpf_sock_hash_update(skops, &sock_ops_map, &key, BPF_NOEXIST);
|
||||
if (ret != 0) {
|
||||
bpf_printk("sock_hash_update() failed, ret: %d\n", ret);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 判断传入连接是否需要处理
|
||||
static inline
|
||||
__u8 sock_filter(struct bpf_sock_ops *skops)
|
||||
{
|
||||
if (skops->family != 2){ // AF_INET
|
||||
return PASS;
|
||||
}
|
||||
/*
|
||||
local traffic only, so check both local and remote IPs
|
||||
*/
|
||||
__u32 remote_ip = skops->remote_ip4;
|
||||
__u32 local_ip = skops->local_ip4;
|
||||
|
||||
// 检查 remote_ip 是否在目标 map 中
|
||||
if (!bpf_map_lookup_elem(&target_ip_map, &remote_ip)) {
|
||||
return PASS;
|
||||
}
|
||||
|
||||
// 如果是本地流量,大概率remote ip 和 local ip 是相同的,此时可以省去一次 map 查询
|
||||
if (remote_ip == local_ip) {
|
||||
return STORE;
|
||||
}
|
||||
|
||||
// 检查 local_ip 是否在目标 map 中
|
||||
if (!bpf_map_lookup_elem(&target_ip_map, &local_ip)) {
|
||||
return PASS;
|
||||
}
|
||||
|
||||
return STORE;
|
||||
}
|
||||
|
||||
SEC("sockops")
|
||||
int bpf_sockops_handler(struct bpf_sock_ops *skops)
|
||||
{
|
||||
// only interested in established events
|
||||
if (skops->op != BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB &&
|
||||
skops->op != BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB) {
|
||||
return BPF_OK;
|
||||
}
|
||||
if (sock_filter(skops)){ // AF_INET
|
||||
store_sock_ops(skops); // 将 socket 信息记录到到 sockmap
|
||||
}
|
||||
|
||||
return BPF_OK;
|
||||
}
|
||||
|
||||
29
lib/ebpf/c/redirect.bpf.c
Normal file
29
lib/ebpf/c/redirect.bpf.c
Normal file
@ -0,0 +1,29 @@
|
||||
#include "common.h"
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
SEC("sk_msg")
|
||||
int bpf_redir(struct sk_msg_md *msg)
|
||||
{
|
||||
struct sock_key key = {
|
||||
.sip = msg->remote_ip4,
|
||||
.dip = msg->local_ip4,
|
||||
.dport = bpf_htonl(msg->local_port), /* convert to network byte order */
|
||||
.sport = msg->remote_port,
|
||||
.family = msg->family,
|
||||
};
|
||||
|
||||
int ret = bpf_msg_redirect_hash(msg, &sock_ops_map, &key, BPF_F_INGRESS);
|
||||
if (ret == SK_DROP) {
|
||||
bpf_printk("redirect from %d.%d.%d.%d:%d to %d.%d.%d.%d:%d failed\n",
|
||||
key.sip & 0xff, (key.sip >> 8) & 0xff,
|
||||
(key.sip >> 16) & 0xff, (key.sip >> 24) & 0xff,
|
||||
bpf_ntohl(key.sport),
|
||||
key.dip & 0xff, (key.dip >> 8) & 0xff,
|
||||
(key.dip >> 16) & 0xff, (key.dip >> 24) & 0xff,
|
||||
bpf_ntohl(key.dport));
|
||||
}
|
||||
|
||||
// 无论是否重定向成功,都返回 SK_PASS。这样失败的重定向还可以使用其他方法处理
|
||||
return SK_PASS;
|
||||
}
|
||||
17
lib/ebpf/c/sockmap.h
Normal file
17
lib/ebpf/c/sockmap.h
Normal file
@ -0,0 +1,17 @@
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
struct sock_key {
|
||||
__u32 sip;
|
||||
__u32 dip;
|
||||
__u32 sport;
|
||||
__u32 dport;
|
||||
__u32 family;
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_SOCKHASH);
|
||||
__uint(max_entries, 65535);
|
||||
__type(key, struct sock_key);
|
||||
__type(value, int);
|
||||
} sock_ops_map SEC(".maps");
|
||||
10
lib/ebpf/c/target_ip_map.h
Normal file
10
lib/ebpf/c/target_ip_map.h
Normal file
@ -0,0 +1,10 @@
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
// 存储需要contrack的IP地址
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__type(key, __u32); // key 类型(目标 IP 地址)
|
||||
__type(value, __u8); // value 类型(标志位,无实际用途,设置为 1 表示存在)
|
||||
__uint(max_entries, 1024); // 最多存储 1024 个目标 IP
|
||||
} target_ip_map SEC(".maps");
|
||||
BIN
lib/ebpf/ebpf.tgz
Normal file
BIN
lib/ebpf/ebpf.tgz
Normal file
Binary file not shown.
53
lib/ebpf/ebpf/app.go
Normal file
53
lib/ebpf/ebpf/app.go
Normal file
@ -0,0 +1,53 @@
|
||||
package ebpf
|
||||
|
||||
const DefaultInterfaceName string = "neto"
|
||||
|
||||
// Ebpf should be initialized only once. when agent.reloads,
|
||||
// agent.try to start ebpf again. so we need to keep track of
|
||||
// how many instances of ebpf are running. only when InstanceRefCounter
|
||||
// is 1, we should start ebpf. when InstanceRefCounter is 0, we should stop ebpf.
|
||||
var InstanceRefCounter uint32 = 0
|
||||
var ebpf *Ebpf
|
||||
|
||||
type App struct {
|
||||
InterfaceName []string `json:"interface_name,omitempty"`
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
}
|
||||
func (a *App) Provision() error {
|
||||
if len(a.InterfaceName) == 0 {
|
||||
a.InterfaceName = []string{DefaultInterfaceName}
|
||||
}
|
||||
|
||||
InstanceRefCounter++
|
||||
if InstanceRefCounter == 1 {
|
||||
|
||||
var err error
|
||||
ebpf, err = ProvisionEBPF()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ebpf.SetIfName(a.InterfaceName)
|
||||
|
||||
ebpf.Start()
|
||||
}
|
||||
|
||||
ebpf.SetIfName(a.InterfaceName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) Stop() error {
|
||||
InstanceRefCounter--
|
||||
if InstanceRefCounter == 0 {
|
||||
ebpf.Stop()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
6
lib/ebpf/ebpf/bpf/common.h
Normal file
6
lib/ebpf/ebpf/bpf/common.h
Normal file
@ -0,0 +1,6 @@
|
||||
#include <bpf/bpf_endian.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
#include "sockmap.h"
|
||||
#include "target_ip_map.h"
|
||||
76
lib/ebpf/ebpf/bpf/contrack.bpf.c
Normal file
76
lib/ebpf/ebpf/bpf/contrack.bpf.c
Normal file
@ -0,0 +1,76 @@
|
||||
#include "common.h"
|
||||
|
||||
enum filter_ret_code {
|
||||
// 不处理当前连接
|
||||
PASS = 0,
|
||||
// 处理当前连接
|
||||
STORE = 1,
|
||||
};
|
||||
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
static inline
|
||||
void store_sock_ops(struct bpf_sock_ops *skops)
|
||||
{
|
||||
struct sock_key key = {
|
||||
.dip = skops->remote_ip4,
|
||||
.sip = skops->local_ip4,
|
||||
.sport = bpf_htonl(skops->local_port), /* convert to network byte order */
|
||||
.dport = skops->remote_port,
|
||||
.family = skops->family,
|
||||
};
|
||||
|
||||
int ret = bpf_sock_hash_update(skops, &sock_ops_map, &key, BPF_NOEXIST);
|
||||
if (ret != 0) {
|
||||
bpf_printk("sock_hash_update() failed, ret: %d\n", ret);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 判断传入连接是否需要处理
|
||||
static inline
|
||||
__u8 sock_filter(struct bpf_sock_ops *skops)
|
||||
{
|
||||
if (skops->family != 2){ // AF_INET
|
||||
return PASS;
|
||||
}
|
||||
/*
|
||||
local traffic only, so check both local and remote IPs
|
||||
*/
|
||||
__u32 remote_ip = skops->remote_ip4;
|
||||
__u32 local_ip = skops->local_ip4;
|
||||
|
||||
// 检查 remote_ip 是否在目标 map 中
|
||||
if (!bpf_map_lookup_elem(&target_ip_map, &remote_ip)) {
|
||||
return PASS;
|
||||
}
|
||||
|
||||
// 如果是本地流量,大概率remote ip 和 local ip 是相同的,此时可以省去一次 map 查询
|
||||
if (remote_ip == local_ip) {
|
||||
return STORE;
|
||||
}
|
||||
|
||||
// 检查 local_ip 是否在目标 map 中
|
||||
if (!bpf_map_lookup_elem(&target_ip_map, &local_ip)) {
|
||||
return PASS;
|
||||
}
|
||||
|
||||
return STORE;
|
||||
}
|
||||
|
||||
SEC("sockops")
|
||||
int bpf_sockops_handler(struct bpf_sock_ops *skops)
|
||||
{
|
||||
// only interested in established events
|
||||
if (skops->op != BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB &&
|
||||
skops->op != BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB) {
|
||||
return BPF_OK;
|
||||
}
|
||||
if (sock_filter(skops)){ // AF_INET
|
||||
store_sock_ops(skops); // 将 socket 信息记录到到 sockmap
|
||||
}
|
||||
|
||||
return BPF_OK;
|
||||
}
|
||||
|
||||
29
lib/ebpf/ebpf/bpf/redirect.bpf.c
Normal file
29
lib/ebpf/ebpf/bpf/redirect.bpf.c
Normal file
@ -0,0 +1,29 @@
|
||||
#include "common.h"
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
SEC("sk_msg")
|
||||
int bpf_redir(struct sk_msg_md *msg)
|
||||
{
|
||||
struct sock_key key = {
|
||||
.sip = msg->remote_ip4,
|
||||
.dip = msg->local_ip4,
|
||||
.dport = bpf_htonl(msg->local_port), /* convert to network byte order */
|
||||
.sport = msg->remote_port,
|
||||
.family = msg->family,
|
||||
};
|
||||
|
||||
int ret = bpf_msg_redirect_hash(msg, &sock_ops_map, &key, BPF_F_INGRESS);
|
||||
if (ret == SK_DROP) {
|
||||
bpf_printk("redirect from %d.%d.%d.%d:%d to %d.%d.%d.%d:%d failed\n",
|
||||
key.sip & 0xff, (key.sip >> 8) & 0xff,
|
||||
(key.sip >> 16) & 0xff, (key.sip >> 24) & 0xff,
|
||||
bpf_ntohl(key.sport),
|
||||
key.dip & 0xff, (key.dip >> 8) & 0xff,
|
||||
(key.dip >> 16) & 0xff, (key.dip >> 24) & 0xff,
|
||||
bpf_ntohl(key.dport));
|
||||
}
|
||||
|
||||
// 无论是否重定向成功,都返回 SK_PASS。这样失败的重定向还可以使用其他方法处理
|
||||
return SK_PASS;
|
||||
}
|
||||
17
lib/ebpf/ebpf/bpf/sockmap.h
Normal file
17
lib/ebpf/ebpf/bpf/sockmap.h
Normal file
@ -0,0 +1,17 @@
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
struct sock_key {
|
||||
__u32 sip;
|
||||
__u32 dip;
|
||||
__u32 sport;
|
||||
__u32 dport;
|
||||
__u32 family;
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_SOCKHASH);
|
||||
__uint(max_entries, 65535);
|
||||
__type(key, struct sock_key);
|
||||
__type(value, int);
|
||||
} sock_ops_map SEC(".maps");
|
||||
10
lib/ebpf/ebpf/bpf/target_ip_map.h
Normal file
10
lib/ebpf/ebpf/bpf/target_ip_map.h
Normal file
@ -0,0 +1,10 @@
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
// 存储需要contrack的IP地址
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__type(key, __u32); // key 类型(目标 IP 地址)
|
||||
__type(value, __u8); // value 类型(标志位,无实际用途,设置为 1 表示存在)
|
||||
__uint(max_entries, 1024); // 最多存储 1024 个目标 IP
|
||||
} target_ip_map SEC(".maps");
|
||||
82
lib/ebpf/ebpf/contrack/contrack.go
Normal file
82
lib/ebpf/ebpf/contrack/contrack.go
Normal file
@ -0,0 +1,82 @@
|
||||
package contrack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/cilium/ebpf/link"
|
||||
)
|
||||
|
||||
type Contrack struct {
|
||||
obj contrackObjects
|
||||
cg link.Link
|
||||
targetIPs map[uint32]struct{}
|
||||
}
|
||||
|
||||
func New() *Contrack {
|
||||
return &Contrack{
|
||||
obj: contrackObjects{},
|
||||
targetIPs: make(map[uint32]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Contrack) Start() error {
|
||||
if err := loadContrackObjects(&c.obj, nil); err != nil {
|
||||
return fmt.Errorf("loading contrack objects failed: %w", err)
|
||||
}
|
||||
|
||||
cgroupPath, err := detectCgroupPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("attaching contrack cgroup failed for detecting cgroup path: %w", err)
|
||||
}
|
||||
|
||||
c.cg, err = link.AttachCgroup(link.CgroupOptions{
|
||||
Path: cgroupPath,
|
||||
Attach: ebpf.AttachCGroupSockOps,
|
||||
Program: c.obj.BpfSockopsHandler,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("attaching contrack cgroup failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Contrack) Close() {
|
||||
if c.cg != nil {
|
||||
c.cg.Close()
|
||||
}
|
||||
c.obj.Close()
|
||||
}
|
||||
|
||||
func (c *Contrack) SockOpsMap() *ebpf.Map {
|
||||
return c.obj.SockOpsMap
|
||||
}
|
||||
|
||||
// AddTargetIp adds an IP to the target_ip_map
|
||||
func (c *Contrack) AddTargetIP(ip *IP) error {
|
||||
value := uint8(1) // Value is not used, just needs to exist
|
||||
if err := c.obj.TargetIpMap.Put(ip.Key(), value); err != nil {
|
||||
return fmt.Errorf("failed to update target_ip_map with IP %s: %v", ip, err)
|
||||
}
|
||||
|
||||
c.targetIPs[ip.Key()] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveTargetIPWithPreCheck removes an IP from the target_ip_map after checking if it exists
|
||||
func (c *Contrack) RemoveTargetIPWithPreCheck(ip *IP) error {
|
||||
if _, ok := c.targetIPs[ip.Key()]; !ok {
|
||||
return fmt.Errorf("IP %s is not in target_ip_map", ip)
|
||||
}
|
||||
return c.RemoveTargetIP(ip)
|
||||
}
|
||||
|
||||
// RemoveTargetIP removes an IP from the target_ip_map
|
||||
func (c *Contrack) RemoveTargetIP(ip *IP) error {
|
||||
if err := c.obj.TargetIpMap.Delete(ip.Key()); err != nil {
|
||||
return fmt.Errorf("failed to remove IP %s from target_ip_map: %v", ip, err)
|
||||
}
|
||||
delete(c.targetIPs, ip.Key())
|
||||
return nil
|
||||
}
|
||||
144
lib/ebpf/ebpf/contrack/contrack_bpfeb.go
Normal file
144
lib/ebpf/ebpf/contrack/contrack_bpfeb.go
Normal file
@ -0,0 +1,144 @@
|
||||
// Code generated by bpf2go; DO NOT EDIT.
|
||||
//go:build mips || mips64 || ppc64 || s390x
|
||||
|
||||
package contrack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
)
|
||||
|
||||
type contrackSockKey struct {
|
||||
Sip uint32
|
||||
Dip uint32
|
||||
Sport uint32
|
||||
Dport uint32
|
||||
Family uint32
|
||||
}
|
||||
|
||||
// loadContrack returns the embedded CollectionSpec for contrack.
|
||||
func loadContrack() (*ebpf.CollectionSpec, error) {
|
||||
reader := bytes.NewReader(_ContrackBytes)
|
||||
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't load contrack: %w", err)
|
||||
}
|
||||
|
||||
return spec, err
|
||||
}
|
||||
|
||||
// loadContrackObjects loads contrack and converts it into a struct.
|
||||
//
|
||||
// The following types are suitable as obj argument:
|
||||
//
|
||||
// *contrackObjects
|
||||
// *contrackPrograms
|
||||
// *contrackMaps
|
||||
//
|
||||
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||
func loadContrackObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||
spec, err := loadContrack()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return spec.LoadAndAssign(obj, opts)
|
||||
}
|
||||
|
||||
// contrackSpecs contains maps and programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type contrackSpecs struct {
|
||||
contrackProgramSpecs
|
||||
contrackMapSpecs
|
||||
contrackVariableSpecs
|
||||
}
|
||||
|
||||
// contrackProgramSpecs contains programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type contrackProgramSpecs struct {
|
||||
BpfSockopsHandler *ebpf.ProgramSpec `ebpf:"bpf_sockops_handler"`
|
||||
}
|
||||
|
||||
// contrackMapSpecs contains maps before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type contrackMapSpecs struct {
|
||||
SockOpsMap *ebpf.MapSpec `ebpf:"sock_ops_map"`
|
||||
TargetIpMap *ebpf.MapSpec `ebpf:"target_ip_map"`
|
||||
}
|
||||
|
||||
// contrackVariableSpecs contains global variables before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type contrackVariableSpecs struct {
|
||||
}
|
||||
|
||||
// contrackObjects contains all objects after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadContrackObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type contrackObjects struct {
|
||||
contrackPrograms
|
||||
contrackMaps
|
||||
contrackVariables
|
||||
}
|
||||
|
||||
func (o *contrackObjects) Close() error {
|
||||
return _ContrackClose(
|
||||
&o.contrackPrograms,
|
||||
&o.contrackMaps,
|
||||
)
|
||||
}
|
||||
|
||||
// contrackMaps contains all maps after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadContrackObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type contrackMaps struct {
|
||||
SockOpsMap *ebpf.Map `ebpf:"sock_ops_map"`
|
||||
TargetIpMap *ebpf.Map `ebpf:"target_ip_map"`
|
||||
}
|
||||
|
||||
func (m *contrackMaps) Close() error {
|
||||
return _ContrackClose(
|
||||
m.SockOpsMap,
|
||||
m.TargetIpMap,
|
||||
)
|
||||
}
|
||||
|
||||
// contrackVariables contains all global variables after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadContrackObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type contrackVariables struct {
|
||||
}
|
||||
|
||||
// contrackPrograms contains all programs after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadContrackObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type contrackPrograms struct {
|
||||
BpfSockopsHandler *ebpf.Program `ebpf:"bpf_sockops_handler"`
|
||||
}
|
||||
|
||||
func (p *contrackPrograms) Close() error {
|
||||
return _ContrackClose(
|
||||
p.BpfSockopsHandler,
|
||||
)
|
||||
}
|
||||
|
||||
func _ContrackClose(closers ...io.Closer) error {
|
||||
for _, closer := range closers {
|
||||
if err := closer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not access this directly.
|
||||
//
|
||||
//go:embed contrack_bpfeb.o
|
||||
var _ContrackBytes []byte
|
||||
0
lib/ebpf/ebpf/contrack/contrack_bpfeb.o
Normal file
0
lib/ebpf/ebpf/contrack/contrack_bpfeb.o
Normal file
144
lib/ebpf/ebpf/contrack/contrack_bpfel.go
Normal file
144
lib/ebpf/ebpf/contrack/contrack_bpfel.go
Normal file
@ -0,0 +1,144 @@
|
||||
// Code generated by bpf2go; DO NOT EDIT.
|
||||
//go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64
|
||||
|
||||
package contrack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
)
|
||||
|
||||
type contrackSockKey struct {
|
||||
Sip uint32
|
||||
Dip uint32
|
||||
Sport uint32
|
||||
Dport uint32
|
||||
Family uint32
|
||||
}
|
||||
|
||||
// loadContrack returns the embedded CollectionSpec for contrack.
|
||||
func loadContrack() (*ebpf.CollectionSpec, error) {
|
||||
reader := bytes.NewReader(_ContrackBytes)
|
||||
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't load contrack: %w", err)
|
||||
}
|
||||
|
||||
return spec, err
|
||||
}
|
||||
|
||||
// loadContrackObjects loads contrack and converts it into a struct.
|
||||
//
|
||||
// The following types are suitable as obj argument:
|
||||
//
|
||||
// *contrackObjects
|
||||
// *contrackPrograms
|
||||
// *contrackMaps
|
||||
//
|
||||
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||
func loadContrackObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||
spec, err := loadContrack()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return spec.LoadAndAssign(obj, opts)
|
||||
}
|
||||
|
||||
// contrackSpecs contains maps and programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type contrackSpecs struct {
|
||||
contrackProgramSpecs
|
||||
contrackMapSpecs
|
||||
contrackVariableSpecs
|
||||
}
|
||||
|
||||
// contrackProgramSpecs contains programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type contrackProgramSpecs struct {
|
||||
BpfSockopsHandler *ebpf.ProgramSpec `ebpf:"bpf_sockops_handler"`
|
||||
}
|
||||
|
||||
// contrackMapSpecs contains maps before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type contrackMapSpecs struct {
|
||||
SockOpsMap *ebpf.MapSpec `ebpf:"sock_ops_map"`
|
||||
TargetIpMap *ebpf.MapSpec `ebpf:"target_ip_map"`
|
||||
}
|
||||
|
||||
// contrackVariableSpecs contains global variables before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type contrackVariableSpecs struct {
|
||||
}
|
||||
|
||||
// contrackObjects contains all objects after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadContrackObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type contrackObjects struct {
|
||||
contrackPrograms
|
||||
contrackMaps
|
||||
contrackVariables
|
||||
}
|
||||
|
||||
func (o *contrackObjects) Close() error {
|
||||
return _ContrackClose(
|
||||
&o.contrackPrograms,
|
||||
&o.contrackMaps,
|
||||
)
|
||||
}
|
||||
|
||||
// contrackMaps contains all maps after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadContrackObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type contrackMaps struct {
|
||||
SockOpsMap *ebpf.Map `ebpf:"sock_ops_map"`
|
||||
TargetIpMap *ebpf.Map `ebpf:"target_ip_map"`
|
||||
}
|
||||
|
||||
func (m *contrackMaps) Close() error {
|
||||
return _ContrackClose(
|
||||
m.SockOpsMap,
|
||||
m.TargetIpMap,
|
||||
)
|
||||
}
|
||||
|
||||
// contrackVariables contains all global variables after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadContrackObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type contrackVariables struct {
|
||||
}
|
||||
|
||||
// contrackPrograms contains all programs after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadContrackObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type contrackPrograms struct {
|
||||
BpfSockopsHandler *ebpf.Program `ebpf:"bpf_sockops_handler"`
|
||||
}
|
||||
|
||||
func (p *contrackPrograms) Close() error {
|
||||
return _ContrackClose(
|
||||
p.BpfSockopsHandler,
|
||||
)
|
||||
}
|
||||
|
||||
func _ContrackClose(closers ...io.Closer) error {
|
||||
for _, closer := range closers {
|
||||
if err := closer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not access this directly.
|
||||
//
|
||||
//go:embed contrack_bpfel.o
|
||||
var _ContrackBytes []byte
|
||||
0
lib/ebpf/ebpf/contrack/contrack_bpfel.o
Normal file
0
lib/ebpf/ebpf/contrack/contrack_bpfel.o
Normal file
3
lib/ebpf/ebpf/contrack/gen.go
Normal file
3
lib/ebpf/ebpf/contrack/gen.go
Normal file
@ -0,0 +1,3 @@
|
||||
package contrack
|
||||
|
||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go contrack ../bpf/contrack.bpf.c
|
||||
41
lib/ebpf/ebpf/contrack/ip.go
Normal file
41
lib/ebpf/ebpf/contrack/ip.go
Normal file
@ -0,0 +1,41 @@
|
||||
package contrack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
)
|
||||
|
||||
type IP struct {
|
||||
net.IP
|
||||
key uint32
|
||||
}
|
||||
|
||||
func (ip IP) Key() uint32 {
|
||||
return ip.key
|
||||
}
|
||||
|
||||
func ParseIPStr(ipStr string) *IP {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ParseIP(ip)
|
||||
}
|
||||
|
||||
func ParseIP(ip net.IP) *IP {
|
||||
parsedIP := ip.To4()
|
||||
if parsedIP == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var key uint32
|
||||
buf := bytes.NewReader(parsedIP)
|
||||
binary.Read(buf, binary.LittleEndian, &key)
|
||||
|
||||
return &IP{
|
||||
IP: parsedIP,
|
||||
key: key,
|
||||
}
|
||||
}
|
||||
29
lib/ebpf/ebpf/contrack/util.go
Normal file
29
lib/ebpf/ebpf/contrack/util.go
Normal file
@ -0,0 +1,29 @@
|
||||
package contrack
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// detectCgroupPath returns the first-found mount point of type cgroup2
|
||||
// and stores it in the cgroupPath global variable.
|
||||
func detectCgroupPath() (string, error) {
|
||||
f, err := os.Open("/proc/mounts")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
// example fields: cgroup2 /sys/fs/cgroup/unified cgroup2 rw,nosuid,nodev,noexec,relatime 0 0
|
||||
fields := strings.Split(scanner.Text(), " ")
|
||||
if len(fields) >= 3 && fields[2] == "cgroup2" {
|
||||
return fields[1], nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("cgroup2 not mounted")
|
||||
}
|
||||
118
lib/ebpf/ebpf/ebpf.go
Normal file
118
lib/ebpf/ebpf/ebpf.go
Normal file
@ -0,0 +1,118 @@
|
||||
package ebpf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"floares/lib/ebpf/ebpf/contrack"
|
||||
"floares/lib/ebpf/ebpf/redirect"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/cilium/ebpf/rlimit"
|
||||
"github.com/vishvananda/netlink"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Ebpf struct {
|
||||
contrack *contrack.Contrack
|
||||
redirect *redirect.Redirect
|
||||
|
||||
cancel context.CancelFunc
|
||||
ifName []string
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func (e *Ebpf) Start() {
|
||||
var ctx context.Context
|
||||
ctx, e.cancel = context.WithCancel(context.Background())
|
||||
|
||||
addrUpdate := make(chan netlink.AddrUpdate)
|
||||
netlink.AddrSubscribe(addrUpdate, ctx.Done())
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case addr := <-addrUpdate:
|
||||
if filterByIfName(addr, e.ifName) {
|
||||
fmt.Println(addr.LinkIndex, addr.LinkAddress)
|
||||
parsedIP := contrack.ParseIP(addr.LinkAddress.IP)
|
||||
if parsedIP == nil {
|
||||
e.logger.Debug("support only ipv4 now, not: ", zap.String("ip", addr.LinkAddress.IP.String()))
|
||||
continue
|
||||
}
|
||||
if addr.NewAddr {
|
||||
if err := e.contrack.AddTargetIP(parsedIP); err != nil {
|
||||
e.logger.Error("failed to add target ip", zap.String("ip", parsedIP.String()), zap.Error(err))
|
||||
} else {
|
||||
e.logger.Debug("added target ip", zap.String("ip", parsedIP.String()))
|
||||
}
|
||||
} else {
|
||||
if err := e.contrack.RemoveTargetIPWithPreCheck(parsedIP); err != nil {
|
||||
e.logger.Error("failed to remove target ip", zap.String("ip", parsedIP.String()), zap.Error(err))
|
||||
} else {
|
||||
e.logger.Debug("removed target ip", zap.String("ip", parsedIP.String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (e *Ebpf) Stop() {
|
||||
e.cancel()
|
||||
}
|
||||
|
||||
func (e *Ebpf) SetIfName(ifName []string) {
|
||||
e.ifName = ifName
|
||||
}
|
||||
|
||||
func (e *Ebpf) SetLogger(logger *zap.Logger) {
|
||||
e.logger = logger
|
||||
}
|
||||
|
||||
// ProvisionEBPF caller must call this function only once.
|
||||
func ProvisionEBPF() (*Ebpf, error) {
|
||||
// Remove resource limits for kernels <5.11.
|
||||
if err := rlimit.RemoveMemlock(); err != nil {
|
||||
return nil, fmt.Errorf("removing memlock: %w", err)
|
||||
}
|
||||
|
||||
con := contrack.New()
|
||||
|
||||
if err := con.Start(); err != nil {
|
||||
return nil, fmt.Errorf("starting contrack failed: %w", err)
|
||||
}
|
||||
|
||||
red := redirect.New(con.SockOpsMap())
|
||||
if err := red.Start(); err != nil {
|
||||
return nil, fmt.Errorf("starting redirect failed: %w", err)
|
||||
}
|
||||
|
||||
return &Ebpf{
|
||||
contrack: con,
|
||||
redirect: red,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// filterByIfName filters the AddrUpdate by interface name.
|
||||
// When AddrUpdate.LinkIndex is not found there is two cases, we handle it in different ways:
|
||||
// 1. The addr is added. returns false.
|
||||
// 2. The addr is removed, return true. Because the link is deleted first and then the addr is removed.
|
||||
func filterByIfName(au netlink.AddrUpdate, ifName []string) bool {
|
||||
link, err := netlink.LinkByIndex(au.LinkIndex)
|
||||
if err != nil {
|
||||
if errors.As(err, &netlink.LinkNotFoundError{}) && !au.NewAddr {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if slices.Contains(ifName, link.Attrs().Name) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
3
lib/ebpf/ebpf/redirect/gen.go
Normal file
3
lib/ebpf/ebpf/redirect/gen.go
Normal file
@ -0,0 +1,3 @@
|
||||
package redirect
|
||||
|
||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go redirect ../bpf/redirect.bpf.c
|
||||
47
lib/ebpf/ebpf/redirect/redirect.go
Normal file
47
lib/ebpf/ebpf/redirect/redirect.go
Normal file
@ -0,0 +1,47 @@
|
||||
package redirect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
)
|
||||
|
||||
type Redirect struct {
|
||||
sockMap *ebpf.Map
|
||||
obj redirectObjects
|
||||
}
|
||||
|
||||
func New(sockMap *ebpf.Map) *Redirect {
|
||||
return &Redirect{
|
||||
sockMap: sockMap,
|
||||
obj: redirectObjects{},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Redirect) Start() (err error) {
|
||||
if err = s.loadProgram(); err != nil {
|
||||
return fmt.Errorf("loading redirect objects failed: %w", err)
|
||||
}
|
||||
|
||||
err = AttachSkMsg(SkMsgOptions{
|
||||
Program: s.obj.BpfRedir,
|
||||
Map: s.sockMap.FD(),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("attaching redirect failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Redirect) Close() {
|
||||
s.obj.Close()
|
||||
}
|
||||
|
||||
func (s *Redirect) loadProgram() error {
|
||||
return loadRedirectObjects(&s.obj, &ebpf.CollectionOptions{
|
||||
MapReplacements: map[string]*ebpf.Map{
|
||||
"sock_ops_map": s.sockMap,
|
||||
},
|
||||
})
|
||||
}
|
||||
144
lib/ebpf/ebpf/redirect/redirect_bpfeb.go
Normal file
144
lib/ebpf/ebpf/redirect/redirect_bpfeb.go
Normal file
@ -0,0 +1,144 @@
|
||||
// Code generated by bpf2go; DO NOT EDIT.
|
||||
//go:build mips || mips64 || ppc64 || s390x
|
||||
|
||||
package redirect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
)
|
||||
|
||||
type redirectSockKey struct {
|
||||
Sip uint32
|
||||
Dip uint32
|
||||
Sport uint32
|
||||
Dport uint32
|
||||
Family uint32
|
||||
}
|
||||
|
||||
// loadRedirect returns the embedded CollectionSpec for redirect.
|
||||
func loadRedirect() (*ebpf.CollectionSpec, error) {
|
||||
reader := bytes.NewReader(_RedirectBytes)
|
||||
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't load redirect: %w", err)
|
||||
}
|
||||
|
||||
return spec, err
|
||||
}
|
||||
|
||||
// loadRedirectObjects loads redirect and converts it into a struct.
|
||||
//
|
||||
// The following types are suitable as obj argument:
|
||||
//
|
||||
// *redirectObjects
|
||||
// *redirectPrograms
|
||||
// *redirectMaps
|
||||
//
|
||||
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||
func loadRedirectObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||
spec, err := loadRedirect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return spec.LoadAndAssign(obj, opts)
|
||||
}
|
||||
|
||||
// redirectSpecs contains maps and programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type redirectSpecs struct {
|
||||
redirectProgramSpecs
|
||||
redirectMapSpecs
|
||||
redirectVariableSpecs
|
||||
}
|
||||
|
||||
// redirectProgramSpecs contains programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type redirectProgramSpecs struct {
|
||||
BpfRedir *ebpf.ProgramSpec `ebpf:"bpf_redir"`
|
||||
}
|
||||
|
||||
// redirectMapSpecs contains maps before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type redirectMapSpecs struct {
|
||||
SockOpsMap *ebpf.MapSpec `ebpf:"sock_ops_map"`
|
||||
TargetIpMap *ebpf.MapSpec `ebpf:"target_ip_map"`
|
||||
}
|
||||
|
||||
// redirectVariableSpecs contains global variables before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type redirectVariableSpecs struct {
|
||||
}
|
||||
|
||||
// redirectObjects contains all objects after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadRedirectObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type redirectObjects struct {
|
||||
redirectPrograms
|
||||
redirectMaps
|
||||
redirectVariables
|
||||
}
|
||||
|
||||
func (o *redirectObjects) Close() error {
|
||||
return _RedirectClose(
|
||||
&o.redirectPrograms,
|
||||
&o.redirectMaps,
|
||||
)
|
||||
}
|
||||
|
||||
// redirectMaps contains all maps after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadRedirectObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type redirectMaps struct {
|
||||
SockOpsMap *ebpf.Map `ebpf:"sock_ops_map"`
|
||||
TargetIpMap *ebpf.Map `ebpf:"target_ip_map"`
|
||||
}
|
||||
|
||||
func (m *redirectMaps) Close() error {
|
||||
return _RedirectClose(
|
||||
m.SockOpsMap,
|
||||
m.TargetIpMap,
|
||||
)
|
||||
}
|
||||
|
||||
// redirectVariables contains all global variables after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadRedirectObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type redirectVariables struct {
|
||||
}
|
||||
|
||||
// redirectPrograms contains all programs after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadRedirectObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type redirectPrograms struct {
|
||||
BpfRedir *ebpf.Program `ebpf:"bpf_redir"`
|
||||
}
|
||||
|
||||
func (p *redirectPrograms) Close() error {
|
||||
return _RedirectClose(
|
||||
p.BpfRedir,
|
||||
)
|
||||
}
|
||||
|
||||
func _RedirectClose(closers ...io.Closer) error {
|
||||
for _, closer := range closers {
|
||||
if err := closer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not access this directly.
|
||||
//
|
||||
//go:embed redirect_bpfeb.o
|
||||
var _RedirectBytes []byte
|
||||
0
lib/ebpf/ebpf/redirect/redirect_bpfeb.o
Normal file
0
lib/ebpf/ebpf/redirect/redirect_bpfeb.o
Normal file
144
lib/ebpf/ebpf/redirect/redirect_bpfel.go
Normal file
144
lib/ebpf/ebpf/redirect/redirect_bpfel.go
Normal file
@ -0,0 +1,144 @@
|
||||
// Code generated by bpf2go; DO NOT EDIT.
|
||||
//go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64
|
||||
|
||||
package redirect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
)
|
||||
|
||||
type redirectSockKey struct {
|
||||
Sip uint32
|
||||
Dip uint32
|
||||
Sport uint32
|
||||
Dport uint32
|
||||
Family uint32
|
||||
}
|
||||
|
||||
// loadRedirect returns the embedded CollectionSpec for redirect.
|
||||
func loadRedirect() (*ebpf.CollectionSpec, error) {
|
||||
reader := bytes.NewReader(_RedirectBytes)
|
||||
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't load redirect: %w", err)
|
||||
}
|
||||
|
||||
return spec, err
|
||||
}
|
||||
|
||||
// loadRedirectObjects loads redirect and converts it into a struct.
|
||||
//
|
||||
// The following types are suitable as obj argument:
|
||||
//
|
||||
// *redirectObjects
|
||||
// *redirectPrograms
|
||||
// *redirectMaps
|
||||
//
|
||||
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||
func loadRedirectObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||
spec, err := loadRedirect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return spec.LoadAndAssign(obj, opts)
|
||||
}
|
||||
|
||||
// redirectSpecs contains maps and programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type redirectSpecs struct {
|
||||
redirectProgramSpecs
|
||||
redirectMapSpecs
|
||||
redirectVariableSpecs
|
||||
}
|
||||
|
||||
// redirectProgramSpecs contains programs before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type redirectProgramSpecs struct {
|
||||
BpfRedir *ebpf.ProgramSpec `ebpf:"bpf_redir"`
|
||||
}
|
||||
|
||||
// redirectMapSpecs contains maps before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type redirectMapSpecs struct {
|
||||
SockOpsMap *ebpf.MapSpec `ebpf:"sock_ops_map"`
|
||||
TargetIpMap *ebpf.MapSpec `ebpf:"target_ip_map"`
|
||||
}
|
||||
|
||||
// redirectVariableSpecs contains global variables before they are loaded into the kernel.
|
||||
//
|
||||
// It can be passed ebpf.CollectionSpec.Assign.
|
||||
type redirectVariableSpecs struct {
|
||||
}
|
||||
|
||||
// redirectObjects contains all objects after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadRedirectObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type redirectObjects struct {
|
||||
redirectPrograms
|
||||
redirectMaps
|
||||
redirectVariables
|
||||
}
|
||||
|
||||
func (o *redirectObjects) Close() error {
|
||||
return _RedirectClose(
|
||||
&o.redirectPrograms,
|
||||
&o.redirectMaps,
|
||||
)
|
||||
}
|
||||
|
||||
// redirectMaps contains all maps after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadRedirectObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type redirectMaps struct {
|
||||
SockOpsMap *ebpf.Map `ebpf:"sock_ops_map"`
|
||||
TargetIpMap *ebpf.Map `ebpf:"target_ip_map"`
|
||||
}
|
||||
|
||||
func (m *redirectMaps) Close() error {
|
||||
return _RedirectClose(
|
||||
m.SockOpsMap,
|
||||
m.TargetIpMap,
|
||||
)
|
||||
}
|
||||
|
||||
// redirectVariables contains all global variables after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadRedirectObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type redirectVariables struct {
|
||||
}
|
||||
|
||||
// redirectPrograms contains all programs after they have been loaded into the kernel.
|
||||
//
|
||||
// It can be passed to loadRedirectObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||
type redirectPrograms struct {
|
||||
BpfRedir *ebpf.Program `ebpf:"bpf_redir"`
|
||||
}
|
||||
|
||||
func (p *redirectPrograms) Close() error {
|
||||
return _RedirectClose(
|
||||
p.BpfRedir,
|
||||
)
|
||||
}
|
||||
|
||||
func _RedirectClose(closers ...io.Closer) error {
|
||||
for _, closer := range closers {
|
||||
if err := closer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not access this directly.
|
||||
//
|
||||
//go:embed redirect_bpfel.o
|
||||
var _RedirectBytes []byte
|
||||
0
lib/ebpf/ebpf/redirect/redirect_bpfel.o
Normal file
0
lib/ebpf/ebpf/redirect/redirect_bpfel.o
Normal file
33
lib/ebpf/ebpf/redirect/skmsg.go
Normal file
33
lib/ebpf/ebpf/redirect/skmsg.go
Normal file
@ -0,0 +1,33 @@
|
||||
package redirect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/cilium/ebpf/link"
|
||||
)
|
||||
|
||||
type SkMsgOptions struct {
|
||||
// Program must be an SkMsg BPF program.
|
||||
Program *ebpf.Program
|
||||
|
||||
// Map is the BPF map the program is attached to.
|
||||
Map int
|
||||
}
|
||||
|
||||
func AttachSkMsg(opts SkMsgOptions) error {
|
||||
if t := opts.Program.Type(); t != ebpf.SkMsg {
|
||||
return fmt.Errorf("invalid program type %s, expected SkMsg", t)
|
||||
}
|
||||
|
||||
err := link.RawAttachProgram(link.RawAttachProgramOptions{
|
||||
Target: opts.Map,
|
||||
Program: opts.Program,
|
||||
Attach: ebpf.AttachSkMsgVerdict,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to attach link: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
581
root.go
Normal file
581
root.go
Normal file
@ -0,0 +1,581 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/common/pkg/ssh"
|
||||
"github.com/containers/podman/v5/cmd/podman/common"
|
||||
"github.com/containers/podman/v5/cmd/podman/registry"
|
||||
"github.com/containers/podman/v5/libpod/define"
|
||||
"github.com/containers/podman/v5/pkg/bindings"
|
||||
"github.com/containers/podman/v5/pkg/domain/entities"
|
||||
"github.com/containers/storage"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// HelpTemplate is the help template for podman commands
|
||||
// This uses the short and long options.
|
||||
// command should not use this.
|
||||
const helpTemplate = `{{.Short}}
|
||||
|
||||
Description:
|
||||
{{.Long}}
|
||||
|
||||
{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
|
||||
|
||||
// UsageTemplate is the usage template for podman commands
|
||||
// This blocks the displaying of the global options. The main podman
|
||||
// command should not use this.
|
||||
const usageTemplate = `Usage:{{if (and .Runnable (not .HasAvailableSubCommands))}}
|
||||
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
|
||||
{{.UseLine}} [command]{{end}}{{if gt (len .Aliases) 0}}
|
||||
|
||||
Aliases:
|
||||
{{.NameAndAliases}}{{end}}{{if .HasExample}}
|
||||
|
||||
Examples:
|
||||
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
|
||||
|
||||
Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
|
||||
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
|
||||
|
||||
Options:
|
||||
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
var (
|
||||
//rootCmd = &cobra.Command{
|
||||
// // In shell completion, there is `.exe` suffix on Windows.
|
||||
// // This does not provide the same experience across platforms
|
||||
// // and was mentioned in [#16499](https://github.com/containers/podman/issues/16499).
|
||||
// Use: strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe") + " [options]",
|
||||
// Long: "Manage pods, containers and images",
|
||||
// SilenceUsage: true,
|
||||
// SilenceErrors: true,
|
||||
// TraverseChildren: true,
|
||||
// PersistentPreRunE: persistentPreRunE,
|
||||
// RunE: validate.SubCommandExists,
|
||||
// //PersistentPostRunE: persistentPostRunE,
|
||||
// Version: version.Version.String(),
|
||||
// DisableFlagsInUseLine: true,
|
||||
//}
|
||||
|
||||
defaultLogLevel = "warn"
|
||||
logLevel = defaultLogLevel
|
||||
dockerConfig = ""
|
||||
debug bool
|
||||
|
||||
requireCleanup = true
|
||||
|
||||
// Defaults for capturing/redirecting the command output since (the) cobra is
|
||||
// global-hungry and doesn't allow you to attach anything that allows us to
|
||||
// transform the noStdout BoolVar to a string that we can assign to useStdout.
|
||||
noStdout = false
|
||||
useStdout = ""
|
||||
)
|
||||
|
||||
func Init() {
|
||||
// Hooks are called before PersistentPreRunE(). These hooks affect global
|
||||
// state and are executed after processing the command-line, but before
|
||||
// actually running the command.
|
||||
cobra.OnInitialize(
|
||||
stdOutHook, // Caution, this hook redirects stdout and output from any following hooks may be affected.
|
||||
loggingHook,
|
||||
syslogHook,
|
||||
earlyInitHook,
|
||||
configHook,
|
||||
)
|
||||
|
||||
// backwards compat still allow --cni-config-dir
|
||||
//rootCmd.Flags().SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
// if name == "cni-config-dir" {
|
||||
// name = "network-config-dir"
|
||||
// }
|
||||
// return pflag.NormalizedName(name)
|
||||
//})
|
||||
//rootCmd.SetUsageTemplate(usageTemplate)
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
//if err := rootCmd.ExecuteContext(registry.Context()); err != nil {
|
||||
// if registry.GetExitCode() == 0 {
|
||||
// registry.SetExitCode(define.ExecErrorCodeGeneric)
|
||||
// }
|
||||
// if registry.IsRemote() {
|
||||
// if errors.As(err, &bindings.ConnectError{}) {
|
||||
// fmt.Fprintln(os.Stderr, "Cannot connect to Podman. Please verify your connection to the Linux system using `podman system connection list`, or try `podman machine init` and `podman machine start` to manage a new Linux VM")
|
||||
// }
|
||||
// }
|
||||
// fmt.Fprintln(os.Stderr, formatError(err))
|
||||
//}
|
||||
//
|
||||
//_ = shutdown.Stop()
|
||||
//
|
||||
//if requireCleanup {
|
||||
// // The cobra post-run is not being executed in case of
|
||||
// // a previous error, so make sure that the engine(s)
|
||||
// // are correctly shutdown.
|
||||
// //
|
||||
// // See https://github.com/spf13/cobra/issues/914
|
||||
// logrus.Debugf("Shutting down engines")
|
||||
// if engine := registry.ImageEngine(); engine != nil {
|
||||
// engine.Shutdown(registry.Context())
|
||||
// }
|
||||
// if engine := registry.ContainerEngine(); engine != nil {
|
||||
// engine.Shutdown(registry.Context())
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//os.Exit(registry.GetExitCode())
|
||||
}
|
||||
|
||||
// readRemoteCliFlags reads cli flags related to operating podman remotely
|
||||
func readRemoteCliFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig) error {
|
||||
|
||||
conf := podmanConfig.ContainersConfDefaultsRO
|
||||
contextConn, host := cmd.Root().LocalFlags().Lookup("context"), cmd.Root().LocalFlags().Lookup("host")
|
||||
conn, url := cmd.Root().LocalFlags().Lookup("connection"), cmd.Root().LocalFlags().Lookup("url")
|
||||
|
||||
switch {
|
||||
case conn != nil && conn.Changed:
|
||||
if contextConn != nil && contextConn.Changed {
|
||||
return fmt.Errorf("use of --connection and --context at the same time is not allowed")
|
||||
}
|
||||
con, err := conf.GetConnection(conn.Value.String(), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
podmanConfig.URI = con.URI
|
||||
podmanConfig.Identity = con.Identity
|
||||
podmanConfig.MachineMode = con.IsMachine
|
||||
case url.Changed:
|
||||
podmanConfig.URI = url.Value.String()
|
||||
case contextConn != nil && contextConn.Changed:
|
||||
service := contextConn.Value.String()
|
||||
if service != "default" {
|
||||
con, err := conf.GetConnection(service, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
podmanConfig.URI = con.URI
|
||||
podmanConfig.Identity = con.Identity
|
||||
podmanConfig.MachineMode = con.IsMachine
|
||||
}
|
||||
case host.Changed:
|
||||
podmanConfig.URI = host.Value.String()
|
||||
default:
|
||||
// No cli options set, in case CONTAINER_CONNECTION was set to something
|
||||
// invalid this contains the error, see setupRemoteConnection().
|
||||
// Important so that we can show a proper useful error message but still
|
||||
// allow the cli overwrites (https://github.com/containers/podman/pull/22997).
|
||||
return podmanConfig.ConnectionError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setupRemoteConnection returns information about the active service destination
|
||||
// The order of priority is:
|
||||
// 1. cli flags (--connection ,--url ,--context ,--host);
|
||||
// 2. Env variables (CONTAINER_HOST and CONTAINER_CONNECTION);
|
||||
// 3. ActiveService from containers.conf;
|
||||
// 4. RemoteURI;
|
||||
// Returns the name of the default connection if any.
|
||||
func setupRemoteConnection(podmanConfig *entities.PodmanConfig) string {
|
||||
conf := podmanConfig.ContainersConfDefaultsRO
|
||||
connEnv, hostEnv, sshkeyEnv := os.Getenv("CONTAINER_CONNECTION"), os.Getenv("CONTAINER_HOST"), os.Getenv("CONTAINER_SSHKEY")
|
||||
|
||||
switch {
|
||||
case connEnv != "":
|
||||
con, err := conf.GetConnection(connEnv, false)
|
||||
if err != nil {
|
||||
podmanConfig.ConnectionError = err
|
||||
return connEnv
|
||||
}
|
||||
podmanConfig.URI = con.URI
|
||||
podmanConfig.Identity = con.Identity
|
||||
podmanConfig.MachineMode = con.IsMachine
|
||||
return con.Name
|
||||
case hostEnv != "":
|
||||
if sshkeyEnv != "" {
|
||||
podmanConfig.Identity = sshkeyEnv
|
||||
}
|
||||
podmanConfig.URI = hostEnv
|
||||
default:
|
||||
con, err := conf.GetConnection("", true)
|
||||
if err == nil {
|
||||
podmanConfig.URI = con.URI
|
||||
podmanConfig.Identity = con.Identity
|
||||
podmanConfig.MachineMode = con.IsMachine
|
||||
return con.Name
|
||||
}
|
||||
podmanConfig.URI = registry.DefaultAPIAddress()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func persistentPreRunE(cmd *cobra.Command, args []string) error {
|
||||
logrus.Debugf("Called %s.PersistentPreRunE(%s)", cmd.Name(), strings.Join(os.Args, " "))
|
||||
|
||||
// Help, completion and commands with subcommands are special cases, no need for more setup
|
||||
// Completion cmd is used to generate the shell scripts
|
||||
|
||||
podmanConfig := registry.PodmanConfig()
|
||||
log.Println(*podmanConfig)
|
||||
if !registry.IsRemote() {
|
||||
if cmd.Flag("hooks-dir").Changed {
|
||||
podmanConfig.ContainersConf.Engine.HooksDir.Set(podmanConfig.HooksDir)
|
||||
}
|
||||
|
||||
// Currently it is only possible to restore a container with the same runtime
|
||||
// as used for checkpointing. It should be possible to make crun and runc
|
||||
// compatible to restore a container with another runtime then checkpointed.
|
||||
// Currently that does not work.
|
||||
// To make it easier for users we will look into the checkpoint archive and
|
||||
// set the runtime to the one used during checkpointing.
|
||||
|
||||
}
|
||||
|
||||
if err := readRemoteCliFlags(cmd, podmanConfig); err != nil {
|
||||
return fmt.Errorf("read cli flags: %w", err)
|
||||
}
|
||||
|
||||
// Special case if command is hidden completion command ("__complete","__completeNoDesc")
|
||||
// Since __completeNoDesc is an alias the cm.Name is always __complete
|
||||
//if cmd.Name() == cobra.ShellCompRequestCmd {
|
||||
// // Parse the cli arguments after the completion cmd (always called as second argument)
|
||||
// // This ensures that the --url, --identity and --connection flags are properly set
|
||||
// compCmd, _, err := cmd.Root().Traverse(os.Args[2:])
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// // If we don't complete the root cmd hide all root flags
|
||||
// // so they won't show up in the completions on subcommands.
|
||||
// if compCmd != compCmd.Root() {
|
||||
// compCmd.Root().Flags().VisitAll(func(flag *pflag.Flag) {
|
||||
// flag.Hidden = true
|
||||
// })
|
||||
// }
|
||||
// // No need for further setup the completion logic setups the engines as needed.
|
||||
// requireCleanup = false
|
||||
// return nil
|
||||
//}
|
||||
|
||||
c := &cobra.Command{}
|
||||
// Prep the engines
|
||||
if _, err := registry.NewImageEngine(c, args); err != nil {
|
||||
// Note: this is gross, but it is the hand we are dealt
|
||||
if registry.IsRemote() && errors.As(err, &bindings.ConnectError{}) && cmd.Name() == "info" && c.Parent() == c.Root() {
|
||||
clientDesc, err := getClientInfo()
|
||||
// we eat the error here. if this fails, they just don't any client info
|
||||
if err == nil {
|
||||
b, _ := yaml.Marshal(clientDesc)
|
||||
fmt.Println(string(b))
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
if _, err := registry.NewContainerEngine(c, args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Hard code TMPDIR functions to use /var/tmp, if user did not override
|
||||
if _, ok := os.LookupEnv("TMPDIR"); !ok {
|
||||
if tmpdir, err := podmanConfig.ContainersConfDefaultsRO.ImageCopyTmpDir(); err != nil {
|
||||
logrus.Warnf("Failed to retrieve default tmp dir: %s", err.Error())
|
||||
} else {
|
||||
os.Setenv("TMPDIR", tmpdir)
|
||||
}
|
||||
}
|
||||
|
||||
//if !registry.IsRemote() {
|
||||
// if c.Flag("cpu-profile").Changed {
|
||||
// f, err := os.Create(podmanConfig.CPUProfile)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if err := pprof.StartCPUProfile(f); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
// if c.Flag("memory-profile").Changed {
|
||||
// // Same value as the default in github.com/pkg/profile.
|
||||
// runtime.MemProfileRate = 4096
|
||||
// if rate := os.Getenv("MemProfileRate"); rate != "" {
|
||||
// r, err := strconv.Atoi(rate)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// runtime.MemProfileRate = r
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if podmanConfig.MaxWorks <= 0 {
|
||||
// return fmt.Errorf("maximum workers must be set to a positive number (got %d)", podmanConfig.MaxWorks)
|
||||
// }
|
||||
// if err := parallel.SetMaxThreads(uint(podmanConfig.MaxWorks)); err != nil {
|
||||
// return err
|
||||
// }
|
||||
//}
|
||||
// Setup Rootless environment, IFF:
|
||||
// 1) in ABI mode
|
||||
// 2) running as non-root
|
||||
// 3) command doesn't require Parent Namespace
|
||||
_, found := c.Annotations[registry.ParentNSRequired]
|
||||
if !registry.IsRemote() && !found {
|
||||
cgroupMode := ""
|
||||
_, noMoveProcess := c.Annotations[registry.NoMoveProcess]
|
||||
if flag := c.LocalFlags().Lookup("cgroups"); flag != nil {
|
||||
cgroupMode = flag.Value.String()
|
||||
}
|
||||
err := registry.ContainerEngine().SetupRootless(registry.Context(), noMoveProcess, cgroupMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func configHook() {
|
||||
if dockerConfig != "" {
|
||||
if err := os.Setenv("DOCKER_CONFIG", dockerConfig); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cannot set DOCKER_CONFIG=%s: %s", dockerConfig, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loggingHook() {
|
||||
var found bool
|
||||
if debug {
|
||||
if logLevel != defaultLogLevel {
|
||||
fmt.Fprintf(os.Stderr, "Setting --log-level and --debug is not allowed\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
logLevel = "debug"
|
||||
}
|
||||
for _, l := range common.LogLevels {
|
||||
if l == strings.ToLower(logLevel) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
fmt.Fprintf(os.Stderr, "Log Level %q is not supported, choose from: %s\n", logLevel, strings.Join(common.LogLevels, ", "))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
level, err := logrus.ParseLevel(logLevel)
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
logrus.SetLevel(level)
|
||||
|
||||
if logrus.IsLevelEnabled(logrus.InfoLevel) {
|
||||
logrus.Infof("%s filtering at log level %s", os.Args[0], logrus.GetLevel())
|
||||
}
|
||||
}
|
||||
|
||||
// used for capturing podman's formatted output to some file as per the -out and -noout flags.
|
||||
func stdOutHook() {
|
||||
// if noStdOut was specified, then assign /dev/null as the standard file for output.
|
||||
if noStdout {
|
||||
useStdout = os.DevNull
|
||||
}
|
||||
// if we were given a filename for output, then open that and use it. we end up leaking
|
||||
// the file since it's intended to be in scope as long as our process is running.
|
||||
if useStdout != "" {
|
||||
if fd, err := os.OpenFile(useStdout, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm); err == nil {
|
||||
os.Stdout = fd
|
||||
|
||||
// if we couldn't open the file for write, then just bail with an error.
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "unable to open file for standard output: %s\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func rootFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig) {
|
||||
connectionName := setupRemoteConnection(podmanConfig)
|
||||
|
||||
lFlags := cmd.Flags()
|
||||
|
||||
sshFlagName := "ssh"
|
||||
lFlags.StringVar(&podmanConfig.SSHMode, sshFlagName, string(ssh.GolangMode), "define the ssh mode")
|
||||
|
||||
connectionFlagName := "connection"
|
||||
lFlags.StringP(connectionFlagName, "c", connectionName, "Connection to use for remote Podman service (CONTAINER_CONNECTION)")
|
||||
|
||||
urlFlagName := "url"
|
||||
lFlags.StringVar(&podmanConfig.URI, urlFlagName, podmanConfig.URI, "URL to access Podman service (CONTAINER_HOST)")
|
||||
lFlags.StringVarP(&podmanConfig.URI, "host", "H", podmanConfig.URI, "Used for Docker compatibility")
|
||||
_ = lFlags.MarkHidden("host")
|
||||
|
||||
lFlags.StringVar(&dockerConfig, "config", "", "Location of authentication config file")
|
||||
|
||||
// Context option added just for compatibility with DockerCLI.
|
||||
lFlags.String("context", "default", "Name of the context to use to connect to the daemon (This flag is a NOOP and provided solely for scripting compatibility.)")
|
||||
_ = lFlags.MarkHidden("context")
|
||||
|
||||
identityFlagName := "identity"
|
||||
lFlags.StringVar(&podmanConfig.Identity, identityFlagName, podmanConfig.Identity, "path to SSH identity file, (CONTAINER_SSHKEY)")
|
||||
|
||||
// Flags that control or influence any kind of output.
|
||||
outFlagName := "out"
|
||||
lFlags.StringVar(&useStdout, outFlagName, "", "Send output (stdout) from podman to a file")
|
||||
|
||||
lFlags.BoolVar(&noStdout, "noout", false, "do not output to stdout")
|
||||
_ = lFlags.MarkHidden("noout") // Superseded by --out
|
||||
|
||||
lFlags.BoolVarP(&podmanConfig.Remote, "remote", "r", registry.IsRemote(), "Access remote Podman service")
|
||||
pFlags := cmd.PersistentFlags()
|
||||
if registry.IsRemote() {
|
||||
if err := lFlags.MarkHidden("remote"); err != nil {
|
||||
logrus.Warnf("Unable to mark --remote flag as hidden: %s", err.Error())
|
||||
}
|
||||
podmanConfig.Remote = true
|
||||
} else {
|
||||
// The --module's are actually used and parsed in
|
||||
// `registry.PodmanConfig()`. But we also need to expose them
|
||||
// as a flag here to a) make sure that rootflags are aware of
|
||||
// this flag and b) to have shell completions.
|
||||
moduleFlagName := "module"
|
||||
lFlags.StringArray(moduleFlagName, nil, "Load the containers.conf(5) module")
|
||||
|
||||
// A *hidden* flag to change the database backend.
|
||||
pFlags.StringVar(&podmanConfig.ContainersConf.Engine.DBBackend, "db-backend", podmanConfig.ContainersConfDefaultsRO.Engine.DBBackend, "Database backend to use")
|
||||
|
||||
cgroupManagerFlagName := "cgroup-manager"
|
||||
pFlags.StringVar(&podmanConfig.ContainersConf.Engine.CgroupManager, cgroupManagerFlagName, podmanConfig.ContainersConfDefaultsRO.Engine.CgroupManager, "Cgroup manager to use (\"cgroupfs\"|\"systemd\")")
|
||||
|
||||
pFlags.StringVar(&podmanConfig.CPUProfile, "cpu-profile", "", "Path for the cpu-profiling results")
|
||||
pFlags.StringVar(&podmanConfig.MemoryProfile, "memory-profile", "", "Path for the memory-profiling results")
|
||||
|
||||
conmonFlagName := "conmon"
|
||||
pFlags.StringVar(&podmanConfig.ConmonPath, conmonFlagName, "", "Path of the conmon binary")
|
||||
|
||||
// TODO (6.0): --network-cmd-path is deprecated, remove this option with the next major release
|
||||
// We need to find all the places that use r.config.Engine.NetworkCmdPath and remove it
|
||||
networkCmdPathFlagName := "network-cmd-path"
|
||||
pFlags.StringVar(&podmanConfig.ContainersConf.Engine.NetworkCmdPath, networkCmdPathFlagName, podmanConfig.ContainersConfDefaultsRO.Engine.NetworkCmdPath, "Path to the command for configuring the network")
|
||||
|
||||
networkConfigDirFlagName := "network-config-dir"
|
||||
pFlags.StringVar(&podmanConfig.ContainersConf.Network.NetworkConfigDir, networkConfigDirFlagName, podmanConfig.ContainersConfDefaultsRO.Network.NetworkConfigDir, "Path of the configuration directory for networks")
|
||||
|
||||
pFlags.StringVar(&podmanConfig.ContainersConf.Containers.DefaultMountsFile, "default-mounts-file", podmanConfig.ContainersConfDefaultsRO.Containers.DefaultMountsFile, "Path to default mounts file")
|
||||
|
||||
eventsBackendFlagName := "events-backend"
|
||||
pFlags.StringVar(&podmanConfig.ContainersConf.Engine.EventsLogger, eventsBackendFlagName, podmanConfig.ContainersConfDefaultsRO.Engine.EventsLogger, `Events backend to use ("file"|"journald"|"none")`)
|
||||
|
||||
hooksDirFlagName := "hooks-dir"
|
||||
pFlags.StringArrayVar(&podmanConfig.HooksDir, hooksDirFlagName, podmanConfig.ContainersConfDefaultsRO.Engine.HooksDir.Get(), "Set the OCI hooks directory path (may be set multiple times)")
|
||||
|
||||
pFlags.IntVar(&podmanConfig.MaxWorks, "max-workers", (runtime.NumCPU()*3)+1, "The maximum number of workers for parallel operations")
|
||||
|
||||
namespaceFlagName := "namespace"
|
||||
pFlags.StringVar(&podmanConfig.ContainersConf.Engine.Namespace, namespaceFlagName, podmanConfig.ContainersConfDefaultsRO.Engine.Namespace, "Set the libpod namespace, used to create separate views of the containers and pods on the system")
|
||||
_ = pFlags.MarkHidden(namespaceFlagName)
|
||||
|
||||
networkBackendFlagName := "network-backend"
|
||||
pFlags.StringVar(&podmanConfig.ContainersConf.Network.NetworkBackend, networkBackendFlagName, podmanConfig.ContainersConfDefaultsRO.Network.NetworkBackend, `Network backend to use ("cni"|"netavark")`)
|
||||
_ = pFlags.MarkHidden(networkBackendFlagName)
|
||||
|
||||
rootFlagName := "root"
|
||||
pFlags.StringVar(&podmanConfig.GraphRoot, rootFlagName, "", "Path to the graph root directory where images, containers, etc. are stored")
|
||||
|
||||
pFlags.StringVar(&podmanConfig.RegistriesConf, "registries-conf", "", "Path to a registries.conf to use for image processing")
|
||||
|
||||
runrootFlagName := "runroot"
|
||||
pFlags.StringVar(&podmanConfig.Runroot, runrootFlagName, "", "Path to the 'run directory' where all state information is stored")
|
||||
|
||||
imageStoreFlagName := "imagestore"
|
||||
pFlags.StringVar(&podmanConfig.ImageStore, imageStoreFlagName, "", "Path to the 'image store', different from 'graph root', use this to split storing the image into a separate 'image store', see 'man containers-storage.conf' for details")
|
||||
|
||||
pFlags.BoolVar(&podmanConfig.TransientStore, "transient-store", false, "Enable transient container storage")
|
||||
|
||||
pFlags.StringArrayVar(&podmanConfig.PullOptions, "pull-option", nil, "Specify an option to change how the image is pulled")
|
||||
|
||||
runtimeFlagName := "runtime"
|
||||
pFlags.StringVar(&podmanConfig.RuntimePath, runtimeFlagName, "youki", "Path to the OCI-compatible binary used to run containers.")
|
||||
|
||||
// -s is deprecated due to conflict with -s on subcommands
|
||||
storageDriverFlagName := "storage-driver"
|
||||
pFlags.StringVar(&podmanConfig.StorageDriver, storageDriverFlagName, "", "Select which storage driver is used to manage storage of images and containers")
|
||||
|
||||
tmpdirFlagName := "tmpdir"
|
||||
pFlags.StringVar(&podmanConfig.ContainersConf.Engine.TmpDir, tmpdirFlagName, podmanConfig.ContainersConfDefaultsRO.Engine.TmpDir, "Path to the tmp directory for libpod state content.\n\nNote: use the environment variable 'TMPDIR' to change the temporary storage location for container images, '/var/tmp'.\n")
|
||||
|
||||
pFlags.BoolVar(&podmanConfig.Trace, "trace", false, "Enable opentracing output (default false)")
|
||||
|
||||
volumePathFlagName := "volumepath"
|
||||
pFlags.StringVar(&podmanConfig.ContainersConf.Engine.VolumePath, volumePathFlagName, podmanConfig.ContainersConfDefaultsRO.Engine.VolumePath, "Path to the volume directory in which volume data is stored")
|
||||
|
||||
// Hide these flags for both ABI and Tunneling
|
||||
for _, f := range []string{
|
||||
"cpu-profile",
|
||||
"db-backend",
|
||||
"default-mounts-file",
|
||||
"max-workers",
|
||||
"memory-profile",
|
||||
"pull-option",
|
||||
"registries-conf",
|
||||
"trace",
|
||||
} {
|
||||
if err := pFlags.MarkHidden(f); err != nil {
|
||||
logrus.Warnf("Unable to mark %s flag as hidden: %s", f, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
storageOptFlagName := "storage-opt"
|
||||
pFlags.StringArrayVar(&podmanConfig.StorageOpts, storageOptFlagName, []string{}, "Used to pass an option to the storage driver")
|
||||
|
||||
// Override default --help information of `--help` global flag
|
||||
var dummyHelp bool
|
||||
pFlags.BoolVar(&dummyHelp, "help", false, "Help for podman")
|
||||
|
||||
logLevelFlagName := "log-level"
|
||||
pFlags.StringVar(&logLevel, logLevelFlagName, logLevel, fmt.Sprintf("Log messages above specified level (%s)", strings.Join(common.LogLevels, ", ")))
|
||||
|
||||
lFlags.BoolVarP(&debug, "debug", "D", false, "Docker compatibility, force setting of log-level")
|
||||
_ = lFlags.MarkHidden("debug")
|
||||
|
||||
// Only create these flags for ABI connections
|
||||
if !registry.IsRemote() {
|
||||
runtimeflagFlagName := "runtime-flag"
|
||||
pFlags.StringArrayVar(&podmanConfig.RuntimeFlags, runtimeflagFlagName, []string{}, "add global flags for the container runtime")
|
||||
|
||||
pFlags.BoolVar(&podmanConfig.Syslog, "syslog", false, "Output logging information to syslog as well as the console (default false)")
|
||||
}
|
||||
}
|
||||
|
||||
func formatError(err error) string {
|
||||
var message string
|
||||
switch {
|
||||
case errors.Is(err, define.ErrOCIRuntime):
|
||||
// OCIRuntimeErrors include the reason for the failure in the
|
||||
// second to last message in the error chain.
|
||||
message = fmt.Sprintf(
|
||||
"Error: %s: %s",
|
||||
define.ErrOCIRuntime.Error(),
|
||||
strings.TrimSuffix(err.Error(), ": "+define.ErrOCIRuntime.Error()),
|
||||
)
|
||||
case errors.Is(err, storage.ErrDuplicateName):
|
||||
message = fmt.Sprintf("Error: %s, or use --replace to instruct Podman to do so.", err.Error())
|
||||
default:
|
||||
if logrus.IsLevelEnabled(logrus.TraceLevel) {
|
||||
message = fmt.Sprintf("Error: %+v", err)
|
||||
} else {
|
||||
message = fmt.Sprintf("Error: %v", err)
|
||||
}
|
||||
}
|
||||
return message
|
||||
}
|
||||
24
syslog_common.go
Normal file
24
syslog_common.go
Normal file
@ -0,0 +1,24 @@
|
||||
//go:build linux || freebsd
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log/syslog"
|
||||
|
||||
"github.com/containers/podman/v5/cmd/podman/registry"
|
||||
"github.com/sirupsen/logrus"
|
||||
logrusSyslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||
)
|
||||
|
||||
func syslogHook() {
|
||||
if !registry.PodmanConfig().Syslog {
|
||||
return
|
||||
}
|
||||
|
||||
hook, err := logrusSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
|
||||
if err != nil {
|
||||
logrus.Debug("Failed to initialize syslog hook: " + err.Error())
|
||||
} else {
|
||||
logrus.AddHook(hook)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user