Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PROPOSAL] policy: Enable to define policy as go plugin #1577

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -2142,6 +2142,7 @@ func toPolicyApi(p *config.PolicyDefinition) *Policy {
}
return l
}(),
PluginPath: p.PluginPath,
}
}

Expand Down Expand Up @@ -2187,6 +2188,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 @@ -2199,8 +2204,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 @@ -5824,6 +5824,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 @@ -5849,6 +5852,9 @@ func (lhs *PolicyDefinition) Equal(rhs *PolicyDefinition) bool {
}
}
}
if lhs.PluginPath != rhs.PluginPath {
return false
}
return true
}

Expand Down
2 changes: 1 addition & 1 deletion docs/sources/cli-command-syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ If you want to remove one element(extended community) of ExtCommunitySet, to spe

```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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you return if you want to reject the prefix?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just returning nil should work as reject.

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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you reload it at runtime if there is any change in the policy? How?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay. It’s a troublesome...
When reloading the confirmation, GoBGP will determine the configured values are updated or not before reload API. Then if the configuration file is not changed, even if the plug in is updated, GoBGP can’t detect change of the plug in's implementation.
So if we need to update and reload the plug in, it is required to change the path to plug in or its name.
For example, policy_a.1.0.so to policy_a.1.1.so or appending the updated timestamp or date.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick answers. I think renaming things and changing the path is fine. Might be worth adding a note to the README so people is aware this is how policies are updated when you are using this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, thanks a lot! I will add description about how to update the policy plug-in and how to reject the given prefix.


```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