Juniper/go-netconfでvJunos-Switchを制御してみよう

## はじめに

NETCONFに対応したNW装置に、自作のGoプログラムから設定を入れたいと思うこともありますよね？
今回は `github.com/Juniper/go-netconf/netconf` を使用して、Go言語でのNETCONFによるサンプルコードを作成し、vJunos-Switchを制御する実現性と出力を確認します。

## 環境

* vJunos-Switch
  * mgmt IP: 10.100.2.94
  * ssh username: qiita
  * ssh password: qiita-password
  * NETCONFが可能なように設定済み
* 上記にアクセス可能な適当な開発端末

vJunos Switchは以下を参考に各自で構築するか、余っているNW装置をご利用ください。

* [Proxmox VEでvJunos-switchを動かしてみる - Qiita](https://qiita.com/ymbk990/items/07ab914240a91e3949c4)
* [Proxmox VEとcloud-initを使って初期設定済みのvJunos-switchを起動する - Qiita](https://qiita.com/ymbk990/items/1d5971d6c82e737d60c6)

:::note info
Go言語の開発環境に関しての話は省略しています
:::

## サンプルコード

まずは簡単にNETCONFでインターフェース `ge-0/0/0` のMTUを1400に変更するコードを書きます。
1行1行のコード解説は省略しますが、大まかな処理内容は以下の通りです。

1. SSH/NETCONFでNW装置に接続
2. 意図しない設定の混入を防ぐため、作業中の変更を破棄
3. 占有状態で設定モードに移行
4. ge-0/0/0のMTU変更の設定を追加
5. commit前の差分確認
6. commit

NW装置に慣れた人であれば `set interfaces ge-0/0/0 mtu 1400` をコミットするだけのコードだと説明したら伝わるでしょうか。そう考えるとプログラムとは長いものですね。

```go
package main

import (
	"fmt"
	"log/slog"

	"github.com/Juniper/go-netconf/netconf"
	"golang.org/x/crypto/ssh"
)

const (
	rpcCommit              = "<commit/>"
	rpcDiscard             = "<discard-changes/>"
	rpcGetCandidateCompare = "<get-configuration compare=\"rollback\" rollback=\"%d\" format=\"text\"/>"
	rpcConfigLoadText      = "<load-configuration action=\"merge\" format=\"text\"><configuration-text>%s</configuration-text></load-configuration>"
)

const sampleConfig = `interfaces {
    ge-0/0/0 {
        mtu 1400;
    }
}`

func Configure(host string, user string, password string) error {
	// Open SSH connection
	s, err := netconf.DialSSH(host, &ssh.ClientConfig{
		User:            user,
		Auth:            []ssh.AuthMethod{ssh.Password(password)},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	})
	if err != nil {
		return fmt.Errorf("failed DialSSH: %w", err)
	}
	defer s.Close()

	preCmds := []netconf.RawMethod{
		netconf.RawMethod(rpcDiscard),
		// Lock
		// If already locked other process, returned &netconf.RPCError{..., Tag:"lock-denied", ...}
		netconf.MethodLock("candidate"),
	}
	configCmds := []netconf.RawMethod{
		netconf.RawMethod(fmt.Sprintf(rpcConfigLoadText, sampleConfig)),
	}
	postCmds := []netconf.RawMethod{
		// Compare config. Get candidate compare (show | compare)
		netconf.RawMethod(fmt.Sprintf(rpcGetCandidateCompare, 0)),
		// Commit
		netconf.RawMethod(rpcCommit),
		// Unlock
		netconf.MethodUnlock("candidate"),
	}
	// preCmds, configCmds, postCmds
	cmds := append(preCmds, configCmds...)
	cmds = append(cmds, postCmds...)
	for _, cmd := range cmds {
		r, err := s.Exec(cmd)
		if err != nil {
			// If delete non-existing config tree, returned &netconf.RPCError{..., Tag:"data-missing", ...}
			return fmt.Errorf("failed rpc command: %s: %w", cmd, err)
		}
		slog.Info(fmt.Sprintf("Request: %s\nReply: %+v", cmd, r))
	}
	return nil
}

func main() {
	err := Configure("10.100.2.94", "qiita", "qiita-password")
	if err != nil {
		slog.Error(fmt.Sprintf("%s", err))
	}
}
```

先のコードを `main.go` として保存してビルドします。（出力は省略）

```
$ go mod init example.com/qiita/go-netconf-unit-test
$ go mod tidy
$ go build
```

フォルダ名が `go-netconf-unit-test` なので、出力されるビルド結果も `go-netconf-unit-test` になります。

## 最低限の動作確認

### 設定前の状態確認

まずは元々のインターフェース設定を確認します。  
ge-0/0/0に特に設定は入っておらず、MTUは `Link-level type: Ethernet, MTU: 1514` ですね。

```
qiita@vjunos-03> show configuration interfaces    
fxp0 {
    unit 0 {
        family inet {
            address 10.100.2.94/22;
        }
    }
}

qiita@vjunos-03> show interfaces ge-0/0/0         
Physical interface: ge-0/0/0, Enabled, Physical link is Up
  Interface index: 141, SNMP ifIndex: 519
  Link-level type: Ethernet, MTU: 1514, LAN-PHY mode, Speed: 1000mbps, BPDU Error: None, Loop Detect PDU Error: None,
  Ethernet-Switching Error: None, MAC-REWRITE Error: None, Loopback: Disabled, Source filtering: Disabled, Flow control: Enabled,
  Auto-negotiation: Enabled, Remote fault: Online, Media type: Fiber
  Device flags   : Present Running
  Interface Specific flags: Internal: 0x101200
  Interface flags: SNMP-Traps Internal: 0x4000
  Link flags     : None
  CoS queues     : 8 supported, 8 maximum usable queues
  Current address: 16:29:3e:a6:56:04, Hardware address: 16:29:3e:a6:56:04
  Last flapped   : 2024-06-06 06:20:12 UTC (31w1d 03:21 ago)
  Input rate     : 0 bps (0 pps)
  Output rate    : 664 bps (0 pps)
  Active alarms  : None
  Active defects : None
  PCS statistics                      Seconds
    Bit errors                             0
    Errored blocks                         0
  Ethernet FEC statistics              Errors
    FEC Corrected Errors                    0
    FEC Uncorrected Errors                  0
    FEC Corrected Errors Rate               0
    FEC Uncorrected Errors Rate             0
  Interface transmit statistics: Disabled

  Logical interface ge-0/0/0.16386 (Index 346) (SNMP ifIndex 529)
    Flags: Up SNMP-Traps 0x4004000 Encapsulation: ENET2
    Input packets : 0
    Output packets: 2

```

### 実行

では先ほどビルドした `go-netconf-unit-test` を実行してみましょう。
リクエストとレスポンスのXMLデータをログに出力しているため、沢山の出力が確認されます。
人の目で見るようなものではありませんが、configuration-textで入力したい設定、configuration-outputで期待する差分（入力した設定が入っている、入力していない設定が入っていないこと）が確認できます。

```xml
$ ./go-netconf-unit-test 
2025/01/31 17:54:23 INFO Request: <discard-changes/>
Reply: &{XMLName:{Space:urn:ietf:params:xml:ns:netconf:base:1.0 Local:rpc-reply} Errors:[] Data:
<ok/>
 Ok:false RawReply:<rpc-reply xmlns:junos="http://xml.juniper.net/junos/23.1R0/junos" message-id="0175ff5c-2688-4c2d-86e9-d68bd87ea856" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<ok/>
</rpc-reply>
 MessageID:0175ff5c-2688-4c2d-86e9-d68bd87ea856}
2025/01/31 17:54:23 INFO Request: <lock><target><candidate/></target></lock>
Reply: &{XMLName:{Space:urn:ietf:params:xml:ns:netconf:base:1.0 Local:rpc-reply} Errors:[] Data:
<ok/>
 Ok:false RawReply:<rpc-reply xmlns:junos="http://xml.juniper.net/junos/23.1R0/junos" message-id="936054e0-8363-436b-97da-29dc40b14dbf" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<ok/>
</rpc-reply>
 MessageID:936054e0-8363-436b-97da-29dc40b14dbf}
2025/01/31 17:54:23 INFO Request: <load-configuration action="merge" format="text"><configuration-text>interfaces {
    ge-0/0/0 {
        mtu 1400;
    }
}</configuration-text></load-configuration>
Reply: &{XMLName:{Space:urn:ietf:params:xml:ns:netconf:base:1.0 Local:rpc-reply} Errors:[] Data:
<load-configuration-results>
<ok/>
</load-configuration-results>
 Ok:false RawReply:<rpc-reply xmlns:junos="http://xml.juniper.net/junos/23.1R0/junos" message-id="8f12d07f-fb0e-4103-aaf2-2394d307b1b5" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<load-configuration-results>
<ok/>
</load-configuration-results>
</rpc-reply>
 MessageID:8f12d07f-fb0e-4103-aaf2-2394d307b1b5}
2025/01/31 17:54:23 INFO Request: <get-configuration compare="rollback" rollback="0" format="text"/>
Reply: &{XMLName:{Space:urn:ietf:params:xml:ns:netconf:base:1.0 Local:rpc-reply} Errors:[] Data:
<configuration-information>
<configuration-output>
[edit interfaces]
+   ge-0/0/0 {
+       mtu 1400;
+   }
</configuration-output>
</configuration-information>
 Ok:false RawReply:<rpc-reply xmlns:junos="http://xml.juniper.net/junos/23.1R0/junos" message-id="c036dce7-1865-4df2-b7a1-92ec2c314f79" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<configuration-information>
<configuration-output>
[edit interfaces]
+   ge-0/0/0 {
+       mtu 1400;
+   }
</configuration-output>
</configuration-information>
</rpc-reply>
 MessageID:c036dce7-1865-4df2-b7a1-92ec2c314f79}
2025/01/31 17:54:25 INFO Request: <commit/>
Reply: &{XMLName:{Space:urn:ietf:params:xml:ns:netconf:base:1.0 Local:rpc-reply} Errors:[] Data:
<ok/>
 Ok:false RawReply:<rpc-reply xmlns:junos="http://xml.juniper.net/junos/23.1R0/junos" message-id="068cd57f-871b-47d1-920a-8acf4b2d1b6b" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<ok/>
</rpc-reply>
 MessageID:068cd57f-871b-47d1-920a-8acf4b2d1b6b}
2025/01/31 17:54:25 INFO Request: <unlock><target><candidate/></target></unlock>
Reply: &{XMLName:{Space:urn:ietf:params:xml:ns:netconf:base:1.0 Local:rpc-reply} Errors:[] Data:
<ok/>
 Ok:false RawReply:<rpc-reply xmlns:junos="http://xml.juniper.net/junos/23.1R0/junos" message-id="18ce27ad-553d-46d1-b279-76b95a2adb6b" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<ok/>
</rpc-reply>
 MessageID:18ce27ad-553d-46d1-b279-76b95a2adb6b}
```

### 設定後の状態確認

設定後のインターフェース状態を確認します。
ge-0/0/0に設定が入り、MTUが `Link-level type: Ethernet, MTU: 1400` になりました。

```
qiita@vjunos-03> show configuration interfaces 
ge-0/0/0 {
    mtu 1400;
}
fxp0 {
    unit 0 {
        family inet {
            address 10.100.2.94/22;
        }
    }
}

qiita@vjunos-03> show interfaces ge-0/0/0   
Physical interface: ge-0/0/0, Enabled, Physical link is Up
  Interface index: 141, SNMP ifIndex: 519
  Link-level type: Ethernet, MTU: 1400, LAN-PHY mode, Speed: 1000mbps, BPDU Error: None, Loop Detect PDU Error: None,
  Ethernet-Switching Error: None, MAC-REWRITE Error: None, Loopback: Disabled, Source filtering: Disabled, Flow control: Enabled,
  Auto-negotiation: Enabled, Remote fault: Online, Media type: Fiber
  Device flags   : Present Running
  Interface Specific flags: Internal: 0x101200
  Interface flags: SNMP-Traps Internal: 0x4000
  Link flags     : None
  CoS queues     : 8 supported, 8 maximum usable queues
  Current address: 16:29:3e:a6:56:04, Hardware address: 16:29:3e:a6:56:04
  Last flapped   : 2024-06-06 06:20:12 UTC (31w1d 03:26 ago)
  Input rate     : 0 bps (0 pps)
  Output rate    : 0 bps (0 pps)
  Active alarms  : None
  Active defects : None
  PCS statistics                      Seconds
    Bit errors                             0
    Errored blocks                         0
  Ethernet FEC statistics              Errors
    FEC Corrected Errors                    0
    FEC Uncorrected Errors                  0
    FEC Corrected Errors Rate               0
    FEC Uncorrected Errors Rate             0
  Interface transmit statistics: Disabled
```

これで、状況に合わせて投入したい設定を変更するだけで、色々な設定に応用出来そうなことが確認できました。

## ロックされていて設定に入れないケースの確認

それでは、ついでにエラーになるケースも見てみましょう。
例えば、誰かがメンテナンス作業中で設定モードをロックしている場合です。

```
qiita@vjunos-03> configure exclusive    
warning: uncommitted changes will be discarded on exit
Entering configuration mode
```

この時にプログラムが動作すると、以下のようなエラーが確認できます。

```
$ ./go-netconf-unit-test 
2025/01/31 18:03:08 ERROR failed rpc command: <discard-changes/>: netconf rpc [error] '
configuration database locked by:
  esi terminal pts/0 (pid 18946) on since 2025-01-31 09:03:43 UTC
      exclusive [edit]
'
```

メッセージは非常に分かりやすいですね。
`configuration database locked` なので設定モードに入る前の `<discard-changes/>` で失敗していることが分かります。
しかし、出力がXMLではないせいか、この例ではあまり詳細な情報が分かりません。

## 間違った設定を入れようとしたケースの確認

では、投入用の設定が間違っている場合はどうでしょうか？
例えば、以下のようにMTUサイズを範囲外に指定してしまったとします。

```diff
@@ -17,7 +17,7 @@
 
 const sampleConfig = `interfaces {
     ge-0/0/0 {
-        mtu 1;
+        mtu 1400;
     }
 }`
 
```

Goのプログラムとしては特に間違っていませんから、コンパイルは通ってしまいます。
再度実行してみると、何やら処理は行われているようですが、先ほどは無かった `<rpc-error>` が出力されています。
しかも、そのままcommitまでされてしまいました！

```xml
$ ./go-netconf-unit-test 
2025/01/31 18:51:21 INFO Request: <discard-changes/>
Reply: &{XMLName:{Space:urn:ietf:params:xml:ns:netconf:base:1.0 Local:rpc-reply} Errors:[] Data:
<ok/>
 Ok:false RawReply:<rpc-reply xmlns:junos="http://xml.juniper.net/junos/23.1R0/junos" message-id="f7a77ee6-c47f-424a-aa1a-0c80bcf9e102" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<ok/>
</rpc-reply>
 MessageID:f7a77ee6-c47f-424a-aa1a-0c80bcf9e102}
2025/01/31 18:51:21 INFO Request: <lock><target><candidate/></target></lock>
Reply: &{XMLName:{Space:urn:ietf:params:xml:ns:netconf:base:1.0 Local:rpc-reply} Errors:[] Data:
<ok/>
 Ok:false RawReply:<rpc-reply xmlns:junos="http://xml.juniper.net/junos/23.1R0/junos" message-id="000df840-d019-453c-b64e-76abf1da5af2" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<ok/>
</rpc-reply>
 MessageID:000df840-d019-453c-b64e-76abf1da5af2}
2025/01/31 18:51:21 INFO Request: <load-configuration action="merge" format="text"><configuration-text>interfaces {
    ge-0/0/0 {
        mtu 1;
    }
}</configuration-text></load-configuration>
Reply: &{XMLName:{Space:urn:ietf:params:xml:ns:netconf:base:1.0 Local:rpc-reply} Errors:[] Data:
<load-configuration-results>
<rpc-error>
<error-type>protocol</error-type>
<error-tag>operation-failed</error-tag>
<error-severity>error</error-severity>
<error-message>Value 1 is not within range (256..9192)</error-message>
<error-info>
<bad-element>1</bad-element>
</error-info>
</rpc-error>
<rpc-error>
<error-severity>warning</error-severity>
<error-path>[edit interfaces]</error-path>
<error-message>mgd: statement has no contents; ignored</error-message>
<error-info>
<bad-element>ge-0/0/0</bad-element>
</error-info>
</rpc-error>
</load-configuration-results>
 Ok:false RawReply:<rpc-reply xmlns:junos="http://xml.juniper.net/junos/23.1R0/junos" message-id="0629d92f-78b9-48fd-99d6-42a7027bc853" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<load-configuration-results>
<rpc-error>
<error-type>protocol</error-type>
<error-tag>operation-failed</error-tag>
<error-severity>error</error-severity>
<error-message>Value 1 is not within range (256..9192)</error-message>
<error-info>
<bad-element>1</bad-element>
</error-info>
</rpc-error>
<rpc-error>
<error-severity>warning</error-severity>
<error-path>[edit interfaces]</error-path>
<error-message>mgd: statement has no contents; ignored</error-message>
<error-info>
<bad-element>ge-0/0/0</bad-element>
</error-info>
</rpc-error>
</load-configuration-results>
</rpc-reply>
 MessageID:0629d92f-78b9-48fd-99d6-42a7027bc853}
2025/01/31 18:51:21 INFO Request: <get-configuration compare="rollback" rollback="0" format="text"/>
Reply: &{XMLName:{Space:urn:ietf:params:xml:ns:netconf:base:1.0 Local:rpc-reply} Errors:[] Data:
<configuration-information>
<configuration-output>
</configuration-output>
</configuration-information>
 Ok:false RawReply:<rpc-reply xmlns:junos="http://xml.juniper.net/junos/23.1R0/junos" message-id="f2a9f478-5d6b-481b-b688-d213fd9a7448" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<configuration-information>
<configuration-output>
</configuration-output>
</configuration-information>
</rpc-reply>
 MessageID:f2a9f478-5d6b-481b-b688-d213fd9a7448}
2025/01/31 18:51:21 INFO Request: <commit/>
Reply: &{XMLName:{Space:urn:ietf:params:xml:ns:netconf:base:1.0 Local:rpc-reply} Errors:[] Data:
<ok/>
 Ok:false RawReply:<rpc-reply xmlns:junos="http://xml.juniper.net/junos/23.1R0/junos" message-id="51082295-a30f-429f-a989-74ae8e96a17f" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<ok/>
</rpc-reply>
 MessageID:51082295-a30f-429f-a989-74ae8e96a17f}
2025/01/31 18:51:21 INFO Request: <unlock><target><candidate/></target></unlock>
Reply: &{XMLName:{Space:urn:ietf:params:xml:ns:netconf:base:1.0 Local:rpc-reply} Errors:[] Data:
<ok/>
 Ok:false RawReply:<rpc-reply xmlns:junos="http://xml.juniper.net/junos/23.1R0/junos" message-id="3c86e33c-0dad-42cf-b78d-624645023f2a" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<ok/>
</rpc-reply>
 MessageID:3c86e33c-0dad-42cf-b78d-624645023f2a}
```

これではいけません。どのようなエラーが起きているのでしょうか？  
該当部分を抜き出して整形してみます。

```xml
<load-configuration-results>
  <rpc-error>
    <error-type>protocol</error-type>
    <error-tag>operation-failed</error-tag>
    <error-severity>error</error-severity>
    <error-message>Value 1 is not within range (256..9192)</error-message>
    <error-info>
      <bad-element>1</bad-element>
    </error-info>
  </rpc-error>
  <rpc-error>
    <error-severity>warning</error-severity>
    <error-path>[edit interfaces]</error-path>
    <error-message>mgd: statement has no contents; ignored</error-message>
    <error-info>
      <bad-element>ge-0/0/0</bad-element>
    </error-info>
  </rpc-error>
</load-configuration-results>
```

エラーを表しているのは `<error-severity>error</error-severity>` の登録のようですね。
対応している `rpc-error` にどのようなものがあるのか見てみましょう。
これには [RFC 6241: Network Configuration Protocol (NETCONF)](https://www.rfc-editor.org/rfc/rfc6241) が参考になりますね！
79ページ目に `<error-tag>operation-failed</error-tag>` で示されているタグの説明を見つけることができました。

```
RFC 6241                    NETCONF Protocol                   June 2011


   error-tag:      operation-failed
   error-type:     rpc, protocol, application
   error-severity: error
   error-info:     none
   Description:    Request could not be completed because the requested
                   operation failed for some reason not covered by
                   any other error condition.
```

何らかの理由でリクエストしたオペレーションが失敗した時に使用するものですね。
vJunos-Switchの場合は投入不可能な設定をしようとした場合は `operation-failed` が使われるようです。

しかし、エラーの内容は分かりましたが、このままcommit処理まで進んでしまうのは良くありません。
修正方法を考えてみましょう。

### エラーハンドリングを修正してみる

`github.com/Juniper/go-netconf/netconf` には `RPCError` の型定義がありました。
https://github.com/Juniper/go-netconf/blob/master/netconf/rpc.go#L94-L101
これを利用してエラータグが返ってきたときに処理を中断できるようにXMLの解析コードを追加してみます。

```diff
diff --git a/main.go b/main.go
index e088ed2..51c6dd3 100644
--- a/main.go
+++ b/main.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+       "encoding/xml"
        "fmt"
        "log/slog"
 
@@ -17,10 +18,14 @@ const (
 
 const sampleConfig = `interfaces {
     ge-0/0/0 {
-        mtu 1400;
+        mtu 1;
     }
 }`
 
+type LoadConfigurationResults struct {
+       Errors []netconf.RPCError `xml:"rpc-error"`
+}
+
 func Configure(host string, user string, password string) error {
        // Open SSH connection
        s, err := netconf.DialSSH(host, &ssh.ClientConfig{
@@ -59,6 +64,13 @@ func Configure(host string, user string, password string) error {
                        // If delete non-existing config tree, returned &netconf.RPCError{..., Tag:"data-missing", ...}
                        return fmt.Errorf("failed rpc command: %s: %w", cmd, err)
                }
+               var results LoadConfigurationResults
+               if err := xml.Unmarshal([]byte(r.Data), &results); err != nil {
+                       return fmt.Errorf("failed parse load-configuration-results: %s: %w", cmd, err)
+               }
+               if len(results.Errors) > 0 {
+                       return fmt.Errorf("failed load-configuration: %s: %+v", cmd, results.Errors)
+               }
                slog.Info(fmt.Sprintf("Request: %s\nReply: %+v", cmd, r))
        }
        return nil
```

### エラーハンドリングを修正後の出力例

差分を適用してビルドしたら、再度MTU=1の設定を入れようとしてNW装置に怒られてみましょう。

```
$ ./go-netconf-unit-test 
2025/01/31 19:05:34 INFO Request: <discard-changes/>
Reply: &{XMLName:{Space:urn:ietf:params:xml:ns:netconf:base:1.0 Local:rpc-reply} Errors:[] Data:
<ok/>
 Ok:false RawReply:<rpc-reply xmlns:junos="http://xml.juniper.net/junos/23.1R0/junos" message-id="141808e9-20b2-4652-8448-aed9d19022be" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<ok/>
</rpc-reply>
 MessageID:141808e9-20b2-4652-8448-aed9d19022be}
2025/01/31 19:05:34 INFO Request: <lock><target><candidate/></target></lock>
Reply: &{XMLName:{Space:urn:ietf:params:xml:ns:netconf:base:1.0 Local:rpc-reply} Errors:[] Data:
<ok/>
 Ok:false RawReply:<rpc-reply xmlns:junos="http://xml.juniper.net/junos/23.1R0/junos" message-id="ecc4cfc0-ab27-4cbc-92e0-2fb314fb93ad" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<ok/>
</rpc-reply>
 MessageID:ecc4cfc0-ab27-4cbc-92e0-2fb314fb93ad}
2025/01/31 19:05:34 ERROR failed load-configuration: <load-configuration action="merge" format="text"><configuration-text>interfaces {
    ge-0/0/0 {
        mtu 1;
    }
}</configuration-text></load-configuration>: [{Type:protocol Tag:operation-failed Severity:error Path: Message:Value 1 is not within range (256..9192) Info:
<error-type>protocol</error-type>
<error-tag>operation-failed</error-tag>
<error-severity>error</error-severity>
<error-message>Value 1 is not within range (256..9192)</error-message>
<error-info>
<bad-element>1</bad-element>
</error-info>
} {Type: Tag: Severity:warning Path:[edit interfaces] Message:mgd: statement has no contents; ignored Info:
<error-severity>warning</error-severity>
<error-path>[edit interfaces]</error-path>
<error-message>mgd: statement has no contents; ignored</error-message>
<error-info>
<bad-element>ge-0/0/0</bad-element>
</error-info>
}]
```

今度はちゃんとエラーが返ってきたタイミングで処理が中断されて、エラーメッセージが表示されるようになりました。
これなら範囲外のパラメータが入ってしまっても、ひとまず安心ですね。

## まとめ

今回は `github.com/Juniper/go-netconf/netconf` を利用して、Go言語を利用したNW装置とのNETCONF通信をしてみました。
NW装置に設定する内容は要件に応じて様々ですが、今回の内容をもとに設定内容や変数化するパラメータをテンプレート化することで、ごく基本的な機能を持ったNWサービスの開発が可能になると思います。

また、ある程度RFCで規範が与えられているとはいえ、エラー発生時の応答はNW装置によって実装が様々です。
表面的なコード理解だけでなく、生データを観測しながら正しいらしい動作を作っていくことが重要ですね。