github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/network/debinterfaces/bridge.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package debinterfaces
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  )
    10  
    11  type deviceNameSet map[string]bool
    12  
    13  // The set of options which should be moved from an existing 'iface'
    14  // stanza when turning it into a bridged device.
    15  var bridgeOnlyOptions = []string{
    16  	"address",
    17  	"gateway",
    18  	"netmask",
    19  	"dns-nameservers",
    20  	"dns-search",
    21  	"dns-sortlist",
    22  }
    23  
    24  func pruneOptions(options []string, names ...string) []string {
    25  	toPrune := map[string]bool{}
    26  	for _, v := range names {
    27  		toPrune[v] = true
    28  	}
    29  	result := make([]string, 0, len(options))
    30  	for _, o := range options {
    31  		words := strings.Fields(o)
    32  		if len(words) >= 1 && !toPrune[words[0]] {
    33  			result = append(result, o)
    34  		}
    35  	}
    36  	return result
    37  }
    38  
    39  func pruneOptionsWithPrefix(options []string, prefix string) []string {
    40  	result := make([]string, 0, len(options))
    41  	for _, o := range options {
    42  		words := strings.Fields(o)
    43  		if len(words) >= 1 && !strings.HasPrefix(words[0], prefix) {
    44  			result = append(result, o)
    45  		}
    46  	}
    47  	return result
    48  }
    49  
    50  func isLoopbackDevice(s *IfaceStanza) bool {
    51  	words := strings.Fields(s.Definition()[0])
    52  	return len(words) >= 4 && words[3] == "loopback"
    53  }
    54  
    55  func newAutoStanza(deviceNames ...string) *AutoStanza {
    56  	return &AutoStanza{
    57  		stanza: stanza{
    58  			definition: fmt.Sprintf("auto %s", strings.Join(deviceNames, " ")),
    59  		},
    60  		DeviceNames: deviceNames,
    61  	}
    62  }
    63  
    64  func isDefinitionBridgeable(iface *IfaceStanza) bool {
    65  	definitions := iface.definition
    66  	words := strings.Fields(definitions)
    67  	return len(words) >= 4
    68  }
    69  
    70  func turnManual(bridgeName string, iface IfaceStanza) *IfaceStanza {
    71  	if iface.IsAlias {
    72  		words := strings.Fields(iface.definition)
    73  		words[1] = bridgeName
    74  		iface.definition = strings.Join(words, " ")
    75  		return &iface
    76  	}
    77  	words := strings.Fields(iface.definition)
    78  	words[3] = "manual"
    79  	iface.definition = strings.Join(words, " ")
    80  	iface.Options = pruneOptions(iface.Options, bridgeOnlyOptions...)
    81  	return &iface
    82  }
    83  
    84  func bridgeInterface(bridgeName string, iface IfaceStanza) *IfaceStanza {
    85  	words := strings.Fields(iface.definition)
    86  	words[1] = bridgeName
    87  	iface.definition = strings.Join(words, " ")
    88  	iface.Options = pruneOptions(iface.Options, "mtu")
    89  	if iface.IsVLAN {
    90  		iface.Options = pruneOptions(iface.Options, "vlan_id", "vlan-raw-device")
    91  	}
    92  	if iface.HasBondOptions {
    93  		iface.Options = pruneOptionsWithPrefix(iface.Options, "bond-")
    94  	}
    95  	iface.Options = append(iface.Options, fmt.Sprintf("bridge_ports %s", iface.DeviceName))
    96  	return &iface
    97  }
    98  
    99  func isBridgeable(iface *IfaceStanza) bool {
   100  	return isDefinitionBridgeable(iface) &&
   101  		!isLoopbackDevice(iface) &&
   102  		!iface.IsBridged &&
   103  		!iface.HasBondMasterOption
   104  }
   105  
   106  // Bridge turns existing devices into bridged devices.
   107  func Bridge(stanzas []Stanza, devices map[string]string) []Stanza {
   108  	result := make([]Stanza, 0)
   109  	autoStanzaSet := map[string]bool{}
   110  	manualInetSet := map[string]bool{}
   111  	manualInet6Set := map[string]bool{}
   112  	bridges := map[string]bool{}
   113  	ifacesToBridge := make([]IfaceStanza, 0)
   114  	devicesToBridge := deviceNameSet{}
   115  
   116  	for name := range devices {
   117  		devicesToBridge[name] = true
   118  	}
   119  
   120  	// We don't want to perturb the existing order, except for
   121  	// aliases which we do mutate in situ. All the bridged stanzas
   122  	// are added to the end of the original input.
   123  
   124  	for _, s := range stanzas {
   125  		switch v := s.(type) {
   126  		case IfaceStanza:
   127  			if devicesToBridge[v.DeviceName] && isBridgeable(&v) {
   128  				// we need to have only iface XXX inet manual
   129  				// TODO do we need to have separate manual for inet6 or is one sufficient?
   130  				if strings.Fields(v.definition)[2] == "inet" && !manualInetSet[v.DeviceName] {
   131  					result = append(result, *turnManual(devices[v.DeviceName], v))
   132  					manualInetSet[v.DeviceName] = true
   133  				}
   134  				if strings.Fields(v.definition)[2] == "inet6" && !manualInet6Set[v.DeviceName] {
   135  					result = append(result, *turnManual(devices[v.DeviceName], v))
   136  					manualInet6Set[v.DeviceName] = true
   137  				}
   138  				ifacesToBridge = append(ifacesToBridge, v)
   139  			} else {
   140  				result = append(result, s)
   141  				if v.IsBridged {
   142  					bridges[v.DeviceName] = true
   143  				}
   144  			}
   145  		case AutoStanza:
   146  			names := v.DeviceNames
   147  			for i, name := range names {
   148  				if isAlias(name) && devicesToBridge[name] {
   149  					names[i] = devices[name]
   150  					autoStanzaSet[names[i]] = true
   151  				} else {
   152  					autoStanzaSet[names[i]] = true
   153  				}
   154  			}
   155  			result = append(result, *newAutoStanza(names...))
   156  		case SourceStanza:
   157  			v.Stanzas = Bridge(v.Stanzas, devices)
   158  			result = append(result, v)
   159  		case SourceDirectoryStanza:
   160  			v.Stanzas = Bridge(v.Stanzas, devices)
   161  			result = append(result, v)
   162  		default:
   163  			result = append(result, v)
   164  		}
   165  	}
   166  
   167  	for _, iface := range ifacesToBridge {
   168  		bridgeName := devices[iface.DeviceName]
   169  		// skip it if we already have a bridge of this name
   170  		if _, ok := bridges[bridgeName]; ok {
   171  			continue
   172  		}
   173  
   174  		// If there was a `auto $DEVICE` stanza make sure we
   175  		// create the complementary auto stanza for the bridge
   176  		// device.
   177  		if autoStanzaSet[iface.DeviceName] {
   178  			if !autoStanzaSet[bridgeName] {
   179  				autoStanzaSet[bridgeName] = true
   180  				result = append(result, *newAutoStanza(bridgeName))
   181  			}
   182  		}
   183  		if isBridgeable(&iface) && !iface.IsAlias {
   184  			result = append(result, *bridgeInterface(bridgeName, iface))
   185  		}
   186  	}
   187  
   188  	return result
   189  }