diff --git a/route/bgp.go b/route/bgp.go
index 47f7db4fc15980936e174ffa540fd3c09bb04a7e..e56b140b4bea79f00125a020c5f05b61aceb7279 100644
--- a/route/bgp.go
+++ b/route/bgp.go
@@ -2,6 +2,8 @@ package route
 
 import (
 	"fmt"
+	"strconv"
+	"strings"
 
 	"github.com/taktv6/tflow2/convert"
 )
@@ -154,6 +156,32 @@ func (b *BGPPath) Print() string {
 	return ret
 }
 
+func (b *BGPPath) Prepend(asn uint32, times uint16) {
+	if times == 0 {
+		return
+	}
+
+	asnStr := strconv.FormatUint(uint64(asn), 10)
+
+	path := make([]string, times+1)
+	for i := 0; uint16(i) < times; i++ {
+		path[i] = asnStr
+	}
+	path[times] = b.ASPath
+
+	b.ASPath = strings.TrimSuffix(strings.Join(path, " "), " ")
+	b.ASPathLen = b.ASPathLen + times
+}
+
+func (p *BGPPath) Copy() *BGPPath {
+	if p == nil {
+		return nil
+	}
+
+	cp := *p
+	return &cp
+}
+
 func uint32To4Byte(addr uint32) [4]byte {
 	slice := convert.Uint32Byte(addr)
 	ret := [4]byte{
diff --git a/route/path.go b/route/path.go
index 544b83b485c106870b8ff7a251539187c85a06d6..ef2e881798212232fc6e116d2df2764e7d6207d3 100644
--- a/route/path.go
+++ b/route/path.go
@@ -100,3 +100,15 @@ func (p *Path) Print() string {
 
 	return ret
 }
+
+func (p *Path) Copy() *Path {
+	if p == nil {
+		return nil
+	}
+
+	cp := *p
+	cp.BGPPath = cp.BGPPath.Copy()
+	cp.StaticPath = cp.StaticPath.Copy()
+
+	return &cp
+}
diff --git a/route/static.go b/route/static.go
index 483138f04874ff7f50185ba3fcfb04fb007c6029..0ab6b26e3dc01c58b756efca28935b2e8e4e9d5a 100644
--- a/route/static.go
+++ b/route/static.go
@@ -23,3 +23,12 @@ func (s *StaticPath) Compare(t *StaticPath) int8 {
 func (s *StaticPath) ECMP(t *StaticPath) bool {
 	return true
 }
+
+func (s *StaticPath) Copy() *StaticPath {
+	if s == nil {
+		return nil
+	}
+
+	cp := *s
+	return &cp
+}
diff --git a/routingtable/filter/actions/as_path_prepend_action.go b/routingtable/filter/actions/as_path_prepend_action.go
new file mode 100644
index 0000000000000000000000000000000000000000..30d63ca783fd91a9530f336f3311106c57ac5968
--- /dev/null
+++ b/routingtable/filter/actions/as_path_prepend_action.go
@@ -0,0 +1,29 @@
+package actions
+
+import (
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/route"
+)
+
+type ASPathPrependAction struct {
+	asn   uint32
+	times uint16
+}
+
+func NewASPathPrependAction(asn uint32, times uint16) *ASPathPrependAction {
+	return &ASPathPrependAction{
+		asn:   asn,
+		times: times,
+	}
+}
+
+func (a *ASPathPrependAction) Do(p net.Prefix, pa *route.Path) (modPath *route.Path, reject bool) {
+	if pa.BGPPath == nil {
+		return pa, false
+	}
+
+	modified := pa.Copy()
+	modified.BGPPath.Prepend(a.asn, a.times)
+
+	return modified, false
+}
diff --git a/routingtable/filter/actions/as_path_prepend_action_test.go b/routingtable/filter/actions/as_path_prepend_action_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..348116ccb33b7e067e7e50effa438b9eb0d42c3a
--- /dev/null
+++ b/routingtable/filter/actions/as_path_prepend_action_test.go
@@ -0,0 +1,59 @@
+package actions
+
+import (
+	"testing"
+
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/route"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAppendPath(t *testing.T) {
+	tests := []struct {
+		name           string
+		times          uint16
+		bgpPath        *route.BGPPath
+		expectedPath   string
+		expectedLength uint16
+	}{
+		{
+			name: "BGPPath is nil",
+		},
+		{
+			name:  "append 0",
+			times: 0,
+			bgpPath: &route.BGPPath{
+				ASPath:    "12345 12345",
+				ASPathLen: 2,
+			},
+			expectedPath:   "12345 12345",
+			expectedLength: 2,
+		},
+		{
+			name:  "append 3",
+			times: 3,
+			bgpPath: &route.BGPPath{
+				ASPath:    "12345 15169",
+				ASPathLen: 2,
+			},
+			expectedPath:   "12345 12345 12345 12345 15169",
+			expectedLength: 5,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(te *testing.T) {
+			a := NewASPathPrependAction(12345, test.times)
+			p, _ := a.Do(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{
+				BGPPath: test.bgpPath,
+			})
+
+			if test.bgpPath == nil {
+				return
+			}
+
+			assert.Equal(te, test.expectedPath, p.BGPPath.ASPath, "ASPath")
+			assert.Equal(te, test.expectedLength, p.BGPPath.ASPathLen, "ASPathLen")
+		})
+	}
+}
diff --git a/routingtable/filter/actions/filter_action.go b/routingtable/filter/actions/filter_action.go
new file mode 100644
index 0000000000000000000000000000000000000000..7a2665146922719766b880f9093eb1049c6ae018
--- /dev/null
+++ b/routingtable/filter/actions/filter_action.go
@@ -0,0 +1,10 @@
+package actions
+
+import (
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/route"
+)
+
+type FilterAction interface {
+	Do(p net.Prefix, pa *route.Path) (modPath *route.Path, reject bool)
+}
diff --git a/routingtable/filter/actions/set_local_pref_action.go b/routingtable/filter/actions/set_local_pref_action.go
new file mode 100644
index 0000000000000000000000000000000000000000..e60b6faa0ab5e4e986f785d7b8322db2c6492e34
--- /dev/null
+++ b/routingtable/filter/actions/set_local_pref_action.go
@@ -0,0 +1,27 @@
+package actions
+
+import (
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/route"
+)
+
+type SetLocalPrefAction struct {
+	pref uint32
+}
+
+func NewSetLocalPrefAction(pref uint32) *SetLocalPrefAction {
+	return &SetLocalPrefAction{
+		pref: pref,
+	}
+}
+
+func (a *SetLocalPrefAction) Do(p net.Prefix, pa *route.Path) (modPath *route.Path, reject bool) {
+	if pa.BGPPath == nil {
+		return pa, false
+	}
+
+	modified := *pa
+	modified.BGPPath.LocalPref = a.pref
+
+	return &modified, false
+}
diff --git a/routingtable/filter/actions/set_local_pref_action_test.go b/routingtable/filter/actions/set_local_pref_action_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..0f21a637379bf7c0018db89a0fab0a46deadd5b0
--- /dev/null
+++ b/routingtable/filter/actions/set_local_pref_action_test.go
@@ -0,0 +1,46 @@
+package actions
+
+import (
+	"testing"
+
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/route"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSetLocalPref(t *testing.T) {
+	tests := []struct {
+		name              string
+		bgpPath           *route.BGPPath
+		expectedLocalPref uint32
+	}{
+		{
+			name: "BGPPath is nil",
+		},
+		{
+			name: "modify path",
+			bgpPath: &route.BGPPath{
+				LocalPref: 100,
+			},
+			expectedLocalPref: 150,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(te *testing.T) {
+			a := NewSetLocalPrefAction(150)
+			p, _ := a.Do(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{
+				BGPPath: test.bgpPath,
+			})
+
+			if test.expectedLocalPref > 0 {
+				assert.Equal(te, test.expectedLocalPref, p.BGPPath.LocalPref)
+			}
+		})
+	}
+}
+
+func strAddr(s string) uint32 {
+	ret, _ := net.StrToAddr(s)
+	return ret
+}
diff --git a/routingtable/filter/actions/set_nexthop_action.go b/routingtable/filter/actions/set_nexthop_action.go
new file mode 100644
index 0000000000000000000000000000000000000000..f178f61802882157856fc1f4bf15c8d9f0b08dc6
--- /dev/null
+++ b/routingtable/filter/actions/set_nexthop_action.go
@@ -0,0 +1,27 @@
+package actions
+
+import (
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/route"
+)
+
+type SetNextHopAction struct {
+	addr uint32
+}
+
+func NewSetNextHopAction(addr uint32) *SetNextHopAction {
+	return &SetNextHopAction{
+		addr: addr,
+	}
+}
+
+func (a *SetNextHopAction) Do(p net.Prefix, pa *route.Path) (modPath *route.Path, reject bool) {
+	if pa.BGPPath == nil {
+		return pa, false
+	}
+
+	modified := pa.Copy()
+	modified.BGPPath.NextHop = a.addr
+
+	return modified, false
+}
diff --git a/routingtable/filter/actions/set_nexthop_action_test.go b/routingtable/filter/actions/set_nexthop_action_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..7501e6bdcc388c65577cea8ce0a425acf8b7d3e3
--- /dev/null
+++ b/routingtable/filter/actions/set_nexthop_action_test.go
@@ -0,0 +1,41 @@
+package actions
+
+import (
+	"testing"
+
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/route"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSetNextHopTest(t *testing.T) {
+	tests := []struct {
+		name     string
+		bgpPath  *route.BGPPath
+		expected uint32
+	}{
+		{
+			name: "BGPPath is nil",
+		},
+		{
+			name: "modify path",
+			bgpPath: &route.BGPPath{
+				NextHop: strAddr("100.64.2.1"),
+			},
+			expected: strAddr("100.64.2.1"),
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(te *testing.T) {
+			a := NewSetNextHopAction(strAddr("100.64.2.1"))
+			p, _ := a.Do(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{
+				BGPPath: test.bgpPath,
+			})
+
+			if test.expected > 0 {
+				assert.Equal(te, test.expected, p.BGPPath.NextHop)
+			}
+		})
+	}
+}