Skip to content

Commit

Permalink
policy: Enable to define policy as go plugin
Browse files Browse the repository at this point in the history
For more programmable policy definition, this patch enables to define
policy as "plugin" (requires Golang 1.8+) and apply arbitrary rules by
implementing policy plugin.

Signed-off-by: IWASE Yusuke <iwase.yusuke0@gmail.com>
  • Loading branch information
iwaseyusuke committed Feb 13, 2018
1 parent f2e2c6e commit 95b2967
Show file tree
Hide file tree
Showing 10 changed files with 789 additions and 477 deletions.
939 changes: 474 additions & 465 deletions api/gobgp.pb.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions api/gobgp.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,7 @@ message Statement {
message Policy {
string name = 1;
repeated Statement statements = 2;
string plugin_path = 3;
}

enum PolicyType {
Expand Down
11 changes: 9 additions & 2 deletions api/grpc_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2125,6 +2125,7 @@ func toPolicyApi(p *config.PolicyDefinition) *Policy {
}
return l
}(),
PluginPath: p.PluginPath,
}
}

Expand Down Expand Up @@ -2172,6 +2173,10 @@ func NewPolicyFromApiStruct(a *Policy) (*table.Policy, error) {
if a.Name == "" {
return nil, fmt.Errorf("empty policy name")
}
applyFunc, err := table.ExtractApplyFuncFromPolicyPlugin(a.PluginPath)
if err != nil {
return nil, err
}
stmts := make([]*table.Statement, 0, len(a.Statements))
for idx, x := range a.Statements {
if x.Name == "" {
Expand All @@ -2184,8 +2189,10 @@ func NewPolicyFromApiStruct(a *Policy) (*table.Policy, error) {
stmts = append(stmts, y)
}
return &table.Policy{
Name: a.Name,
Statements: stmts,
Name: a.Name,
Statements: stmts,
PluginPath: a.PluginPath,
PluginApplyFunc: applyFunc,
}, nil
}

Expand Down
6 changes: 6 additions & 0 deletions config/bgp_configs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5700,6 +5700,9 @@ type PolicyDefinition struct {
// original -> rpol:statements
// Enclosing container for policy statements.
Statements []Statement `mapstructure:"statements" json:"statements,omitempty"`
// original -> gobgp:plugin-path
// Path to policy plugin which must be an shared object.
PluginPath string `mapstructure:"plugin-path" json:"plugin-path,omitempty"`
}

func (lhs *PolicyDefinition) Equal(rhs *PolicyDefinition) bool {
Expand All @@ -5725,6 +5728,9 @@ func (lhs *PolicyDefinition) Equal(rhs *PolicyDefinition) bool {
}
}
}
if lhs.PluginPath != rhs.PluginPath {
return false
}
return true
}

Expand Down
4 changes: 3 additions & 1 deletion docs/sources/cli-command-syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,10 +398,12 @@ If you want to remove one element(extended community) of ExtCommunitySet, to spe
```
### 3.8 Policy Operation - add/del/show -
#### Syntax
```shell
# mod policy
% gobgp policy { add | del | set } <policy name> [<statement name>...]
% gobgp policy { add | del | set } <policy name> [<statement name>...] [plugin-path <plugin-path>]
# show all policies
% gobgp policy
# show a specific policy
Expand Down
223 changes: 223 additions & 0 deletions docs/sources/policy-plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
# Policy Plugin

This page explains how to implement your own policy plugin and how to configure
it.

