go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/os.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package resources
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/cockroachdb/errors"
    13  	"github.com/rs/zerolog/log"
    14  	"go.mondoo.com/cnquery/llx"
    15  	"go.mondoo.com/cnquery/providers-sdk/v1/inventory"
    16  	"go.mondoo.com/cnquery/providers/os/connection"
    17  	"go.mondoo.com/cnquery/providers/os/connection/shared"
    18  	"go.mondoo.com/cnquery/providers/os/id/hostname"
    19  	"go.mondoo.com/cnquery/providers/os/id/platformid"
    20  	"go.mondoo.com/cnquery/providers/os/resources/reboot"
    21  	"go.mondoo.com/cnquery/providers/os/resources/systemd"
    22  	"go.mondoo.com/cnquery/providers/os/resources/updates"
    23  	"go.mondoo.com/cnquery/providers/os/resources/uptime"
    24  	"go.mondoo.com/cnquery/providers/os/resources/windows"
    25  )
    26  
    27  func (p *mqlOs) rebootpending() (bool, error) {
    28  	switch p.MqlRuntime.Connection.(type) {
    29  	case *connection.DockerSnapshotConnection:
    30  		return false, nil
    31  	case *connection.TarConnection:
    32  		return false, nil
    33  	}
    34  
    35  	// check photon
    36  	conn := p.MqlRuntime.Connection.(shared.Connection)
    37  	asset := conn.Asset()
    38  
    39  	if asset.Platform.Name == "photon" {
    40  		// get installed kernel and check if the found one is running
    41  		k, err := CreateResource(p.MqlRuntime, "kernel", map[string]*llx.RawData{})
    42  		if err != nil {
    43  			return false, err
    44  		}
    45  		kernel := k.(*mqlKernel)
    46  
    47  		kernelInstalled := kernel.GetInstalled()
    48  		if kernelInstalled.Error != nil {
    49  			return false, kernelInstalled.Error
    50  		}
    51  
    52  		kernels := []KernelVersion{}
    53  		data, err := json.Marshal(kernelInstalled)
    54  		if err != nil {
    55  			return false, err
    56  		}
    57  		err = json.Unmarshal([]byte(data), &kernels)
    58  		if err != nil {
    59  			return false, err
    60  		}
    61  
    62  		// we should only have one kernel here
    63  		if len(kernels) != 1 {
    64  			return false, errors.New("unexpected kernel list result for photon os")
    65  		}
    66  
    67  		return !kernels[0].Running, nil
    68  	}
    69  
    70  	// TODO: move more logic into MQL to leverage its cache
    71  	// try to collect if a reboot is required, fails for static images
    72  	rb, err := reboot.New(conn)
    73  	if err != nil {
    74  		return false, err
    75  	}
    76  	return rb.RebootPending()
    77  }
    78  
    79  func (p *mqlOs) getUnixEnv() (map[string]interface{}, error) {
    80  	rawCmd, err := CreateResource(p.MqlRuntime, "command", map[string]*llx.RawData{
    81  		"command": llx.StringData("env"),
    82  	})
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	cmd := rawCmd.(*mqlCommand)
    87  
    88  	out := cmd.GetStdout()
    89  	if out.Error != nil {
    90  		return nil, out.Error
    91  	}
    92  
    93  	res := map[string]interface{}{}
    94  	lines := strings.Split(out.Data, "\n")
    95  	for i := range lines {
    96  		parts := strings.SplitN(lines[i], "=", 2)
    97  		if len(parts) != 2 {
    98  			continue
    99  		}
   100  		res[parts[0]] = parts[1]
   101  	}
   102  
   103  	return res, nil
   104  }
   105  
   106  func (p *mqlOs) getWindowsEnv() (map[string]interface{}, error) {
   107  	rawCmd, err := CreateResource(p.MqlRuntime, "powershell", map[string]*llx.RawData{
   108  		"script": llx.StringData("Get-ChildItem Env:* | ConvertTo-Json"),
   109  	})
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	cmd := rawCmd.(*mqlPowershell)
   114  
   115  	out := cmd.GetStdout()
   116  	if out.Error != nil {
   117  		return nil, out.Error
   118  	}
   119  
   120  	return windows.ParseEnv(strings.NewReader(out.Data))
   121  }
   122  
   123  func (p *mqlOs) env() (map[string]interface{}, error) {
   124  	conn := p.MqlRuntime.Connection.(shared.Connection)
   125  	platform := conn.Asset().Platform
   126  
   127  	if platform.IsFamily("windows") {
   128  		return p.getWindowsEnv()
   129  	}
   130  	return p.getUnixEnv()
   131  }
   132  
   133  func (p *mqlOs) path(env map[string]interface{}) ([]interface{}, error) {
   134  	rawPath, ok := env["PATH"]
   135  	if !ok {
   136  		return []interface{}{}, nil
   137  	}
   138  
   139  	path := rawPath.(string)
   140  	parts := strings.Split(path, ":")
   141  	res := make([]interface{}, len(parts))
   142  	for i := range parts {
   143  		res[i] = parts[i]
   144  	}
   145  
   146  	return res, nil
   147  }
   148  
   149  func MqlTime(t time.Time) *time.Time {
   150  	return &t
   151  }
   152  
   153  // returns uptime in nanoseconds
   154  func (p *mqlOs) uptime() (*time.Time, error) {
   155  	uptime, err := uptime.New(p.MqlRuntime.Connection.(shared.Connection))
   156  	if err != nil {
   157  		return MqlTime(llx.DurationToTime(0)), err
   158  	}
   159  
   160  	t, err := uptime.Duration()
   161  	if err != nil {
   162  		return MqlTime(llx.DurationToTime(0)), err
   163  	}
   164  
   165  	// we get nano seconds but duration to time only takes seconds
   166  	bootTime := time.Now().Add(-t)
   167  	up := time.Now().Unix() - bootTime.Unix()
   168  	return MqlTime(llx.DurationToTime(up)), nil
   169  }
   170  
   171  func (p *mqlOsUpdate) id() (string, error) {
   172  	return p.Name.Data, nil
   173  }
   174  
   175  func (p *mqlOs) updates() ([]interface{}, error) {
   176  	// find suitable system updates
   177  	conn := p.MqlRuntime.Connection.(shared.Connection)
   178  	um, err := updates.ResolveSystemUpdateManager(conn)
   179  	if um == nil || err != nil {
   180  		return nil, fmt.Errorf("could not detect suitable update manager for platform")
   181  	}
   182  
   183  	// retrieve all system updates
   184  	updates, err := um.List()
   185  	if err != nil {
   186  		return nil, fmt.Errorf("could not retrieve updates list for platform")
   187  	}
   188  
   189  	// create MQL update resources for each update
   190  	osupdates := make([]interface{}, len(updates))
   191  	log.Debug().Int("updates", len(updates)).Msg("mql[updates]> found system updates")
   192  	for i, update := range updates {
   193  
   194  		o, err := CreateResource(p.MqlRuntime, "os.update", map[string]*llx.RawData{
   195  			"name":     llx.StringData(update.Name),
   196  			"severity": llx.StringData(update.Severity),
   197  			"category": llx.StringData(update.Category),
   198  			"restart":  llx.BoolData(update.Restart),
   199  			"format":   llx.StringData(update.Format),
   200  		})
   201  		if err != nil {
   202  			return nil, err
   203  		}
   204  
   205  		osupdates[i] = o.(*mqlOsUpdate)
   206  	}
   207  
   208  	// return the packages as new entries
   209  	return osupdates, nil
   210  }
   211  
   212  func (s *mqlOs) hostname() (string, error) {
   213  	conn := s.MqlRuntime.Connection.(shared.Connection)
   214  	platform := conn.Asset().Platform
   215  
   216  	if res, ok := hostname.Hostname(conn, platform); ok {
   217  		return res, nil
   218  	}
   219  	return "", errors.New("cannot determine hostname")
   220  }
   221  
   222  func (p *mqlOs) name() (string, error) {
   223  	conn := p.MqlRuntime.Connection.(shared.Connection)
   224  	platform := conn.Asset().Platform
   225  
   226  	if !platform.IsFamily(inventory.FAMILY_UNIX) && !platform.IsFamily(inventory.FAMILY_WINDOWS) {
   227  		return "", errors.New("your platform is not supported by operating system resource")
   228  	}
   229  
   230  	if platform.IsFamily(inventory.FAMILY_LINUX) {
   231  		lf, err := CreateResource(p.MqlRuntime, "file", map[string]*llx.RawData{
   232  			"path": llx.StringData("/etc/machine-info"),
   233  		})
   234  		if err != nil {
   235  			return "", err
   236  		}
   237  		file := lf.(*mqlFile)
   238  
   239  		exists := file.GetExists()
   240  		if exists.Error != nil {
   241  			return "", exists.Error
   242  		}
   243  		// if the file does not exist, the pretty hostname is just empty
   244  		// fallback to hostname
   245  		if !exists.Data {
   246  			hn := p.GetHostname()
   247  			return hn.Data, hn.Error
   248  		}
   249  
   250  		// gather content
   251  		data := file.GetContent()
   252  		if data.Error != nil {
   253  			return "", data.Error
   254  		}
   255  
   256  		mi, err := systemd.ParseMachineInfo(strings.NewReader(data.Data))
   257  		if err != nil {
   258  			return "", err
   259  		}
   260  
   261  		if mi.PrettyHostname != "" {
   262  			return mi.PrettyHostname, nil
   263  		}
   264  	}
   265  
   266  	// return plain hostname, this also happens for linux if no pretty name was found
   267  	if platform.IsFamily(inventory.FAMILY_UNIX) {
   268  		hn := p.GetHostname()
   269  		return hn.Data, hn.Error
   270  	}
   271  
   272  	if platform.IsFamily(inventory.FAMILY_WINDOWS) {
   273  
   274  		// try to get the computer name from env
   275  		env, err := p.getWindowsEnv()
   276  		if err == nil {
   277  			val, ok := env["COMPUTERNAME"]
   278  			if ok {
   279  				return val.(string), nil
   280  			}
   281  		}
   282  
   283  		// fallback to hostname
   284  		hn := p.GetHostname()
   285  		return hn.Data, hn.Error
   286  	}
   287  
   288  	return "", errors.New("your platform is not supported by operating system resource")
   289  }
   290  
   291  // returns the OS native machine UUID/GUID
   292  func (s *mqlOs) machineid() (string, error) {
   293  	conn := s.MqlRuntime.Connection.(shared.Connection)
   294  	platform := conn.Asset().Platform
   295  
   296  	uuidProvider, err := platformid.MachineIDProvider(conn, platform)
   297  	if err != nil {
   298  		return "", errors.Wrap(err, "cannot determine platform uuid")
   299  	}
   300  
   301  	if uuidProvider == nil {
   302  		return "", errors.New("cannot determine platform uuid")
   303  	}
   304  
   305  	id, err := uuidProvider.ID()
   306  	if err != nil {
   307  		return "", errors.Wrap(err, "cannot determine platform uuid")
   308  	}
   309  
   310  	return id, nil
   311  }
   312  
   313  func (p *mqlOsBase) id() (string, error) {
   314  	conn := p.MqlRuntime.Connection.(shared.Connection)
   315  	asset := conn.Asset()
   316  
   317  	ident := asset.GetMrn()
   318  	if ident == "" {
   319  		ident = strings.Join(asset.GetPlatformIds(), ",")
   320  	}
   321  	return "os.base(" + ident + ")", nil
   322  }
   323  
   324  func (p *mqlOsBase) rebootpending() (bool, error) {
   325  	// it is a container image, a reboot is never required
   326  	switch p.MqlRuntime.Connection.(type) {
   327  	case *connection.DockerSnapshotConnection:
   328  		return false, nil
   329  	case *connection.TarConnection:
   330  		return false, nil
   331  	}
   332  
   333  	// check photon
   334  	conn := p.MqlRuntime.Connection.(shared.Connection)
   335  	platform := conn.Asset().Platform
   336  
   337  	if platform.Name == "photon" {
   338  		// get installed kernel and check if the found one is running
   339  		raw, err := CreateResource(p.MqlRuntime, "kernel", map[string]*llx.RawData{})
   340  		if err != nil {
   341  			return false, err
   342  		}
   343  		kernel := raw.(*mqlKernel)
   344  		installed := kernel.GetInstalled()
   345  		if installed.Error != nil {
   346  			return false, installed.Error
   347  		}
   348  
   349  		kernels := []KernelVersion{}
   350  		data, err := json.Marshal(installed)
   351  		if err != nil {
   352  			return false, err
   353  		}
   354  		err = json.Unmarshal([]byte(data), &kernels)
   355  		if err != nil {
   356  			return false, err
   357  		}
   358  
   359  		// we should only have one kernel here
   360  		if len(kernels) != 1 {
   361  			return false, errors.New("unexpected kernel list result for photon os")
   362  		}
   363  
   364  		return !kernels[0].Running, nil
   365  	}
   366  
   367  	// TODO: move more logic into MQL to leverage its cache
   368  	// try to collect if a reboot is required, fails for static images
   369  	rb, err := reboot.New(conn)
   370  	if err != nil {
   371  		return false, err
   372  	}
   373  	return rb.RebootPending()
   374  }
   375  
   376  func (p *mqlOsBase) getUnixEnv() (map[string]interface{}, error) {
   377  	rawCmd, err := CreateResource(p.MqlRuntime, "command", map[string]*llx.RawData{
   378  		"command": llx.StringData("env"),
   379  	})
   380  	if err != nil {
   381  		return nil, err
   382  	}
   383  	cmd := rawCmd.(*mqlCommand)
   384  
   385  	out := cmd.GetStdout()
   386  	if out.Error != nil {
   387  		return nil, out.Error
   388  	}
   389  
   390  	res := map[string]interface{}{}
   391  	lines := strings.Split(out.Data, "\n")
   392  	for i := range lines {
   393  		parts := strings.SplitN(lines[i], "=", 2)
   394  		if len(parts) != 2 {
   395  			continue
   396  		}
   397  		res[parts[0]] = parts[1]
   398  	}
   399  
   400  	return res, nil
   401  }
   402  
   403  func (p *mqlOsBase) getWindowsEnv() (map[string]interface{}, error) {
   404  	rawCmd, err := CreateResource(p.MqlRuntime, "powershell", map[string]*llx.RawData{
   405  		"script": llx.StringData("Get-ChildItem Env:* | ConvertTo-Json"),
   406  	})
   407  	if err != nil {
   408  		return nil, err
   409  	}
   410  	cmd := rawCmd.(*mqlPowershell)
   411  
   412  	out := cmd.GetStdout()
   413  	if out.Error != nil {
   414  		return nil, out.Error
   415  	}
   416  
   417  	return windows.ParseEnv(strings.NewReader(out.Data))
   418  }
   419  
   420  func (p *mqlOsBase) env() (map[string]interface{}, error) {
   421  	conn := p.MqlRuntime.Connection.(shared.Connection)
   422  	platform := conn.Asset().Platform
   423  
   424  	if platform.IsFamily("windows") {
   425  		return p.getWindowsEnv()
   426  	}
   427  	return p.getUnixEnv()
   428  }
   429  
   430  func (p *mqlOsBase) path(env map[string]interface{}) ([]interface{}, error) {
   431  	rawPath, ok := env["PATH"]
   432  	if !ok {
   433  		return []interface{}{}, nil
   434  	}
   435  
   436  	path := rawPath.(string)
   437  	parts := strings.Split(path, ":")
   438  	res := make([]interface{}, len(parts))
   439  	for i := range parts {
   440  		res[i] = parts[i]
   441  	}
   442  
   443  	return res, nil
   444  }
   445  
   446  // returns uptime in nanoseconds
   447  func (p *mqlOsBase) uptime() (*time.Time, error) {
   448  	conn := p.MqlRuntime.Connection.(shared.Connection)
   449  	uptime, err := uptime.New(conn)
   450  	if err != nil {
   451  		return MqlTime(llx.DurationToTime(0)), err
   452  	}
   453  
   454  	t, err := uptime.Duration()
   455  	if err != nil {
   456  		return MqlTime(llx.DurationToTime(0)), err
   457  	}
   458  
   459  	// we get nano seconds but duration to time only takes seconds
   460  	bootTime := time.Now().Add(-t)
   461  	up := time.Now().Unix() - bootTime.Unix()
   462  	return MqlTime(llx.DurationToTime(up)), nil
   463  }
   464  
   465  func (p *mqlOsBase) updates() ([]interface{}, error) {
   466  	// find suitable system updates
   467  	conn := p.MqlRuntime.Connection.(shared.Connection)
   468  	um, err := updates.ResolveSystemUpdateManager(conn)
   469  	if um == nil || err != nil {
   470  		return nil, fmt.Errorf("could not detect suitable update manager for platform")
   471  	}
   472  
   473  	// retrieve all system updates
   474  	updates, err := um.List()
   475  	if err != nil {
   476  		return nil, fmt.Errorf("could not retrieve updates list for platform")
   477  	}
   478  
   479  	// create MQL update resources for each update
   480  	osupdates := make([]interface{}, len(updates))
   481  	log.Debug().Int("updates", len(updates)).Msg("mql[updates]> found system updates")
   482  	for i, update := range updates {
   483  
   484  		o, err := CreateResource(p.MqlRuntime, "os.update", map[string]*llx.RawData{
   485  			"name":     llx.StringData(update.Name),
   486  			"severity": llx.StringData(update.Severity),
   487  			"category": llx.StringData(update.Category),
   488  			"restart":  llx.BoolData(update.Restart),
   489  			"format":   llx.StringData(update.Format),
   490  		})
   491  		if err != nil {
   492  			return nil, err
   493  		}
   494  
   495  		osupdates[i] = o.(*mqlOsUpdate)
   496  	}
   497  
   498  	// return the packages as new entries
   499  	return osupdates, nil
   500  }
   501  
   502  func (s *mqlOsBase) hostname() (string, error) {
   503  	conn := s.MqlRuntime.Connection.(shared.Connection)
   504  	platform := conn.Asset().Platform
   505  
   506  	if res, ok := hostname.Hostname(conn, platform); ok {
   507  		return res, nil
   508  	}
   509  	return "", errors.New("cannot determine hostname")
   510  }
   511  
   512  func (p *mqlOsBase) name() (string, error) {
   513  	conn := p.MqlRuntime.Connection.(shared.Connection)
   514  	platform := conn.Asset().Platform
   515  
   516  	if !platform.IsFamily(inventory.FAMILY_UNIX) && !platform.IsFamily(inventory.FAMILY_WINDOWS) {
   517  		return "", errors.New("your platform is not supported by operating system resource")
   518  	}
   519  
   520  	if platform.IsFamily(inventory.FAMILY_LINUX) {
   521  		lf, err := CreateResource(p.MqlRuntime, "file", map[string]*llx.RawData{
   522  			"path": llx.StringData("/etc/machine-info"),
   523  		})
   524  		if err != nil {
   525  			return "", err
   526  		}
   527  		file := lf.(*mqlFile)
   528  
   529  		exists := file.GetExists()
   530  		if exists.Error != nil {
   531  			return "", exists.Error
   532  		}
   533  		// if the file does not exist, the pretty hostname is just empty
   534  		if !exists.Data {
   535  			return "", nil
   536  		}
   537  
   538  		// gather content
   539  		data := file.GetContent()
   540  		if data.Error != nil {
   541  			return "", data.Error
   542  		}
   543  
   544  		mi, err := systemd.ParseMachineInfo(strings.NewReader(data.Data))
   545  		if err != nil {
   546  			return "", err
   547  		}
   548  
   549  		if mi.PrettyHostname != "" {
   550  			return mi.PrettyHostname, nil
   551  		}
   552  	}
   553  
   554  	// return plain hostname, this also happens for linux if no pretty name was found
   555  	if platform.IsFamily(inventory.FAMILY_UNIX) {
   556  		if res, ok := hostname.Hostname(conn, platform); ok {
   557  			return res, nil
   558  		}
   559  		return "", errors.New("cannot determine hostname")
   560  	}
   561  
   562  	if platform.IsFamily(inventory.FAMILY_WINDOWS) {
   563  
   564  		// try to get the computer name from env
   565  		env, err := p.getWindowsEnv()
   566  		if err == nil {
   567  			val, ok := env["COMPUTERNAME"]
   568  			if ok {
   569  				return val.(string), nil
   570  			}
   571  		}
   572  
   573  		// fallback to hostname
   574  		if res, ok := hostname.Hostname(conn, platform); ok {
   575  			return res, nil
   576  		}
   577  		return "", errors.New("cannot determine hostname")
   578  	}
   579  
   580  	return "", errors.New("your platform is not supported by operating system resource")
   581  }
   582  
   583  // returns the OS native machine UUID/GUID
   584  func (s *mqlOsBase) machineid() (string, error) {
   585  	conn := s.MqlRuntime.Connection.(shared.Connection)
   586  	platform := conn.Asset().Platform
   587  
   588  	uuidProvider, err := platformid.MachineIDProvider(conn, platform)
   589  	if err != nil {
   590  		return "", errors.Wrap(err, "cannot determine platform uuid")
   591  	}
   592  
   593  	if uuidProvider == nil {
   594  		return "", errors.New("cannot determine platform uuid")
   595  	}
   596  
   597  	id, err := uuidProvider.ID()
   598  	if err != nil {
   599  		return "", errors.Wrap(err, "cannot determine platform uuid")
   600  	}
   601  
   602  	return id, nil
   603  }
   604  
   605  func (s *mqlOsBase) machine() (*mqlMachine, error) {
   606  	res, err := CreateResource(s.MqlRuntime, "machine", map[string]*llx.RawData{})
   607  	if err != nil {
   608  		return nil, err
   609  	}
   610  	return res.(*mqlMachine), nil
   611  }
   612  
   613  func (s *mqlOsBase) groups() (*mqlGroups, error) {
   614  	res, err := CreateResource(s.MqlRuntime, "groups", map[string]*llx.RawData{})
   615  	if err != nil {
   616  		return nil, err
   617  	}
   618  	return res.(*mqlGroups), nil
   619  }
   620  
   621  func (s *mqlOsBase) users() (*mqlUsers, error) {
   622  	res, err := CreateResource(s.MqlRuntime, "users", map[string]*llx.RawData{})
   623  	if err != nil {
   624  		return nil, err
   625  	}
   626  	return res.(*mqlUsers), nil
   627  }
   628  
   629  func (s *mqlOsUnix) id() (string, error) {
   630  	conn := s.MqlRuntime.Connection.(shared.Connection)
   631  	asset := conn.Asset()
   632  
   633  	ident := asset.GetMrn()
   634  	if ident == "" {
   635  		ident = strings.Join(asset.GetPlatformIds(), ",")
   636  	}
   637  	return "os.unix(" + ident + ")", nil
   638  }
   639  
   640  func (s *mqlOsUnix) base() (*mqlOsBase, error) {
   641  	res, err := CreateResource(s.MqlRuntime, "os.base", map[string]*llx.RawData{})
   642  	if err != nil {
   643  		return nil, err
   644  	}
   645  	return res.(*mqlOsBase), nil
   646  }
   647  
   648  func (s *mqlOsLinux) id() (string, error) {
   649  	conn := s.MqlRuntime.Connection.(shared.Connection)
   650  	asset := conn.Asset()
   651  
   652  	ident := asset.GetMrn()
   653  	if ident == "" {
   654  		ident = strings.Join(asset.GetPlatformIds(), ",")
   655  	}
   656  	return "os.linux(" + ident + ")", nil
   657  }
   658  
   659  func (s *mqlOsLinux) unix() (*mqlOsUnix, error) {
   660  	res, err := CreateResource(s.MqlRuntime, "os.unix", map[string]*llx.RawData{})
   661  	if err != nil {
   662  		return nil, err
   663  	}
   664  	return res.(*mqlOsUnix), nil
   665  }
   666  
   667  func (s *mqlOsLinux) iptables() (*mqlIptables, error) {
   668  	res, err := CreateResource(s.MqlRuntime, "iptables", map[string]*llx.RawData{})
   669  	if err != nil {
   670  		return nil, err
   671  	}
   672  	return res.(*mqlIptables), nil
   673  }
   674  
   675  func (s *mqlOsLinux) ip6tables() (*mqlIp6tables, error) {
   676  	res, err := CreateResource(s.MqlRuntime, "ip6tables", map[string]*llx.RawData{})
   677  	if err != nil {
   678  		return nil, err
   679  	}
   680  	return res.(*mqlIp6tables), nil
   681  }