Skip to content

Commit 8366a58

Browse files
committed
Add PPP over Hysteria2
1 parent 26a03b0 commit 8366a58

91 files changed

Lines changed: 26370 additions & 60 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/cmd/client.go

Lines changed: 114 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"github.com/apernet/hysteria/app/v2/internal/url"
3838
"github.com/apernet/hysteria/app/v2/internal/utils"
3939
"github.com/apernet/hysteria/core/v2/client"
40+
coreErrs "github.com/apernet/hysteria/core/v2/errors"
4041
"github.com/apernet/hysteria/extras/v2/correctnet"
4142
"github.com/apernet/hysteria/extras/v2/obfs"
4243
"github.com/apernet/hysteria/extras/v2/realm"
@@ -90,6 +91,28 @@ type clientConfig struct {
9091
UDPTProxy *udpTProxyConfig `mapstructure:"udpTProxy"`
9192
TCPRedirect *tcpRedirectConfig `mapstructure:"tcpRedirect"`
9293
TUN *tunConfig `mapstructure:"tun"`
94+
PPP *pppConfig `mapstructure:"ppp"`
95+
}
96+
97+
type pppSSTPConfig struct {
98+
BinaryPath string `mapstructure:"binaryPath"`
99+
Listen string `mapstructure:"listen"`
100+
CertDir string `mapstructure:"certDir"`
101+
Endpoint string `mapstructure:"endpoint"`
102+
User string `mapstructure:"user"`
103+
Password string `mapstructure:"password"`
104+
MSSClamp *int `mapstructure:"mssClamp"` // nil=auto, 0=off, >0=forced
105+
ServerRoute *bool `mapstructure:"serverRoute"`
106+
LogLevel string `mapstructure:"logLevel"`
107+
}
108+
109+
type pppConfig struct {
110+
Mode string `mapstructure:"mode"`
111+
MTU uint32 `mapstructure:"mtu"`
112+
PPPDPath string `mapstructure:"pppdPath"`
113+
PPPDArgs []string `mapstructure:"pppdArgs"`
114+
DataStreams int `mapstructure:"dataStreams"`
115+
SSTP *pppSSTPConfig `mapstructure:"sstp"`
93116
}
94117

95118
type clientConfigRealm struct {
@@ -618,6 +641,9 @@ func (c *clientConfig) Config() (*client.Config, error) {
618641
return nil, err
619642
}
620643
}
644+
if c.PPP != nil && c.PPP.DataStreams == 0 {
645+
hyConfig.PPPMode = true
646+
}
621647
return hyConfig, nil
622648
}
623649

@@ -810,29 +836,75 @@ func runClientCmd(cmd *cobra.Command, args []string) {
810836
runClient(defaultViper)
811837
}
812838