The policy plugin enables to apply policy rules flexibly which are difficult
to define with the [OpenConfig](http://www.openconfig.net/) policy model, just
logging received paths and complex attributes manipulation for example.

## Prerequisites

The following assumes you finished [Getting Started](getting-started.md) and
bases of [GoBGP Policy Model](policy.md).

Also, the policy plugin feature uses ["plugin"](https://golang.org/pkg/plugin/)
package of Golang, which requires to use Golang version 1.8 or later.

## Contents

- [Implementation of Policy Plugin](#implementation-of-policy-plugin)
- [Build Policy Plugin](#build-policy-plugin)
- [Configuration](#configuration)
- [Verification](#verification)

## Implementation of Policy Plugin

A policy plugin must have an `Apply` function which receives `path *table.Path`
and `options *table.PolicyOptions` arguments, then returns `*table.Path` and
`error`.

```go
package main

import (
"github.com/osrg/gobgp/table"
)

func Apply(path *table.Path, options *table.PolicyOptions) (*table.Path, error) {
// Do some manipulation of "path".
// Or "return nil, nil" to reject "path".
return path, nil
}
```

`path *table.Path` includes the incoming path information and
`options *table.PolicyOptions` provides the additional information about sender
router for example.

The return value of `*table.Path` should be the updated path if successfully
manipulated (also with nothing to update) and `error` describes errors during
manipulation process. Also, to reject the given path, please `return nil, nil`.

Example: `policy_a.go`

```go
package main

import (
"fmt"

"github.com/osrg/gobgp/table"
)

func Apply(path *table.Path, options *table.PolicyOptions) (*table.Path, error) {
// Just do logging
fmt.Printf("Apply policy to %+v\n", path)
return path, nil
}
```

## Build Policy Plugin

To generate a loadable policy plugin, execute `go build` command with
`-buildmode=plugin` option.

```bash
go build -buildmode=plugin -o <plugin name>.so <plugin name>.go
```

Example: Build `policy_a.so` with `policy_a.go`

```bash
go build -buildmode=plugin -o policy_a.so policy_a.go
```

## Configuration

To attach the policy plugin to global RIB or neighbor's local RIB (only when
the neighbor is configured as a "route-server client"), specify path to the
generated policy plugin (`.so` file) with `plugin-path` in
`[[policy-definitions]]` section.

Example: Attach `policy_a.so` to global RIB as "import policy"

```toml
[global.apply-policy.config]
import-policy-list = ["policy_a"]
default-import-policy = "accept-route"
default-export-policy = "accept-route"

[[policy-definitions]]
name = "policy_a"
plugin-path = "/path/to/policy_a.so"
```

Example: Attach `policy_a.so` to global RIB as "export policy"

```toml
[[neighbors]]
# ...(snip)...
[neighbors.route-server.config]
route-server-client = true
[neighbors.apply-policy.config]
export-policy-list = ["policy_a"]
default-import-policy = "accept-route"
default-export-policy = "accept-route"
default-in-policy = "accept-route"

[[policy-definitions]]
name = "policy_a"
plugin-path = "/path/to/policy_a.so"
```

**NOTE:** When reloading the confirmation file, GoBGP will determine whether
the configured values are updated or not before calling the reloading policy
API. So if the configured value (`plugin-path`) is not changed, even if the
plugin is updated, GoBGP can't detect the change of the plugin's
implementation. Then for reloading the policy plugin, please also update the
value of `plugin-path`, for example, `/path/to/policy_a.1.0.so` to
`/path/to/policy_a.1.1.so` or appending the timestamp.

## Verification

Let's verify the example policy plugin `policy_a.so` attached to global RIB can
output log messages when `Apply` function is called.

Topology:

```text
+----------+ +----------+
| r1 | | r2 |
| 10.0.0.1 +----(iBGP)----+ 10.0.0.2 |
| AS 65000 | | AS 65000 |
+----------+ +----------+
```

`gobgpd.toml` on r1:

```toml
[global.config]
as = 65000
router-id = "1.1.1.1"

[[neighbors]]
[neighbors.config]
neighbor-address = "10.0.0.2"
peer-as = 65000
[[neighbors.afi-safis]]
[neighbors.afi-safis.config]
afi-safi-name = "ipv4-unicast"

[global.apply-policy.config]
import-policy-list = ["policy_a"]
default-import-policy = "accept-route"
default-export-policy = "accept-route"

[[policy-definitions]]
name = "policy_a"
plugin-path = "/path/to/policy_a.so"
```

`gobgpd.toml` on r2:

```toml
[global.config]
as = 65000
router-id = "2.2.2.2"

[[neighbors]]
[neighbors.config]
neighbor-address = "10.0.0.1"
peer-as = 65000
[[neighbors.afi-safis]]
[neighbors.afi-safis.config]
afi-safi-name = "ipv4-unicast"
```

Start GoBGP on r1 and r2.

```bash
r1> gobgpd -f gobgpd.toml
{"level":"info","msg":"gobgpd started","time":"YYYY-MM-DDThh:mm:ss+09:00"}
{"Topic":"Config","level":"info","msg":"Finished reading the config file","time":"YYYY-MM-DDThh:mm:ss+09:00"}
{"level":"info","msg":"Peer 10.0.0.2 is added","time":"YYYY-MM-DDThh:mm:ss+09:00"}
{"Topic":"Peer","level":"info","msg":"Add a peer configuration for:10.0.0.2","time":"YYYY-MM-DDThh:mm:ss+09:00"}
{"Key":"10.0.0.2","State":"BGP_FSM_OPENCONFIRM","Topic":"Peer","level":"info","msg":"Peer Up","time":"YYYY-MM-DDThh:mm:ss+09:00"}
...(snip)...

r2> gobgpd -f gobgpd.toml
{"level":"info","msg":"gobgpd started","time":"YYYY-MM-DDThh:mm:ss+09:00"}
{"Topic":"Config","level":"info","msg":"Finished reading the config file","time":"YYYY-MM-DDThh:mm:ss+09:00"}
{"level":"info","msg":"Peer 10.0.0.1 is added","time":"YYYY-MM-DDThh:mm:ss+09:00"}
{"Topic":"Peer","level":"info","msg":"Add a peer configuration for:10.0.0.1","time":"YYYY-MM-DDThh:mm:ss+09:00"}
{"Key":"10.0.0.1","State":"BGP_FSM_OPENCONFIRM","Topic":"Peer","level":"info","msg":"Peer Up","time":"YYYY-MM-DDThh:mm:ss+09:00"}
...(snip)...
```

Add a route on r2.

```bash
r2> gobgp global rib -a ipv4 add 10.2.1.0/24
r2> gobgp global rib -a ipv4
Network Next Hop AS_PATH Age Attrs
*> 10.2.1.0/24 0.0.0.0 00:00:00 [{Origin: ?}]
```

Then, GoBGP on r1 should output like;

```bash
r1> gobgpd -f r1_gobgpd.toml
...(snip)...
Apply policy to { 10.2.1.0/24 | src: { 10.0.0.2 | as: 65000, id: 2.2.2.2 }, nh: 10.0.0.2 }
```
16 changes: 13 additions & 3 deletions gobgp/cmd/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ func printPolicy(indent int, pd *table.Policy) {
for _, s := range pd.Statements {
printStatement(indent, s)
}
fmt.Printf("%sPluginPath: %s\n", strings.Repeat(" ", indent), pd.PluginPath)
}

func showPolicy(args []string) error {
Expand Down Expand Up @@ -777,17 +778,26 @@ func modAction(name, op string, args []string) error {

func modPolicy(modtype string, args []string) error {
if len(args) < 1 {
return fmt.Errorf("usage: gobgp policy %s <name> [<statement name>...]", modtype)
return fmt.Errorf("usage: gobgp policy %s <name> [<statement name>...] [plugin-path <plugin-path>]", modtype)
}
name := args[0]
args = args[1:]
stmts := make([]config.Statement, 0, len(args))
for _, n := range args {
stmts = append(stmts, config.Statement{Name: n})
pluginPath := ""
for idx, arg := range args {
if arg == "plugin-path" {
if len(args) <= idx+1 {
return fmt.Errorf("specify plugin-path argument")
}
pluginPath = args[idx+1]
break
}
stmts = append(stmts, config.Statement{Name: arg})
}
policy, err := table.NewPolicy(config.PolicyDefinition{
Name: name,
Statements: stmts,
PluginPath: pluginPath,
})
if err != nil {
return err
Expand Down
Loading

0 comments on commit 95b2967

Please sign in to comment.