Skip to content

Commit

Permalink
feat: Add EoIP tunnel support (#283)
Browse files Browse the repository at this point in the history
  • Loading branch information
dokmic committed Oct 31, 2023
1 parent e8c9140 commit bcab0fb
Show file tree
Hide file tree
Showing 9 changed files with 343 additions and 206 deletions.
62 changes: 62 additions & 0 deletions docs/resources/routeros_interface_eoip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# routeros_interface_eoip (Resource)


## Example Usage
```terraform
resource "routeros_interface_eoip" "eoip_tunnel1" {
name = "eoip-tunnel1"
local_address = "192.168.88.1"
remote_address = "192.168.88.2"
disabled = true
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `name` (String) Changing the name of this resource will force it to be recreated.
> The links of other configuration properties to this resource may be lost!
> Changing the name of the resource outside of a Terraform will result in a loss of control integrity for that resource!

### Optional

- `allow_fast_path` (Boolean) Whether to allow FastPath processing. Must be disabled if IPsec tunneling is used.
- `arp` (String) Address Resolution Protocol mode:
* disabled - the interface will not use ARP
* enabled - the interface will use ARP
* local-proxy-arp - the router performs proxy ARP on the interface and sends replies to the same interface
* proxy-arp - the router performs proxy ARP on the interface and sends replies to other interfaces
* reply-only - the interface will only reply to requests originated from matching IP address/MAC address combinations which are entered as static entries in the ARP table. No dynamic entries will be automatically stored in the ARP table. Therefore for communications to be successful, a valid static entry must already exist.
- `arp_timeout` (String) ARP timeout is time how long ARP record is kept in ARP table after no packets are received from IP. Value auto equals to the value of arp-timeout in IP/Settings, default is 30s. Can use postfix ms, s, M, h, d for milliseconds, seconds, minutes, hours or days. If no postfix is set then seconds (s) is used.
- `clamp_tcp_mss` (Boolean) Controls whether to change MSS size for received TCP SYN packets. When enabled, a router will change the MSS size for received TCP SYN packets if the current MSS size exceeds the tunnel interface MTU (taking into account the TCP/IP overhead). The received encapsulated packet will still contain the original MSS, and only after decapsulation the MSS is changed.
- `comment` (String)
- `disabled` (Boolean)
- `dont_fragment` (String)
- `dscp` (String) Set dscp value in GRE header to a fixed value '0..63' or 'inherit' from dscp value taken from tunnelled traffic.
- `ipsec_secret` (String, Sensitive) When secret is specified, router adds dynamic IPsec peer to remote-address with pre-shared key and policy (by default phase2 uses sha1/aes128cbc).
- `keepalive` (String) Tunnel keepalive parameter sets the time interval in which the tunnel running flag will remain even if the remote end of tunnel goes down. If configured time,retries fail, interface running flag is removed. Parameters are written in following format: KeepaliveInterval,KeepaliveRetries where KeepaliveInterval is time interval and KeepaliveRetries - number of retry attempts. KeepaliveInterval is integer 0..4294967295
- `local_address` (String) Source address of the tunnel packets, local on the router.
- `loop_protect` (String)
- `loop_protect_disable_time` (String)
- `loop_protect_send_interval` (String)
- `mtu` (String) Layer3 Maximum transmission unit ('auto', 0 .. 65535)
- `remote_address` (String) IP address of the remote end of the tunnel.
- `tunnel_id` (String) Unique tunnel identifier, which must match the other side of the tunnel.

### Read-Only

- `actual_mtu` (Number)
- `id` (String) The ID of this resource.
- `l2mtu` (Number) Layer2 Maximum transmission unit. [See](https://wiki.mikrotik.com/wiki/Maximum_Transmission_Unit_on_RouterBoards).
- `loop_protect_status` (String)
- `mac_address` (String) Current mac address.
- `running` (Boolean)

## Import
Import is supported using the following syntax:
```shell
# Import with the name of the EoIP interface in case of the example, use `eoip-tunnel1`
terraform import routeros_interface_eoip.eoip_tunnel1 eoip-tunnel1
```
2 changes: 2 additions & 0 deletions examples/resources/routeros_interface_eoip/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Import with the name of the EoIP interface in case of the example, use `eoip-tunnel1`
terraform import routeros_interface_eoip.eoip_tunnel1 eoip-tunnel1
6 changes: 6 additions & 0 deletions examples/resources/routeros_interface_eoip/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
resource "routeros_interface_eoip" "eoip_tunnel1" {
name = "eoip-tunnel1"
local_address = "192.168.88.1"
remote_address = "192.168.88.2"
disabled = true
}
1 change: 1 addition & 0 deletions routeros/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ func Provider() *schema.Provider {
"routeros_interface_bridge_port": ResourceInterfaceBridgePort(),
"routeros_interface_bridge_vlan": ResourceInterfaceBridgeVlan(),
"routeros_interface_bridge_settings": ResourceInterfaceBridgeSettings(),
"routeros_interface_eoip": ResourceInterfaceEoip(),
"routeros_interface_gre": ResourceInterfaceGre(),
"routeros_interface_vlan": ResourceInterfaceVlan(),
"routeros_interface_vrrp": ResourceInterfaceVrrp(),
Expand Down
188 changes: 171 additions & 17 deletions routeros/provider_schema_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,35 @@ const (
)

const (
KeyActualMtu = "actual_mtu"
KeyArp = "arp"
KeyArpTimeout = "arp_timeout"
KeyComment = "comment"
KeyDynamic = "dynamic"
KeyDisabled = "disabled"
KeyFilter = "filter"
KeyInactive = "inactive"
KeyInterface = "interface"
KeyInvalid = "invalid"
KeyL2Mtu = "l2mtu"
KeyMacAddress = "mac_address"
KeyMtu = "mtu"
KeyName = "name"
KeyPlaceBefore = "place_before"
KeyRunning = "running"
KeyVrf = "vrf"
KeyActualMtu = "actual_mtu"
KeyAllowFastPath = "allow_fast_path"
KeyArp = "arp"
KeyArpTimeout = "arp_timeout"
KeyClampTcpMss = "clamp_tcp_mss"
KeyComment = "comment"
KeyDynamic = "dynamic"
KeyDisabled = "disabled"
KeyDontFragment = "dont_fragment"
KeyDscp = "dscp"
KeyFilter = "filter"
KeyInactive = "inactive"
KeyInterface = "interface"
KeyInvalid = "invalid"
KeyIpsecSecret = "ipsec_secret"
KeyKeepalive = "keepalive"
KeyL2Mtu = "l2mtu"
KeyLocalAddress = "local_address"
KeyLoopProtect = "loop_protect"
KeyLoopProtectDisableTime = "loop_protect_disable_time"
KeyLoopProtectSendInterval = "loop_protect_send_interval"
KeyLoopProtectStatus = "loop_protect_status"
KeyMacAddress = "mac_address"
KeyMtu = "mtu"
KeyName = "name"
KeyPlaceBefore = "place_before"
KeyRemoteAddress = "remote_address"
KeyRunning = "running"
KeyVrf = "vrf"
)

// PropResourcePath Resource path property.
Expand Down Expand Up @@ -107,6 +119,12 @@ var (
Type: schema.TypeInt,
Computed: true,
}
PropAllowFastPathRw = &schema.Schema{
Type: schema.TypeBool,
Optional: true, // Must be present in the request so that the IPSEC PSK can be set correctly.
Default: true,
Description: "Whether to allow FastPath processing. Must be disabled if IPsec tunneling is used.",
}
PropArpRw = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Expand All @@ -130,6 +148,15 @@ var (
ValidateFunc: validation.StringMatch(regexp.MustCompile(`^$|auto$|(\d+(ms|s|M|h|d)?)+$`),
"expected arp_timout value to be 'auto' string or time value"),
}
PropClampTcpMssRw = &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Controls whether to change MSS size for received TCP SYN packets. When enabled, a " +
"router will change the MSS size for received TCP SYN packets if the current MSS size exceeds the " +
"tunnel interface MTU (taking into account the TCP/IP overhead). The received encapsulated packet " +
"will still contain the original MSS, and only after decapsulation the MSS is changed.",
}
PropCommentRw = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Expand All @@ -139,6 +166,42 @@ var (
Optional: true,
Default: false,
}
PropDontFragmentRw = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "no",
ValidateFunc: validation.StringInSlice([]string{"inherit", "no"}, false),
}
PropDscpRw = &schema.Schema{
// dscp (inherit | integer [0-63]; Default: '')
Type: schema.TypeString,
Optional: true,
Default: "inherit",
ValidateDiagFunc: func(v interface{}, p cty.Path) (diags diag.Diagnostics) {
value := v.(string)

if value == "" || value == "inherit" {
return
}

i, err := strconv.Atoi(value)
if err != nil {
diags = diag.Errorf(
"expected dscp value (%s) to be empty string or 'inherit' or integer 0..63", value)
return
}

if i < 0 || i > 63 {
diags = diag.Errorf(
"expected %s to be in the range 0 - 63, got %d", value, i)
return
}

return
},
Description: "Set dscp value in GRE header to a fixed value '0..63' or 'inherit' from dscp value taken " +
"from tunnelled traffic.",
}
PropDynamicRo = &schema.Schema{
Type: schema.TypeBool,
Computed: true,
Expand All @@ -164,12 +227,96 @@ var (
Type: schema.TypeBool,
Computed: true,
}
PropIpsecSecretRw = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "",
Sensitive: true,
Description: "When secret is specified, router adds dynamic IPsec peer to remote-address with " +
"pre-shared key and policy (by default phase2 uses sha1/aes128cbc).",
}
PropKeepaliveRw = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "10s,10",
ValidateFunc: validation.StringMatch(regexp.MustCompile(`^(\d+[smhdw]?)+(,\d+)?$`),
"value must be integer[/time],integer 0..4294967295 (https://help.mikrotik.com/docs/display/ROS/GRE)"),
Description: "Tunnel keepalive parameter sets the time interval in which the tunnel running flag will " +
"remain even if the remote end of tunnel goes down. If configured time,retries fail, interface " +
"running flag is removed. Parameters are written in following format: " +
"KeepaliveInterval,KeepaliveRetries where KeepaliveInterval is time interval and " +
"KeepaliveRetries - number of retry attempts. KeepaliveInterval is integer 0..4294967295",
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
if old == new {
return true
}

if old == "" || new == "" {
return false
}

o := strings.Split(old, ",")
n := strings.Split(new, ",")
if len(o) != 2 || len(n) != 2 {
panic(fmt.Sprintf("[GRE keepalive] wrong keepalive format, old: '%v', new: '%v'", old, new))
}

// Compare keepalive retries.
if o[1] != n[1] {
return false
}

// Compare keepalive intervals.
oDuration, err := ParseDuration(o[0])
if err != nil {
panic("[GRE keepalive] parse 'old' duration error: " + err.Error())
}

nDuration, err := ParseDuration(n[0])
if err != nil {
panic("[GRE keepalive] parse 'new' duration error: " + err.Error())
}

return oDuration.Seconds() == nDuration.Seconds()
},
}
PropL2MtuRo = &schema.Schema{
Type: schema.TypeInt,
Computed: true,
Description: "Layer2 Maximum transmission unit. " +
"[See](https://wiki.mikrotik.com/wiki/Maximum_Transmission_Unit_on_RouterBoards).",
}
PropLocalAddressRw = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "0.0.0.0",
Description: "Source address of the tunnel packets, local on the router.",
ValidateFunc: validation.IsIPv4Address,
}
PropLoopProtectRw = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "default",
ValidateFunc: validation.StringInSlice([]string{"default", "on", "off"}, false),
}
PropLoopProtectDisableTimeRw = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "5m",
ValidateFunc: ValidationTime,
DiffSuppressFunc: TimeEquall,
}
PropLoopProtectSendIntervalRw = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "5s",
ValidateFunc: ValidationTime,
DiffSuppressFunc: TimeEquall,
}
PropLoopProtectStatusRo = &schema.Schema{
Type: schema.TypeString,
Computed: true,
}
PropMacAddressRo = &schema.Schema{
Type: schema.TypeString,
Computed: true,
Expand All @@ -193,6 +340,13 @@ var (
> Best way to use in conjunction with a data source. See [example](../data-sources/firewall.md#example-usage).
`,
}
PropRemoteAddressRw = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "0.0.0.0",
Description: "IP address of the remote end of the tunnel.",
ValidateFunc: validation.IsIPv4Address,
}
PropRunningRo = &schema.Schema{
Type: schema.TypeBool,
Computed: true,
Expand Down
66 changes: 66 additions & 0 deletions routeros/resource_interface_eoip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package routeros

import (
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

// https://help.mikrotik.com/docs/display/ROS/EoIP
func ResourceInterfaceEoip() *schema.Resource {
resSchema := map[string]*schema.Schema{
MetaResourcePath: PropResourcePath("/interface/eoip"),
MetaId: PropId(Name),

KeyActualMtu: PropActualMtuRo,
KeyArp: PropArpRw,
KeyArpTimeout: PropArpTimeoutRw,
KeyAllowFastPath: PropAllowFastPathRw,
KeyClampTcpMss: PropClampTcpMssRw,
KeyComment: PropCommentRw,
KeyDisabled: PropDisabledRw,
KeyDontFragment: PropDontFragmentRw,
KeyDscp: PropDscpRw,
KeyIpsecSecret: PropIpsecSecretRw,
KeyKeepalive: PropKeepaliveRw,
KeyL2Mtu: PropL2MtuRo,
KeyLocalAddress: PropLocalAddressRw,
KeyLoopProtect: PropLoopProtectRw,
KeyLoopProtectDisableTime: PropLoopProtectDisableTimeRw,
KeyLoopProtectSendInterval: PropLoopProtectSendIntervalRw,
KeyLoopProtectStatus: PropLoopProtectStatusRo,
KeyMacAddress: PropMacAddressRo,
KeyMtu: PropMtuRw(),
KeyName: PropNameForceNewRw,
KeyRemoteAddress: PropRemoteAddressRw,
KeyRunning: PropRunningRo,
"tunnel_id": {
Type: schema.TypeString,
Optional: true,
Default: "0",
Description: "Unique tunnel identifier, which must match the other side of the tunnel.",
},
}

return &schema.Resource{
CreateContext: DefaultValidateCreate(resSchema, func(d *schema.ResourceData) diag.Diagnostics {
if d.Get("allow_fast_path").(bool) && d.Get("ipsec_secret").(string) != "" {
return diag.Errorf("can't enable fastpath together with ipsec")
}
return nil
}),
ReadContext: DefaultRead(resSchema),
UpdateContext: DefaultValidateUpdate(resSchema, func(d *schema.ResourceData) diag.Diagnostics {
if d.Get("allow_fast_path").(bool) && d.Get("ipsec_secret").(string) != "" {
return diag.Errorf("can't enable fastpath together with ipsec")
}
return nil
}),
DeleteContext: DefaultDelete(resSchema),

Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},

Schema: resSchema,
}
}
Loading

0 comments on commit bcab0fb

Please sign in to comment.