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 <[email protected]>
  • Loading branch information
iwaseyusuke committed Feb 12, 2018
1 parent f2e2c6e commit d081270
Show file tree
Hide file tree
Showing 8 changed files with 769 additions and 471 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
10 changes: 8 additions & 2 deletions api/grpc_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2172,6 +2172,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 +2188,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
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 }
```
52 changes: 48 additions & 4 deletions table/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"encoding/json"
"fmt"
"net"
"plugin"
"reflect"
"regexp"
"strconv"
Expand Down Expand Up @@ -2726,14 +2727,31 @@ func NewStatement(c config.Statement) (*Statement, error) {
}

type Policy struct {
Name string
Statements []*Statement
Name string
Statements []*Statement
PluginPath string
PluginApplyFunc func(*Path, *PolicyOptions) (*Path, error)
}

// Compare path with a policy's condition in stored order in the policy.
// If a condition match, then this function stops evaluation and
// subsequent conditions are skipped.
func (p *Policy) Apply(path *Path, options *PolicyOptions) (RouteType, *Path) {
if p.PluginApplyFunc != nil {
updatedPath, err := p.PluginApplyFunc(path, options)
if err != nil {
log.WithFields(log.Fields{
"Topic": "Policy",
"Key": p.Name,
"Error": err,
"Path": path,
}).Warn("failed to apply policy plugin")
}
if updatedPath == nil {
return ROUTE_TYPE_REJECT, nil
}
return ROUTE_TYPE_ACCEPT, updatedPath
}
for _, stmt := range p.Statements {
var result RouteType
result, path = stmt.Apply(path, options)
Expand All @@ -2752,6 +2770,7 @@ func (p *Policy) ToConfig() *config.PolicyDefinition {
return &config.PolicyDefinition{
Name: p.Name,
Statements: ss,
PluginPath: p.PluginPath,
}
}

Expand Down Expand Up @@ -2800,10 +2819,33 @@ func (p *Policy) MarshalJSON() ([]byte, error) {
return json.Marshal(p.ToConfig())
}

func ExtractApplyFuncFromPolicyPlugin(pluginPath string) (func(*Path, *PolicyOptions) (*Path, error), error) {
if pluginPath == "" {
return nil, nil
}
p, err := plugin.Open(pluginPath)
if err != nil {
return nil, fmt.Errorf("cannot open policy plugin: %s: %s", pluginPath, err.Error())
}
sym, err := p.Lookup("Apply")
if err != nil {
return nil, fmt.Errorf("no 'Apply' func in policy plugin: %s: %s", pluginPath, err.Error())
}
f, ok := sym.(func(*Path, *PolicyOptions) (*Path, error))
if !ok {
return nil, fmt.Errorf("invalid 'Apply' func definition in policy plugin: %s", pluginPath)
}
return f, nil
}

func NewPolicy(c config.PolicyDefinition) (*Policy, error) {
if c.Name == "" {
return nil, fmt.Errorf("empty policy name")
}
applyFunc, err := ExtractApplyFuncFromPolicyPlugin(c.PluginPath)
if err != nil {
return nil, err
}
var st []*Statement
stmts := c.Statements
if len(stmts) != 0 {
Expand All @@ -2820,8 +2862,10 @@ func NewPolicy(c config.PolicyDefinition) (*Policy, error) {
}
}
return &Policy{
Name: c.Name,
Statements: st,
Name: c.Name,
Statements: st,
PluginPath: c.PluginPath,
PluginApplyFunc: applyFunc,
}, nil
}

Expand Down
8 changes: 8 additions & 0 deletions tools/pyang_plugins/gobgp.yang
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,14 @@ module gobgp {

}

augment "/rpol:routing-policy/rpol:policy-definitions/" +
"rpol:policy-definition" {
leaf plugin-path {
description "Path to policy plugin which must be an shared object.";
type string;
}
}

augment "/rpol:routing-policy/rpol:policy-definitions/" +
"rpol:policy-definition/rpol:statements/rpol:statement/" +
"rpol:actions/bgp-pol:bgp-actions/bgp-pol:set-as-path-prepend" {
Expand Down
1 change: 1 addition & 0 deletions tools/spell-check/dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ mututally
nexthop
nexthops
nlri
plugin
pmsi
prepend
reachability
Expand Down

0 comments on commit d081270

Please sign in to comment.