ooni-probe-cli/internal/cmd/jafar/iptables/iptables_integration_test.go
Simone Basso 15da0f5344
cleanup(jafar): do not depend on netx and urlgetter (#792)
There's no point in doing that. Also, once this change is merged, it becomes easier to cleanup/simplify netx.

See https://github.com/ooni/probe/issues/2121
2022-06-02 22:25:37 +02:00

349 lines
8.5 KiB
Go

package iptables
import (
"context"
"errors"
"net"
"net/http"
"net/http/httptest"
"net/url"
"runtime"
"strings"
"testing"
"time"
"golang.org/x/sys/execabs"
"github.com/apex/log"
"github.com/ooni/probe-cli/v3/internal/cmd/jafar/resolver"
"github.com/ooni/probe-cli/v3/internal/cmd/jafar/uncensored"
"github.com/ooni/probe-cli/v3/internal/shellx"
)
func init() {
log.SetLevel(log.ErrorLevel)
}
func newCensoringPolicy() *CensoringPolicy {
policy := NewCensoringPolicy()
policy.Waive() // start over to allow for repeated tests on failure
return policy
}
func TestCannotApplyPolicy(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("not implemented on this platform")
}
if testing.Short() {
t.Skip("skip test in short mode")
}
policy := newCensoringPolicy()
defer policy.Waive()
policy.DropIPs = []string{"antani"}
if err := policy.Apply(); err == nil {
t.Fatal("expected an error here")
}
}
func TestCreateChainsError(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("not implemented on this platform")
}
if testing.Short() {
t.Skip("skip test in short mode")
}
policy := newCensoringPolicy()
defer policy.Waive()
if err := policy.Apply(); err != nil {
t.Fatal(err)
}
// you should not be able to apply the policy when there is
// already a policy, you need to waive it first
if err := policy.Apply(); err == nil {
t.Fatal("expected an error here")
}
}
func TestDropIP(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("not implemented on this platform")
}
if testing.Short() {
t.Skip("skip test in short mode")
}
policy := newCensoringPolicy()
defer policy.Waive()
policy.DropIPs = []string{"1.1.1.1"}
if err := policy.Apply(); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
conn, err := (&net.Dialer{}).DialContext(ctx, "tcp", "1.1.1.1:853")
if err == nil {
t.Fatalf("expected an error here")
}
if err.Error() != "dial tcp 1.1.1.1:853: i/o timeout" {
t.Fatal("unexpected error occurred")
}
if conn != nil {
t.Fatal("expected nil connection here")
}
}
func TestDropKeyword(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("not implemented on this platform")
}
if testing.Short() {
t.Skip("skip test in short mode")
}
policy := newCensoringPolicy()
defer policy.Waive()
policy.DropKeywords = []string{"ooni.io"}
if err := policy.Apply(); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
req, err := http.NewRequest("GET", "http://www.ooni.io", nil)
if err != nil {
t.Fatal(err)
}
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
if err == nil {
t.Fatal("expected an error here")
}
if !strings.HasSuffix(err.Error(), "context deadline exceeded") {
t.Fatal("unexpected error occurred")
}
if resp != nil {
t.Fatal("expected nil response here")
}
}
func TestDropKeywordHex(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("not implemented on this platform")
}
if testing.Short() {
t.Skip("skip test in short mode")
}
policy := newCensoringPolicy()
defer policy.Waive()
policy.DropKeywordsHex = []string{"|6f 6f 6e 69|"}
if err := policy.Apply(); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
reso := &net.Resolver{
PreferGo: true,
}
addrs, err := reso.LookupHost(ctx, "www.ooni.io")
if err == nil {
t.Fatal("expected an error here")
}
// the error we see with GitHub Actions is different from the error
// we see when testing locally on Fedora
if !strings.HasSuffix(err.Error(), "operation not permitted") &&
!strings.HasSuffix(err.Error(), "Temporary failure in name resolution") &&
!strings.HasSuffix(err.Error(), "no such host") {
t.Fatalf("unexpected error occurred: %+v", err)
}
if addrs != nil {
t.Fatal("expected nil response here")
}
}
func TestResetIP(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("not implemented on this platform")
}
if testing.Short() {
t.Skip("skip test in short mode")
}
policy := newCensoringPolicy()
defer policy.Waive()
policy.ResetIPs = []string{"1.1.1.1"}
if err := policy.Apply(); err != nil {
t.Fatal(err)
}
conn, err := (&net.Dialer{}).Dial("tcp", "1.1.1.1:853")
if err == nil {
t.Fatalf("expected an error here")
}
if err.Error() != "dial tcp 1.1.1.1:853: connect: connection refused" {
t.Fatal("unexpected error occurred")
}
if conn != nil {
t.Fatal("expected nil connection here")
}
}
func TestResetKeyword(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("not implemented on this platform")
}
if testing.Short() {
t.Skip("skip test in short mode")
}
policy := newCensoringPolicy()
defer policy.Waive()
policy.ResetKeywords = []string{"ooni.io"}
if err := policy.Apply(); err != nil {
t.Fatal(err)
}
resp, err := http.Get("http://www.ooni.io")
if err == nil {
t.Fatal("expected an error here")
}
if strings.Contains(err.Error(), "read: connection reset by peer") == false {
t.Fatal("unexpected error occurred")
}
if resp != nil {
t.Fatal("expected nil response here")
}
}
func TestResetKeywordHex(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("not implemented on this platform")
}
if testing.Short() {
t.Skip("skip test in short mode")
}
policy := newCensoringPolicy()
defer policy.Waive()
policy.ResetKeywordsHex = []string{"|6f 6f 6e 69|"}
if err := policy.Apply(); err != nil {
t.Fatal(err)
}
resp, err := http.Get("http://www.ooni.io")
if err == nil {
t.Fatal("expected an error here")
}
if strings.Contains(err.Error(), "read: connection reset by peer") == false {
t.Fatal("unexpected error occurred")
}
if resp != nil {
t.Fatal("expected nil response here")
}
}
func TestHijackDNS(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("not implemented on this platform")
}
if testing.Short() {
t.Skip("skip test in short mode")
}
resolver := resolver.NewCensoringResolver(
[]string{"ooni.io"}, nil, nil,
uncensored.NewClient("https://1.1.1.1/dns-query"),
)
server, err := resolver.Start("127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer server.Shutdown()
policy := newCensoringPolicy()
defer policy.Waive()
policy.HijackDNSAddress = server.PacketConn.LocalAddr().String()
if err := policy.Apply(); err != nil {
t.Fatal(err)
}
reso := &net.Resolver{
PreferGo: true,
}
addrs, err := reso.LookupHost(context.Background(), "www.ooni.io")
if err == nil {
t.Fatal("expected an error here")
}
if strings.Contains(err.Error(), "no such host") == false {
t.Fatal("unexpected error occurred")
}
if addrs != nil {
t.Fatal("expected nil addrs here")
}
}
func TestHijackHTTP(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("not implemented on this platform")
}
if testing.Short() {
t.Skip("skip test in short mode")
}
// Implementation note: this test is complicated by the fact
// that we are running as root and so we're whitelisted.
server := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(451)
}),
)
defer server.Close()
policy := newCensoringPolicy()
defer policy.Waive()
pu, err := url.Parse(server.URL)
if err != nil {
t.Fatal(err)
}
policy.HijackHTTPAddress = pu.Host
if err := policy.Apply(); err != nil {
t.Fatal(err)
}
err = shellx.Run(log.Log, "sudo", "-u", "nobody", "--",
"curl", "-sf", "http://example.com")
if err == nil {
t.Fatal("expected an error here")
}
var exitErr *execabs.ExitError
if !errors.As(err, &exitErr) {
t.Fatal("not the error type we expected")
}
if exitErr.ExitCode() != 22 {
t.Fatal("not the exit code we expected")
}
}
func TestHijackHTTPS(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("not implemented on this platform")
}
if testing.Short() {
t.Skip("skip test in short mode")
}
// Implementation note: this test is complicated by the fact
// that we are running as root and so we're whitelisted.
server := httptest.NewTLSServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(451)
}),
)
defer server.Close()
policy := newCensoringPolicy()
defer policy.Waive()
pu, err := url.Parse(server.URL)
if err != nil {
t.Fatal(err)
}
policy.HijackHTTPSAddress = pu.Host
if err := policy.Apply(); err != nil {
t.Fatal(err)
}
err = shellx.Run(log.Log, "sudo", "-u", "nobody", "--",
"curl", "-sf", "https://example.com")
if err == nil {
t.Fatal("expected an error here")
}
t.Log(err)
var exitErr *execabs.ExitError
if !errors.As(err, &exitErr) {
t.Fatal("not the error type we expected")
}
if exitErr.ExitCode() != 60 {
t.Fatal("not the exit code we expected")
}
}