...

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