839+
// authRetryDelay is how long to wait before asking a second time about a refused
840+
// password. Short enough to stay inside the restart floor that is being spent
841+
// anyway, long enough to outlast the momentary gap it exists to tolerate.
842+
const authRetryDelay = 2 * time.Second
843+
844+
// connectClient builds the client, and when the server refuses the password asks
845+
// once more before believing it.
846+
//
847+
// AuthError carries no more meaning than "the server answered with something
848+
// other than 233". A server whose auth backend is momentarily unavailable serves
849+
// the request through its masquerade handler instead -- a 404 -- which is the
850+
// same answer a genuinely wrong password gets, and the client cannot tell them
851+
// apart. With lazy off this handshake is a single un-retried attempt, and the
852+
// caller turns an AuthError into a status file that makes the netifd handler
853+
// call proto_block_restart. One unlucky moment would then hold the WAN down
854+
// until somebody logged in and ran ifup, where before it recovered by itself.
855+
//
856+
// Asking twice separates the two: a wrong password is refused both times, a gap
857+
// almost never lasts across the delay. Only an auth failure is retried -- every
858+
// other error is already treated as retryable and is left to netifd.
859+
func connectClient(config clientConfig, connected func(client.Client, *client.HandshakeInfo, int)) (client.Client, error) {
860+
c, err := client.NewReconnectableClient(config.Config, connected, config.Lazy)
861+
var authErr coreErrs.AuthError
862+
if err == nil || !errors.As(err, &authErr) {
863+
return c, err
864+
}
865+
logger.Warn("server refused the password, confirming before reporting it as final",
866+
zap.Int("status", authErr.StatusCode))
867+
time.Sleep(authRetryDelay)
868+
return client.NewReconnectableClient(config.Config, connected, config.Lazy)
869+
}
870+
813871
func runClient(v *viper.Viper) {
814872
if err := v.ReadInConfig(); err != nil {
873+
holdPPPRestart()
815874
logger.Fatal("failed to read client config", zap.Error(err))
816875
}
817876
var config clientConfig
818877
if err := v.Unmarshal(&config); err != nil {
878+
holdPPPRestart()
819879
logger.Fatal("failed to parse client config", zap.Error(err))
820880
}
821881

822-
c, err := client.NewReconnectableClient(
823-
config.Config,
824-
func(c client.Client, info *client.HandshakeInfo, count int) {
825-
connectLog(info, count)
826-
// On the client side, we start checking for updates after we successfully connect
827-
// to the server, which, depending on whether lazy mode is enabled, may or may not
828-
// be immediately after the client starts. We don't want the update check request
829-
// to interfere with the lazy mode option.
830-
if count == 1 && !disableUpdateCheck {
831-
go runCheckUpdateClient(c)
832-
}
833-
}, config.Lazy,
834-
)
882+
connected := func(c client.Client, info *client.HandshakeInfo, count int) {
883+
connectLog(info, count)
884+
// On the client side, we start checking for updates after we successfully connect
885+
// to the server, which, depending on whether lazy mode is enabled, may or may not
886+
// be immediately after the client starts. We don't want the update check request
887+
// to interfere with the lazy mode option.
888+
if count == 1 && !disableUpdateCheck {
889+
go runCheckUpdateClient(c)
890+
}
891+
}
892+
893+
c, err := connectClient(config, connected)
835894
if err != nil {
895+
// Two unrelated things share this line. The status write is where a
896+
// refused Hysteria2 password is reported from: with lazy off the
897+
// handshake has already happened here, so it never reaches the runner
898+
// below. The hold is for the opposite case -- a dropped upstream fails
899+
// this dial in microseconds, and without it that becomes a restart loop.
900+
writePPPStatus(err)
901+
// And the state file, which reports every failure rather than only the
902+
// permanent ones. A handshake refused here never reaches PPP mode, so
903+
// without this the link's last published state is whatever it was before
904+
// the interface was rebuilt -- which for a link that was working a moment
905+
// ago is "connected".
906+
writePPPStateDown(err)
907+
holdPPPRestart()
836908
logger.Fatal("failed to initialize client", zap.Error(err))
837909
}
838910
defer c.Close()
@@ -887,6 +959,11 @@ func runClient(v *viper.Viper) {
887959
return clientTUN(*config.TUN, c)
888960
})
889961
}
962+
if config.PPP != nil {
963+
runner.Add("PPP", func() error {
964+
return clientPPP(*config.PPP, c, strings.EqualFold(config.Obfs.Type, "salamander"))
965+
})
966+
}
890967

891968
signalChan := make(chan os.Signal, 1)
892969
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
@@ -904,7 +981,31 @@ func runClient(v *viper.Viper) {
904981
if r.OK {
905982
logger.Info(r.Msg)
906983
} else {
984+
// A connect that fails only reaches here in lazy mode, where the
985+
// handshake is deferred until PPP mode asks for the conn. With lazy
986+
// off -- which is what the OpenWrt handler generates, since it never
987+
// writes the key -- the handshake has already happened above, and a
988+
// refused password fails there instead. Both sites write the status.
989+
writePPPStatus(r.Err)
990+
writePPPStateDown(r.Err)
907991
_ = c.Close() // Close the client here as Fatal will exit the program without running defer
992+
// Hand SIGTERM back to the runtime before holding. The deferred Stop
993+
// never runs -- Fatal exits -- so without this an ifdown arriving
994+
// during the hold would sit unread in signalChan and be ignored for
995+
// the length of it, keeping netifd waiting on a process that has
996+
// already decided to die.
997+
signal.Stop(signalChan)
998+
// Stop only restores the default disposition for signals still to
999+
// come. One that arrived while this select was being decided is
1000+
// already buffered, and the select is free to have picked the runner
1001+
// case instead -- so it has to be looked for, not waited for. Finding
1002+
// one means an ifdown is in progress and nothing is going to restart
1003+
// us, which is exactly when holding is pure delay.
1004+
select {
1005+
case <-signalChan:
1006+
default:
1007+
holdPPPRestart()
1008+
}
9081009
if r.Err != nil {
9091010
logger.Fatal(r.Msg, zap.Error(r.Err))
9101011
} else {

0 commit comments

Comments
 (0)