Skip to content

Commit

Permalink
Merge pull request #2 from yseto/ip-address
Browse files Browse the repository at this point in the history
capture Ip address on interfaces.
  • Loading branch information
yseto committed Mar 22, 2023
2 parents 63f458d + 84fcefb commit a09b78c
Show file tree
Hide file tree
Showing 11 changed files with 324 additions and 18 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ mackerel: # (オプション)Mackerel に送信する時のパラメータ
name: "" # (オプション)Mackerel に登録するホスト名
x-api-key: xxxxx # (必須) Mackerel の APIキー
host-id: xxxxx # (オプション) Mackerel でのホストID、無指定時の場合、プログラム内で自動的に取得し、設定ファイルを更新します。
ignore-network-info: false # (オプション) true時、mackerel へインターフェイスに紐づくIPアドレス、MACアドレスの情報を送信しません。
```
## v0.0.1 からの移行
Expand Down
46 changes: 46 additions & 0 deletions collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type snmpClientImpl interface {
BulkWalk(oid string, length uint64) (map[uint64]uint64, error)
BulkWalkGetInterfaceName(length uint64) (map[uint64]string, error)
BulkWalkGetInterfaceState(length uint64) (map[uint64]bool, error)
BulkWalkGetInterfaceIPAddress() (map[uint64][]string, error)
BulkWalkGetInterfacePhysAddress(length uint64) (map[uint64]string, error)
Close() error
GetInterfaceNumber() (uint64, error)
}
Expand Down Expand Up @@ -74,3 +76,47 @@ func do(ctx context.Context, snmpClient snmpClientImpl, c *config.Config) ([]Met
}
return metrics, nil
}

func DoInterfaceIPAddress(ctx context.Context, c *config.Config) ([]Interface, error) {
snmpClient, err := snmp.Init(ctx, c.Target, c.Community)
if err != nil {
return nil, err
}
defer snmpClient.Close()
return doInterfaceIPAddress(ctx, snmpClient, c)
}

func doInterfaceIPAddress(ctx context.Context, snmpClient snmpClientImpl, c *config.Config) ([]Interface, error) {
ifNumber, err := snmpClient.GetInterfaceNumber()
if err != nil {
return nil, err
}
ifDescr, err := snmpClient.BulkWalkGetInterfaceName(ifNumber)
if err != nil {
return nil, err
}

ifIndexIP, err := snmpClient.BulkWalkGetInterfaceIPAddress()
if err != nil {
return nil, err
}

ifPhysAddress, err := snmpClient.BulkWalkGetInterfacePhysAddress(ifNumber)
if err != nil {
return nil, err
}

var interfaces []Interface
for ifIndex, ip := range ifIndexIP {
if name, ok := ifDescr[ifIndex]; ok {
phy := ifPhysAddress[ifIndex]
interfaces = append(interfaces, Interface{
IfName: name,
IpAddress: ip,
MacAddress: phy,
})
}
}

return interfaces, nil
}
56 changes: 55 additions & 1 deletion collector/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,23 @@ func (m *mockSnmpClient) GetInterfaceNumber() (uint64, error) {
return 4, nil
}

func (m *mockSnmpClient) BulkWalkGetInterfaceIPAddress() (map[uint64][]string, error) {
return map[uint64][]string{
1: {"127.0.0.1"},
2: {"192.0.2.1"},
3: {"192.0.2.2", "192.0.2.3"},
4: {"198.51.100.1"},
5: {"198.51.100.2"},
}, nil
}
func (m *mockSnmpClient) BulkWalkGetInterfacePhysAddress(length uint64) (map[uint64]string, error) {
return map[uint64]string{
2: "00:00:87:12:34:56",
3: "00:00:4C:23:45:67",
4: "00:00:0E:34:56:78",
}, nil
}

func TestDo(t *testing.T) {
ctx := context.Background()

Expand Down Expand Up @@ -135,7 +152,7 @@ func TestDo(t *testing.T) {
}
})

t.Run("non skip", func(t *testing.T) {
t.Run("skip down-linkstate", func(t *testing.T) {
c := &config.Config{
MIBs: []string{"ifHCInOctets", "ifHCOutOctets"},
SkipDownLinkState: true,
Expand All @@ -162,3 +179,40 @@ func TestDo(t *testing.T) {
})

}

func TestDoInterfaceIPAddress(t *testing.T) {
ctx := context.Background()
c := &config.Config{}
actual, err := doInterfaceIPAddress(ctx, &mockSnmpClient{}, c)
if err != nil {
t.Error("invalid raised error")
}
expected := []Interface{
{
IfName: "eth0",
IpAddress: []string{"192.0.2.1"},
MacAddress: "00:00:87:12:34:56",
},
{
IfName: "eth1",
IpAddress: []string{"192.0.2.2", "192.0.2.3"},
MacAddress: "00:00:4C:23:45:67",
},
{
IfName: "eth2",
IpAddress: []string{"198.51.100.1"},
MacAddress: "00:00:0E:34:56:78",
},
{
IfName: "lo0",
IpAddress: []string{"127.0.0.1"},
},
}
if d := cmp.Diff(
actual,
expected,
cmpopts.SortSlices(func(i, j Interface) bool { return i.IfName < j.IfName }),
); d != "" {
t.Errorf("invalid result %s", d)
}
}
6 changes: 6 additions & 0 deletions collector/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ type MetricsDutum struct {
func (m *MetricsDutum) String() string {
return fmt.Sprintf("%d\t%s\t%s\t%d", m.IfIndex, m.IfName, m.Mib, m.Value)
}

type Interface struct {
IfName string
IpAddress []string
MacAddress string
}
1 change: 1 addition & 0 deletions config.yaml.sample
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ mackerel:
x-api-key: xxxxx
host-id: xxxxx
name: "" # display Name on Mackerel
ignore-network-info: false
7 changes: 4 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ type Interface struct {
}

type Mackerel struct {
HostID string `yaml:"host-id"`
ApiKey string `yaml:"x-api-key"`
Name string `yaml:"name,omitempty"`
HostID string `yaml:"host-id"`
ApiKey string `yaml:"x-api-key"`
Name string `yaml:"name,omitempty"`
IgnoreNetworkInfo bool `yaml:"ignore-network-info,omitempty"`
}

type Config struct {
Expand Down
24 changes: 18 additions & 6 deletions mackerel/mackerel.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,26 @@ func NewQueue(qa *QueueArg) *Queue {
}

// return host ID when create.
func (q *Queue) Init() (*string, error) {
func (q *Queue) Init(ifs []collector.Interface) (*string, error) {
log.Println("init queue")

interfaces := []mackerel.Interface{
{
Name: "main",
IPv4Addresses: []string{q.targetAddr},
},
var interfaces []mackerel.Interface

if len(ifs) == 0 {
interfaces = []mackerel.Interface{
{
Name: "main",
IPv4Addresses: []string{q.targetAddr},
},
}
} else {
for i := range ifs {
interfaces = append(interfaces, mackerel.Interface{
Name: ifs[i].IfName,
IPv4Addresses: ifs[i].IpAddress,
MacAddress: ifs[i].MacAddress,
})
}
}

var newHostID *string
Expand Down
41 changes: 40 additions & 1 deletion mackerel/mackerel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"testing"

"github.com/mackerelio/mackerel-client-go"

"github.com/yseto/switch-traffic-to-mackerel/collector"
)

type mackerelClientMock struct {
Expand Down Expand Up @@ -71,6 +73,7 @@ func TestInit(t *testing.T) {
returnHostID *string
queue *Queue
mock *mackerelClientMock
interfaces []collector.Interface
}{
{
name: "create host when hostID is empty",
Expand Down Expand Up @@ -142,12 +145,48 @@ func TestInit(t *testing.T) {
},
expectedGraphDef: graphDefs,
},
{
name: "[]collector.interface is exist",
expectedCreateParam: mackerel.CreateHostParam{
Name: "hostname",
Interfaces: []mackerel.Interface{
{
Name: "eth0",
IPv4Addresses: []string{"192.0.2.1", "192.0.2.2"},
},
{
Name: "eth1",
IPv4Addresses: []string{"192.0.2.3"},
},
},
},
queue: &Queue{
buffers: list.New(),
name: "hostname",
targetAddr: "192.0.2.1",
},
returnHostID: &id,
mock: &mackerelClientMock{
returnHostID: "1234567890",
},
expectedGraphDef: graphDefs,
interfaces: []collector.Interface{
{
IfName: "eth0",
IpAddress: []string{"192.0.2.1", "192.0.2.2"},
},
{
IfName: "eth1",
IpAddress: []string{"192.0.2.3"},
},
},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tc.queue.client = tc.mock
newHostID, err := tc.queue.Init()
newHostID, err := tc.queue.Init(tc.interfaces)
if !errors.Is(err, tc.expectedError) {
t.Error("invalid error")
}
Expand Down
11 changes: 10 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,16 @@ func main() {
return
}

newHostID, err := queue.Init()
var interfaces []collector.Interface
if !c.Mackerel.IgnoreNetworkInfo {
interfaces, err = collector.DoInterfaceIPAddress(ctx, c)
if err != nil {
log.Println("HINT: try mackerel > ignore-network-info: true")
log.Fatal(err)
}
}

newHostID, err := queue.Init(interfaces)
if err != nil {
log.Fatal(err)
}
Expand Down
75 changes: 69 additions & 6 deletions snmp/snmp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@ package snmp
import (
"context"
"errors"
"fmt"
"net"
"strconv"
"strings"

"github.com/gosnmp/gosnmp"
)

const (
MIBifNumber = "1.3.6.1.2.1.2.1.0"
MIBifDescr = "1.3.6.1.2.1.2.2.1.2"
MIBifOperStatus = "1.3.6.1.2.1.2.2.1.8"
MIBifNumber = "1.3.6.1.2.1.2.1.0"
MIBifDescr = "1.3.6.1.2.1.2.2.1.2"
MIBifPhysAddress = "1.3.6.1.2.1.2.2.1.6"
MIBifOperStatus = "1.3.6.1.2.1.2.2.1.8"
MIBipAdEntIfIndex = "1.3.6.1.2.1.4.20.1.2"
)

type SNMP struct {
Expand All @@ -33,9 +37,10 @@ func (s *SNMP) Close() error {
}

var (
errGetInterfaceNumber = errors.New("cant get interface number")
errParseInterfaceName = errors.New("cant parse interface name")
errParseError = errors.New("cant parse value.")
errGetInterfaceNumber = errors.New("cant get interface number")
errParseInterfaceName = errors.New("cant parse interface name")
errParseInterfacePhyAddress = errors.New("cant parse phy address")
errParseError = errors.New("cant parse value.")
)

func (s *SNMP) GetInterfaceNumber() (uint64, error) {
Expand Down Expand Up @@ -129,3 +134,61 @@ func captureIfIndex(name string) (uint64, error) {
sl := strings.Split(name, ".")
return strconv.ParseUint(sl[len(sl)-1], 10, 64)
}

func (s *SNMP) BulkWalkGetInterfaceIPAddress() (map[uint64][]string, error) {
kv := make(map[uint64][]string)
err := s.handler.BulkWalk(MIBipAdEntIfIndex, func(pdu gosnmp.SnmpPDU) error {
ipAddress := strings.Replace(pdu.Name, MIBipAdEntIfIndex, "", 1)
ipAddress = strings.TrimLeft(ipAddress, ".")

ip := net.ParseIP(ipAddress)
if ip == nil {
return nil
}
if ip.IsLoopback() {
return nil
}

switch pdu.Type {
case gosnmp.OctetString:
return errParseError
default:
ifIndex := gosnmp.ToBigInt(pdu.Value).Uint64()
kv[ifIndex] = append(kv[ifIndex], ipAddress)
}
return nil
})
if err != nil {
return nil, err
}
return kv, nil
}

func (s *SNMP) BulkWalkGetInterfacePhysAddress(length uint64) (map[uint64]string, error) {
kv := make(map[uint64]string, length)
err := s.handler.BulkWalk(MIBifPhysAddress, func(pdu gosnmp.SnmpPDU) error {
index, err := captureIfIndex(pdu.Name)
if err != nil {
return err
}
switch pdu.Type {
case gosnmp.OctetString:
value, ok := pdu.Value.([]byte)
if !ok {
return errParseInterfacePhyAddress
}
var parts []string
for _, i := range value {
parts = append(parts, fmt.Sprintf("%02x", i))
}
kv[index] = strings.Join(parts, ":")
default:
return errParseInterfacePhyAddress
}
return nil
})
if err != nil {
return nil, err
}
return kv, nil
}
Loading

0 comments on commit a09b78c

Please sign in to comment.