...

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/api/v1alpha1"
    27  	"github.com/chaos-mesh/chaos-mesh/pkg/bpm"
    28  	pb "github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/pb"
    29  	"github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/util"
    30  )
    31  
    32  const (
    33  	ipsetExistErr        = "set with the same name already exists"
    34  	ipExistErr           = "it's already added"
    35  	ipsetNewNameExistErr = "a set with the new name already exists"
    36  )
    37  
    38  func (s *DaemonServer) FlushIPSets(ctx context.Context, req *pb.IPSetsRequest) (*empty.Empty, error) {
    39  	log := s.getLoggerFromContext(ctx)
    40  	log.Info("flush ipset", "request", req)
    41  
    42  	pid, err := s.crClient.GetPidFromContainerID(ctx, req.ContainerId)
    43  	if err != nil {
    44  		log.Error(err, "error while getting PID")
    45  		return nil, err
    46  	}
    47  
    48  	for _, ipset := range req.Ipsets {
    49  		// All operations on the ipset with the same name should be serialized,
    50  		// because ipset is not isolated with namespace in linux < 3.12
    51  
    52  		// **Notice**: Serialization should be enough for Chaos Mesh (but no
    53  		// need to use name to simulate isolation), because the operation on
    54  		// the ipset with the same name should be same for NetworkChaos.
    55  		// It's a bad solution, only for the users who don't want to upgrade
    56  		// their linux version to 3.12 :(
    57  		ipset := ipset
    58  		s.IPSetLocker.Lock(ipset.Name)
    59  		err := flushIPSet(ctx, log, req.EnterNS, pid, ipset)
    60  		s.IPSetLocker.Unlock(ipset.Name)
    61  		if err != nil {
    62  			return nil, err
    63  		}
    64  	}
    65  
    66  	return &empty.Empty{}, nil
    67  }
    68  
    69  func flushIPSet(ctx context.Context, log logr.Logger, enterNS bool, pid uint32, set *pb.IPSet) error {
    70  	name := set.Name
    71  
    72  	// If the IP set already exists, it will be renamed to this temp name.
    73  	tmpName := fmt.Sprintf("%sold", name)
    74  
    75  	ipSetType := v1alpha1.IPSetType(set.Type)
    76  	var values []string
    77  
    78  	switch ipSetType {
    79  	case v1alpha1.SetIPSet:
    80  		values = set.SetNames
    81  	case v1alpha1.NetIPSet:
    82  		values = set.Cidrs
    83  	case v1alpha1.NetPortIPSet:
    84  		for _, cidrAndPort := range set.CidrAndPorts {
    85  			values = append(values, fmt.Sprintf("%s,%d", cidrAndPort.Cidr, cidrAndPort.Port))
    86  		}
    87  	default:
    88  		return fmt.Errorf("unexpected IP set type: %s", ipSetType)
    89  	}
    90  
    91  	// IP sets can't be deleted if there are iptables rules referencing them.
    92  	// Therefore, we create new sets and swap them.
    93  	if err := createIPSet(ctx, log, enterNS, pid, tmpName, ipSetType); err != nil {
    94  		return err
    95  	}
    96  
    97  	// Populate the IP set.
    98  	for _, value := range values {
    99  		if err := addToIPSet(ctx, log, enterNS, pid, tmpName, value); err != nil {
   100  			return err
   101  		}
   102  	}
   103  
   104  	// Finally, rename the IP set to target name.
   105  	err := renameIPSet(ctx, log, enterNS, pid, tmpName, name)
   106  
   107  	return err
   108  }
   109  
   110  func createIPSet(ctx context.Context, log logr.Logger, enterNS bool, pid uint32, name string, ipSetType v1alpha1.IPSetType) error {
   111  	// ipset name cannot be longer than 31 bytes
   112  	if len(name) > 31 {
   113  		name = name[:31]
   114  	}
   115  
   116  	processBuilder := bpm.DefaultProcessBuilder("ipset", "create", name, string(ipSetType)).SetContext(ctx)
   117  	if enterNS {
   118  		processBuilder = processBuilder.SetNS(pid, bpm.NetNS)
   119  	}
   120  
   121  	cmd := processBuilder.Build(ctx)
   122  	log.Info("create ipset", "command", cmd.String())
   123  
   124  	out, err := cmd.CombinedOutput()
   125  	if err != nil {
   126  		output := string(out)
   127  		if !strings.Contains(output, ipsetExistErr) {
   128  			log.Error(err, "ipset create error", "command", cmd.String(), "output", output)
   129  			return util.EncodeOutputToError(out, err)
   130  		}
   131  
   132  		processBuilder = bpm.DefaultProcessBuilder("ipset", "flush", name).SetContext(ctx)
   133  		if enterNS {
   134  			processBuilder = processBuilder.SetNS(pid, bpm.NetNS)
   135  		}
   136  
   137  		cmd = processBuilder.Build(ctx)
   138  		log.Info("flush ipset", "command", cmd.String())
   139  
   140  		out, err := cmd.CombinedOutput()
   141  		if err != nil {
   142  			log.Error(err, "ipset flush error", "command", cmd.String(), "output", string(out))
   143  			return util.EncodeOutputToError(out, err)
   144  		}
   145  	}
   146  
   147  	return nil
   148  }
   149  
   150  func addToIPSet(ctx context.Context, log logr.Logger, enterNS bool, pid uint32, name string, value string) error {
   151  	processBuilder := bpm.DefaultProcessBuilder("ipset", "add", name, value).SetContext(ctx)
   152  	if enterNS {
   153  		processBuilder = processBuilder.SetNS(pid, bpm.NetNS)
   154  	}
   155  	cmd := processBuilder.Build(ctx)
   156  	log.Info("add to ipset", "command", cmd.String())
   157  
   158  	out, err := cmd.CombinedOutput()
   159  	if err != nil {
   160  		output := string(out)
   161  		if !strings.Contains(output, ipExistErr) {
   162  			log.Error(err, "ipset add error", "command", cmd.String(), "output", output)
   163  			return util.EncodeOutputToError(out, err)
   164  		}
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  func renameIPSet(ctx context.Context, log logr.Logger, enterNS bool, pid uint32, oldName string, newName string) error {
   171  	processBuilder := bpm.DefaultProcessBuilder("ipset", "rename", oldName, newName).SetContext(ctx)
   172  	if enterNS {
   173  		processBuilder = processBuilder.SetNS(pid, bpm.NetNS)
   174  	}
   175  
   176  	cmd := processBuilder.Build(ctx)
   177  	log.Info("rename ipset", "command", cmd.String())
   178  
   179  	out, err := cmd.CombinedOutput()
   180  	if err != nil {
   181  		output := string(out)
   182  		if !strings.Contains(output, ipsetNewNameExistErr) {
   183  			log.Error(err, "rename ipset failed", "command", cmd.String(), "output", output)
   184  			return util.EncodeOutputToError(out, err)
   185  		}
   186  
   187  		// swap the old ipset and the new ipset if the new ipset already exist.
   188  		processBuilder = bpm.DefaultProcessBuilder("ipset", "swap", oldName, newName).SetContext(ctx)
   189  		if enterNS {
   190  			processBuilder = processBuilder.SetNS(pid, bpm.NetNS)
   191  		}
   192  		cmd := processBuilder.Build(ctx)
   193  		log.Info("swap ipset", "command", cmd.String())
   194  
   195  		out, err := cmd.CombinedOutput()
   196  		if err != nil {
   197  			log.Error(err, "swap ipset failed", "command", cmd.String(), "output", string(out))
   198  			return util.EncodeOutputToError(out, err)
   199  		}
   200  	}
   201  	return nil
   202  }
   203