...

Source file src/github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/ipset_server.go

Documentation: github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon

     1  // Copyright 2021 Chaos Mesh 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  
    16  package chaosdaemon
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"strings"
    22  
    23  	"github.com/go-logr/logr"
    24  	"github.com/golang/protobuf/ptypes/empty"
    25  
    26  	"github.com/chaos-mesh/chaos-mesh/pkg/bpm"
    27  	pb "github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/pb"
    28  )
    29  
    30  const (
    31  	ipsetExistErr        = "set with the same name already exists"
    32  	ipExistErr           = "it's already added"
    33  	ipsetNewNameExistErr = "a set with the new name already exists"
    34  )
    35  
    36  func (s *DaemonServer) FlushIPSets(ctx context.Context, req *pb.IPSetsRequest) (*empty.Empty, error) {
    37  	log := s.getLoggerFromContext(ctx)
    38  	log.Info("flush ipset", "request", req)
    39  
    40  	pid, err := s.crClient.GetPidFromContainerID(ctx, req.ContainerId)
    41  	if err != nil {
    42  		log.Error(err, "error while getting PID")
    43  		return nil, err
    44  	}
    45  
    46  	for _, ipset := range req.Ipsets {
    47  		// All operations on the ipset with the same name should be serialized,
    48  		// because ipset is not isolated with namespace in linux < 3.12
    49  
    50  		// **Notice**: Serialization should be enough for Chaos Mesh (but no
    51  		// need to use name to simulate isolation), because the operation on
    52  		// the ipset with the same name should be same for NetworkChaos.
    53  		// It's a bad solution, only for the users who don't want to upgrade
    54  		// their linux version to 3.12 :(
    55  		ipset := ipset
    56  		s.IPSetLocker.Lock(ipset.Name)
    57  		err := flushIPSet(ctx, log, req.EnterNS, pid, ipset)
    58  		s.IPSetLocker.Unlock(ipset.Name)
    59  		if err != nil {
    60  			return nil, err
    61  		}
    62  	}
    63  
    64  	return &empty.Empty{}, nil
    65  }
    66  
    67  func flushIPSet(ctx context.Context, log logr.Logger, enterNS bool, pid uint32, set *pb.IPSet) error {
    68  	name := set.Name
    69  
    70  	// If the ipset already exists, the ipset will be renamed to this temp name.
    71  	tmpName := fmt.Sprintf("%sold", name)
    72  
    73  	// the ipset while existing iptables rules are using them can not be deleted,.
    74  	// so we creates an temp ipset and swap it with existing one.
    75  	if err := createIPSet(ctx, log, enterNS, pid, tmpName); err != nil {
    76  		return err
    77  	}
    78  
    79  	// add ips to the temp ipset
    80  	if err := addCIDRsToIPSet(ctx, log, enterNS, pid, tmpName, set.Cidrs); err != nil {
    81  		return err
    82  	}
    83  
    84  	// rename the temp ipset with the target name of ipset if the taget ipset not exists,
    85  	// otherwise swap  them with each other.
    86  	err := renameIPSet(ctx, log, enterNS, pid, tmpName, name)
    87  
    88  	return err
    89  }
    90  
    91  func createIPSet(ctx context.Context, log logr.Logger, enterNS bool, pid uint32, name string) error {
    92  	// ipset name cannot be longer than 31 bytes
    93  	if len(name) > 31 {
    94  		name = name[:31]
    95  	}
    96  
    97  	processBuilder := bpm.DefaultProcessBuilder("ipset", "create", name, "hash:net").SetContext(ctx)
    98  	if enterNS {
    99  		processBuilder = processBuilder.SetNS(pid, bpm.NetNS)
   100  	}
   101  
   102  	cmd := processBuilder.Build(ctx)
   103  	log.Info("create ipset", "command", cmd.String())
   104  
   105  	out, err := cmd.CombinedOutput()
   106  	if err != nil {
   107  		output := string(out)
   108  		if !strings.Contains(output, ipsetExistErr) {
   109  			log.Error(err, "ipset create error", "command", cmd.String(), "output", output)
   110  			return encodeOutputToError(out, err)
   111  		}
   112  
   113  		processBuilder = bpm.DefaultProcessBuilder("ipset", "flush", name).SetContext(ctx)
   114  		if enterNS {
   115  			processBuilder = processBuilder.SetNS(pid, bpm.NetNS)
   116  		}
   117  
   118  		cmd = processBuilder.Build(ctx)
   119  		log.Info("flush ipset", "command", cmd.String())
   120  
   121  		out, err := cmd.CombinedOutput()
   122  		if err != nil {
   123  			log.Error(err, "ipset flush error", "command", cmd.String(), "output", string(out))
   124  			return encodeOutputToError(out, err)
   125  		}
   126  	}
   127  
   128  	return nil
   129  }
   130  
   131  func addCIDRsToIPSet(ctx context.Context, log logr.Logger, enterNS bool, pid uint32, name string, cidrs []string) error {
   132  	for _, cidr := range cidrs {
   133  		processBuilder := bpm.DefaultProcessBuilder("ipset", "add", name, cidr).SetContext(ctx)
   134  		if enterNS {
   135  			processBuilder = processBuilder.SetNS(pid, bpm.NetNS)
   136  		}
   137  		cmd := processBuilder.Build(ctx)
   138  		log.Info("add CIDR to ipset", "command", cmd.String())
   139  
   140  		out, err := cmd.CombinedOutput()
   141  		if err != nil {
   142  			output := string(out)
   143  			if !strings.Contains(output, ipExistErr) {
   144  				log.Error(err, "ipset add error", "command", cmd.String(), "output", output)
   145  				return encodeOutputToError(out, err)
   146  			}
   147  		}
   148  	}
   149  
   150  	return nil
   151  }
   152  
   153  func renameIPSet(ctx context.Context, log logr.Logger, enterNS bool, pid uint32, oldName string, newName string) error {
   154  	processBuilder := bpm.DefaultProcessBuilder("ipset", "rename", oldName, newName).SetContext(ctx)
   155  	if enterNS {
   156  		processBuilder = processBuilder.SetNS(pid, bpm.NetNS)
   157  	}
   158  
   159  	cmd := processBuilder.Build(ctx)
   160  	log.Info("rename ipset", "command", cmd.String())
   161  
   162  	out, err := cmd.CombinedOutput()
   163  	if err != nil {
   164  		output := string(out)
   165  		if !strings.Contains(output, ipsetNewNameExistErr) {
   166  			log.Error(err, "rename ipset failed", "command", cmd.String(), "output", output)
   167  			return encodeOutputToError(out, err)
   168  		}
   169  
   170  		// swap the old ipset and the new ipset if the new ipset already exist.
   171  		processBuilder = bpm.DefaultProcessBuilder("ipset", "swap", oldName, newName).SetContext(ctx)
   172  		if enterNS {
   173  			processBuilder = processBuilder.SetNS(pid, bpm.NetNS)
   174  		}
   175  		cmd := processBuilder.Build(ctx)
   176  		log.Info("swap ipset", "command", cmd.String())
   177  
   178  		out, err := cmd.CombinedOutput()
   179  		if err != nil {
   180  			log.Error(err, "swap ipset failed", "command", cmd.String(), "output", string(out))
   181  			return encodeOutputToError(out, err)
   182  		}
   183  	}
   184  	return nil
   185  }
   186